Skip to Content
These docs describe PRME 0.16.0a8, an alpha. pip install prme installs the latest stable release. To use these docs as written, run pip install prme==0.16.0a8. Changelog
ReferenceChangelog

Changelog

All notable changes to PRME will be documented in this file.

The format is based on Keep a Changelog , and this project adheres to Semantic Versioning .

Unreleased 

0.16.0a8  - 2026-10-22

This is an alpha. It passed CI and the cheap fast tiers and the offline gate, replayed from the recorded caches with no live model call, and it has not been through the full benchmark or the mid tier. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a8.

Added

  • Add enable_pronoun_repair (PRME_ENABLE_PRONOUN_REPAIR, off by default; part of dwamianm/prism#345 and #347): a source whose first extraction left a claim on a pronoun subject or object (they, he, she, it and the like; never a first person pronoun) is asked a second time, shown the owner’s preceding memories (up to extraction_window_turns) and only that claim’s own sentences. The claim is repaired only when the answer names someone written in the sentence, the message, the speaker or a memory shown: its text becomes its own sentence with the names written in, the quote stays its evidence, metadata.resolved_from_context lists the memories that wrote the names, and no other claim of the message changes. It needs nothing else on, works in ingest(), store(extract=True) and reingest(), and counts in the re-derivation settings. With the option off every extraction is unchanged. The measurements are in BENCHMARKS.md. It is on by default in the alpha channel (see Changed).
  • A store can switch embedding models (dwamianm/prism#368, behind enable_multi_model_embeddings, off by default). A stored vector means something only under the model that made it, and a store was tied to its model: DuckDB had a rebuild but a write under another dimension failed, and PostgreSQL, with its fixed vector(N) column, had no way to switch at all (rebuild_indexes() raised while the mismatch error told you to run prme rebuild). With the option on, vectors are kept by node, model name and model version, one index per model: one USearch file per model on DuckDB (the pack’s vectors.usearch stays the first model’s), and a node_embeddings table with one partial HNSW index per model on PostgreSQL (the reasons are in docs/EMBEDDING-MODELS.md). embedding.model_name is the active model that searches read, and the new embedding.additional_models (PRME_EMBEDDING__ADDITIONAL_MODELS) are also written on every store, including what ingest(), reingest() and consolidation derive. MemoryEngine.reembed() (MemoryClient.reembed(), POST /v1/reembed, the memory_reembed MCP tool and prme reembed, which also takes --postgres) builds a model’s vectors for the stored memories with the local model: in batches, with a cursor, a pause between batches, total, done, remaining and failed as its progress, safe to stop and resume and to run while the store is read and written, and it calls no model service. Setting embedding.model_name to a model whose index is complete is the switch, and the old index stays until drop_embeddings() (prme embeddings drop); embedding_status() and prme embeddings status list the models a store holds. A store whose vectors were all made by another model than the configured one is not mixed: writes are refused before they store anything, with an error that names the stored and the configured model and their dimensions, and retrieval metadata and receipts carry embedding_incomplete with the counts of memories without a vector (beside the existing embedding_mismatch). An existing store is adopted when the option is first turned on, with no re-embedding: a DuckDB pack gives its file to the model most of its vectors came from, and a PostgreSQL store’s nodes.embedding vectors are copied once into the new table, in resumable batches under an advisory lock, with the column left as it was. With the option off, nothing is constructed, vectors are stored as before and the configuration dump is unchanged. Single-file packs are not supported with the option yet. prme rebuild stays DuckDB only, and its error on PostgreSQL now names reembed. benchmarks.embedding_models job times the job. A protected store (one under a privacy policy) refuses reembed(), embedding_status() and drop_embeddings(), as it refuses rebuild_indexes() and embed_texts(). Not part of this change: choosing another default model.
  • Options for the task prefixes some local embedding models are trained with (dwamianm/prism#366): embedding.query_prefix (PRME_EMBEDDING__QUERY_PREFIX) is put before every search question the fastembed model embeds, and embedding.document_prefix (PRME_EMBEDDING__DOCUMENT_PREFIX) before every stored text. Both are off by default, and with neither set the provider, its version and the configuration dump are unchanged. When one is set, the stored vectors’ model version names the recipe, so vectors made under one recipe are refused under another and a store needs its vectors rebuilt. This is the experiment’s only change to the engine: it changes no default, and the experiment’s report (BENCHMARKS.md, “Embedding models”) recommends none.

Changed

  • enable_pronoun_repair is on by default in the alpha channel. It was turned on by the maintainer’s decision on 2026-10-22. The alpha pass rule’s defect probe condition (7 of 7 answers in the top 3) was not met, because the extraction model used in production scores 6 of 7 with the option off, and the failing probe has no pronoun in it. No mid tier or full benchmark was run on it, and one memory in the fixtures is repaired (the cities claim, from 23rd to 1st). PRME_ENABLE_PRONOUN_REPAIR=false (or enable_pronoun_repair=False) restores the extraction every earlier release made, byte for byte. Two effects to know before upgrading: a source whose first extraction leaves a claim on a third person pronoun now makes one more model request, and because the repair request and its window are part of the re-derivation settings, reingest() reads every memory derived before the change as stale. The benchmark harness names the setting itself, so the benchmark baselines and their caches replay unchanged and cache-only.

Tooling

These do not ship in the wheel and change no behavior.

  • The embedding model experiment (dwamianm/prism#366, BENCHMARKS.md, “Embedding models”) and its job timer, python -m benchmarks.embedding_models. Its report recommends no new default model.

Upgrade notes

  • One default changed since 0.16.0a7: enable_pronoun_repair is on, in the alpha channel, by the maintainer’s decision of 2026-10-22, and without meeting the alpha rule’s defect probe condition (see Changed). PRME_ENABLE_PRONOUN_REPAIR=false restores the extraction of every earlier release, byte for byte. A source whose first extraction leaves a claim on a third person pronoun costs one more model request, and reingest() reads every memory derived before this release as stale, because the repair request and its window are part of the re-derivation settings; one reingest() makes them current again.
  • New and off by default, with no effect until you set them: enable_multi_model_embeddings and embedding.additional_models (with reembed(), POST /v1/reembed, the memory_reembed tool, prme reembed and prme embeddings status and drop), and the two prefix options embedding.query_prefix and embedding.document_prefix. With the first on, a PostgreSQL store gets a node_embeddings table and copies its existing vectors into it once, in resumable batches, leaving nodes.embedding as it was. prme rebuild still works on DuckDB only, and its error on PostgreSQL now names reembed. Single-file packs are not supported with the option yet.
  • No other default changed since 0.16.0a7. The defaults of 0.16.0a7 are as they were, including the exact pin of cryptography to 50.0.2 and the privacy extra that does not install the sanitizer’s model (see the notes of 0.16.0a6).
  • Known weaknesses are as in 0.16.0a7. In addition, the pronoun repair default rests on one repaired fixture memory (the cities claim of the fast cycle, from 23rd to 1st) and no mid tier or full benchmark run.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a7  - 2026-10-21

This is an alpha. It passed CI and the cheap fast tiers, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a7.

Added

  • Separate admitted memory from sealed source evidence in reads (dwamianm/prism#359, part of issue #323). This is protected-store work, opt in with the privacy policies of 0.16.0a6. Ordinary sealed-mode reads return admitted statements and permitted metadata across retrieval, graph, history, snapshots, the exact-state and aggregate APIs, HTTP and MCP, the CLI, the explorer and the supported framework adapters, and source-only text needs explicit selected-source access. Responses keep conditions, negation, effective time and provenance, and report unavailable source coverage. Exact event, node, provenance and source-inspection reads now authorize their actual contributing records, so a protected record in another scope or project no longer makes an unrelated exact read fail or deadlock; broad and ambiguous reads keep the complete-range checks, and orphan markers, ownership, selected-source grants and final deadlines still fail closed. The pull request lists what it does not do: shared whole-case acceptance is pending (issues #324 and #325), sanitization stays bounded, permitted facts can remain sensitive, and source access does not establish that a fact is true or allow an exact historical replay. It changes 26 source files, among them the retrieval pipeline, candidate generation, session and evidence context, the reranker and the temporal modules, and the pull request does not say what it does to a store with no policy. So this release was checked on such a store: the defect probes, chat probe, corrections probe, fast cycle and offline evidence gate are unchanged from 0.16.0a6.

Changed

  • reingest() no longer reads the package version into its settings fingerprint (part of dwamianm/prism#350). Every release changed the fingerprint, so after an upgrade every stored memory read as stale and a dry run offered to re-derive all of them, with a model request each, though the release had not touched extraction. The fingerprint now carries DERIVATION_VERSION, an integer in src/prme/ingestion/reingest.py that a developer bumps by one only when a change alters what an unchanged source derives (the grounding checks, planning, linking, supersedence or claim text rules, or the extraction schema). The prompts, the model, the window options and the other options that decide what is derived are in it as before, and with the window options off the settings keep the keys they had. A release that changes none of those leaves every source current. tests/test_derivation_version.py derives a recorded extraction (a fixture under tests/fixtures/extraction/) with a stub provider and no model, and fails when the nodes and edges differ from the saved dump (tests/fixtures/derivation/trip_derived.json), with a message that says to bump the number and refresh the dump. Migration: a source marked under an earlier fingerprint reads as stale once, by design, because the new settings differ from the old ones; after one more reingest() it is current under the new fingerprint and stays so across releases. CONTRIBUTING.md and CLAUDE.md say when to bump.

Fixed

  • Candidate generation waits for both nested pinned and task queries before it propagates a failure (dwamianm/prism#359). Before, a caller could begin database teardown while a sibling query was still running.

Tooling

These do not ship in the wheel and change no behavior.

  • Routing gate for the typed walk (python -m benchmarks.graph_oracle routed, tooling only; part of dwamianm/prism#351): a rule based question shape router, a one statement SQL walk for PostgreSQL and DuckDB with per entity caps, a new memory set with its gold graph, a new blind draw of 65 questions and a gate fixed before the run (benchmarks/graph_oracle/GATE.md). The gate failed on two of six conditions; the result is in BENCHMARKS.md.

  • Scale test for the typed walk (python -m benchmarks.graph_oracle scale, tooling only; part of dwamianm/prism#351): the four fixtures combined for one owner and padded to 500, 2,000 and 10,000 memories with ordinary conversation turns or renamed look-alike copies, a gate fixed before the run (benchmarks/graph_oracle/SCALE.md), an embedding cache and the walk’s latency on DuckDB and PostgreSQL. The graph’s advantage did not grow with store size; the result is in BENCHMARKS.md.

Upgrade notes

  • One change since 0.16.0a6, and it is not a default: the settings fingerprint of reingest() now carries DERIVATION_VERSION (1) instead of the package version. A source that reingest() marked under 0.16.0a5 or 0.16.0a6 was marked under the old fingerprint, so it reads as stale once, and a dry run offers to re-derive it, with one model request per batch. After one more reingest() it is current, and it stays current across later releases that leave what is derived as it was. Stores that never ran reingest() are not affected, and nothing is rewritten by the upgrade.
  • No default changed since 0.16.0a6, and no option was added. The sealed-store read separation of #359 applies to stores that use a privacy policy. Every default of 0.16.0a6 is as it was, including the exact pin of cryptography to 50.0.2 (see the notes of 0.16.0a6).
  • Known weaknesses are as in 0.16.0a6.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a6  - 2026-10-20

This is an alpha. It passed CI and the fast tiers, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a6.

Added

  • Graph oracle (python -m benchmarks.graph_oracle, tooling only, no engine or default change; part of dwamianm/prism#351): hand written gold graphs for the defects-v1, store-v1 and chat-v1 corpora (benchmarks/fixtures/graph-oracle/), a design for the ideal graph (benchmarks/graph_oracle/DESIGN.md) and a deterministic probe that writes a gold graph into the graph store over memories stored with store() and ranks every question by retrieve(), by the gold query spec (the upper bound of the shape), by a generic typed walk and by rank fusion of the walk with the text list, with ablations, a cost table, a compare command and a fidelity command that scores an extracted graph against a gold graph. It stores relation labels in edge metadata on the existing edge types and adds no edge type. See BENCHMARKS.md.

  • Blind validation of the typed walk (python -m benchmarks.graph_oracle blind, latency; tooling only; part of dwamianm/prism#351): 86 questions a local model wrote about the three memory sets from the memories alone, three paraphrases of each, relation cue words derived from the vocabulary definitions, a decision rule fixed before the run (benchmarks/graph_oracle/BLIND.md) and the walk’s latency on synthetic 2,000 and 10,000 memory packs in DuckDB and PostgreSQL. The result is in BENCHMARKS.md.

  • Add enable_owner_window_extraction (PRME_ENABLE_OWNER_WINDOW_EXTRACTION, off by default; dwamianm/prism#345 and #347): with enable_windowed_extraction, a turn with no session_id gets the owner’s preceding memories in the same scope as its window, so a plainly stored memory such as “They chose Tokyo, Kyoto, Kanazawa, and Osaka.” is read with the memories before it. The window options and this one stay off: on the fast tiers no combination passed the alpha rule (defect probes, chat-v1 and the fast cycle’s ingest verdict), so nothing flips. With both window options on, the memory’s claim moved from 30th to 6th and 9th for the fast cycle’s Japan question, and chat-v1 fell from 14 to 10 answers in the top 3. BENCHMARKS.md (“Windowed extraction on memories that name no one”) has the numbers and one bounded alternative.

  • reingest() no longer needs batched extraction. While enable_windowed_extraction or enable_fact_text_resolution has turned batching off, each source is its own request, shown the turns before it. The window settings, fact text resolution and their prompts are part of the settings fingerprint, so sources re-derived without an option are re-derived again when it is turned on; with both off the fingerprint is what it was.

  • get_by_user() on both event stores accepts before, an event: only events that come before it in the result order are returned, so the first is the event just before it.

  • MemoryEngine.get_events() and both event stores accept until, an ingestion time: only events recorded at or before it are returned.

  • Add scoring.rrf_graph_seed_count (PRME_SCORING__RRF_GRAPH_SEED_COUNT), on by default in the alpha channel: rank fusion’s graph list now tells records apart, so a “We” claim linked to the people a question names can rise. A record joins the list only when two or more of the question’s seeds reach it within graph_max_hops, one of them by a direct link, along links that never pass through the owner’s entity, and when the semantic search ranks it in its top 10; the list ranks more seeds first, then fewer total hops, and its weight is rrf_graph_weight, 0.5 when unset. A record that session, episode or evidence augmentation context pulls in inherits its trigger’s score without that graph term; evidence projection, which replaces a claim with its source, keeps it. With the group links (enable_owner_group_links) it puts “We chose Tokyo, Kyoto, Kanazawa, and Osaka.” 4th for “which cities will alex visit in japan?” on the defects-v1 chat probe (6th without it). On the fast tiers the defect probes keep 7 of 7 answers in the top 3 and the chat probe 14 of 18 (15 of 18 in the top 5), the fast cycle passes with three answers one place lower (travel 2 to 3, coffee 4 to 5, job-now 7 to 8), and the offline evidence gate and the mid tier show no change (LoCoMo evidence in the top 25 70.3% on both sides). Two earlier versions of the rule failed: without the semantic gate the defect probes lost an answer from the top 3 and the mid tier lost 5.2 points of LoCoMo evidence in the top 25.

    • Limits: only a record the graph search keeps among its candidates (graph_max_candidates, 150, newest first at equal confidence) can join the list, so on a large memory an old answer may not; and retrieval does not read I, my or we as the owner, so a first-person question (“which cities will we visit in Japan?”) has one seed at most and the list does nothing for it.
    • Cost: a question that names two or more entities reads the links around them once more, so the cost grows with their neighborhood. On a 400-turn LoCoMo conversation it adds about 60 ms per retrieval (median); on synthetic DuckDB packs about 0.1 s at 2,000 turns and 0.5 s at 10,000 when the question names two entities other than the owner. The walk leaves out the links of an owner entity the question does not name, and it is skipped when more than 5,000 nodes would be read (the response metadata reports graph_seed_reach: neighborhood_limit); before those two changes it added about 0.6 s at 2,000 turns and 3 s at 10,000 for a question naming the owner, which is now skipped there. A failed read is reported as graph_seed_reach: backend_error and keeps every graph candidate.
    • Upgrading: if you set PRME_SCORING__RRF_GRAPH_WEIGHT for the hop count list of 0.16.0a4, set PRME_SCORING__RRF_GRAPH_SEED_COUNT=false too to keep it; otherwise that weight now drives the seed count list. false restores the scoring of 0.16.0a5, with its scores, ranks and receipts. The default rests on the fast tiers by the owner’s directive of 2026-10-19; the full benchmark audit is pending. BENCHMARKS.md (“Graph seed count”) has the measurements of all three versions.
  • Add explicit evidence capture, retention and admission policies (dwamianm/prism#342, issue #320), opt in. PRMEConfig.privacy_policy (an immutable policy, default none) or a policy on one request of store(), ingest(), ingest_batch(), fast ingest, the HTTP routes and the MCP tools selects them; the Python, HTTP and MCP paths share one validation and the same authority checks at the provider, persistence and disclosure boundaries. An accepted record keeps its effective policy and its original deadlines, a caller can tighten a default, and a retry cannot weaken it. A no-evidence admission keeps the admitted memory without a completed evidence payload, extraction of a protected admission runs in its own finite processing slot, and an unsupported operation or an unavailable capability is refused before any protected effect. Protected profiles turn owner-group links off. Retrieval of a protected owner, owner-group linking and reingest() stay unavailable for it until their dependencies land (issues #321 to #326); the pull request says its component tests do not certify the complete protected product (issue #327). With no policy set, existing defaults and ordinary group matching are unchanged: the same records come out of store(), ingest() and retrieve(). Opening a store now also creates an empty table privacy_admissions (CREATE TABLE IF NOT EXISTS, DuckDB and PostgreSQL), and a few legacy entry points (fast ingest, adapter control events and reingest()) look the owner up in it, so that a legacy call against an owner admitted under a policy is refused.

  • Add bounded local source sanitization to that opt-in path (dwamianm/prism#355, part of issue #322). Source bodies and metadata are checked before they are stored or sent to a provider, and generated extraction and the final memory and index projections are checked again before they are published, with pinned offline recognizers, scoped pseudonyms, strict structured values, cumulative operation budgets and reports that hold no content. The optional extra privacy installs presidio-analyzer and spacy; the model en_core_web_sm 3.8.0 is not part of it, because PyPI does not accept a direct URL dependency. Install it from its release (see docs/SANITIZATION.md); initialization checks its version and file digest and refuses a missing or different model. Sanitization is heuristic and does not certify that text holds no sensitive information, and the issue stays open until it is verified with the real key custody, read, durable-work and authority components. Nothing changes while no policy is set.

  • Add authenticated evidence encryption to that opt-in path (dwamianm/prism#356, part of issue #321). Public-write and private-read are separate capabilities. Each object gets a fresh data key and nonce, and its policy, identity, owner, scope and purpose are authenticated with it. Key versions are exact, grants name finite selected sources, audits hold no content, recovery is deliberate and encrypted, and rewrapping resumes after a stop. prme privacy-keys provisions and diagnoses local keys, and the optional extra local-privacy (keyring) adds native local key custody. Opening a store creates one more empty table, privacy_rotation_jobs. The pull request lists what this does not do: a local same-process capability does not isolate secrets from malicious code in that process or from a local administrator, rotation does not erase older backup or recovery copies, a missing key never falls back to plaintext, native custody was checked on macOS only, and the issue stays open until its integration criteria are verified with the real authority, read, durable-work and migration components (issues #323 to #326). Nothing changes while no policy is set.

Changed

  • The window’s guidance to the model now says to keep every subject, object and quote as the source message words it, a pronoun included, and the resolved text guidance covers “They” for two named people. Before, a model that read an earlier turn wrote the person or trip as the subject and the grounding check dropped the claim. The two texts are sent only while enable_windowed_extraction or enable_fact_text_resolution is on, so nothing changes with them off. A window now holds only turns stored before the turn, so an extraction that runs later no longer shows it turns stored after it.

  • enable_owner_group_links (PRME_ENABLE_OWNER_GROUP_LINKS) is on by default in the alpha channel, and a “We” claim is now linked to the people it speaks for. A claim stated with “We” was attached only to an entity named We that exists for one message, so the graph did not know who made the decision, and “We” claims from different messages never shared a subject. Each such claim of a user turn keeps its literal “We” and its group subject, and gains a group_member link to the owner, because “we” always includes the speaker, and to each person the session names with the owner, when the owner’s turns of the session up to the claim’s turn name exactly one group (“My partner Jordan and I”, “me and Jordan”) and that person is a stored person entity (the link records the naming turn in named_in). Two different groups, a member without a name (“my wife and I”) or a name no single person entity has link the owner only; no member is invented. With enable_claim_merge, the same “We” claim repeated in two user turns of one session merges into one current record, as a named speaker’s repeated “I” claim does. Participant, assistant, tool and system turns keep their own “we”. It adds no model call and no extraction request change. Memories ingested before the upgrade are not linked or merged, and are not rewritten. On the fast tiers the defect probes keep 7 of 7 answers in the top 3, their key links linked go from 4 to 9 of 14 with the five links the probe key gains for the “We” claims, the chat probe and the corrections probe are unchanged, the fast cycle moves only the edge counts, the offline evidence gate shows no change, and on the mid tier no LoCoMo measure moves clear of zero (all evidence packed -0.1 points, 95% interval -0.3 to +0.1). false restores the claims and links of 0.16.0a3. The default rests on the fast tiers by the owner’s directive of 2026-10-19; the full benchmark audit is pending. BENCHMARKS.md (“We claims linked to their members”) has the measurements.

  • The runtime dependency cryptography is now pinned to exactly 50.0.2 for every install (dwamianm/prism#356). It was >=43.0. The pin comes with the evidence encryption above, but it applies whether or not a privacy policy is used: an environment that needs another cryptography release cannot install this release next to it. The optional extra local-privacy pins keyring to 25.7.0.

Fixed

  • A close no longer cancels an extraction batch whose output is saved but not yet published to the graph (dwamianm/prism#332). The concurrent count of unfinished work at close time treated saved output as finished, which could reduce the remaining wait to zero and cancel the batch before its graph publication completed. The close now reserves the existing batch allowance for pending or running work, including saved output awaiting publication; the cap on the wait is unchanged.

Upgrade notes

  • Two defaults changed since 0.16.0a5, both in the alpha channel and both with an environment variable that turns them off: enable_owner_group_links is on (PRME_ENABLE_OWNER_GROUP_LINKS=false) and scoring.rrf_graph_seed_count is on (PRME_SCORING__RRF_GRAPH_SEED_COUNT=false). Neither rewrites a stored memory. With the second on, a PRME_SCORING__RRF_GRAPH_WEIGHT you set for the hop-count list of 0.16.0a4 now drives the seed count list; see its entry.
  • Everything else new is off by default: enable_owner_window_extraction, the windowed extraction options it extends, and the privacy admission policies (privacy_policy), with their sanitization and evidence encryption. scoring.rrf_graph_weight and the other options that were off in 0.16.0a5 stay off. The defaults turned on in earlier alphas are still on (lexical_term_weights for PostgreSQL, enable_ingest_claim_fixes, packing.folded_records_keep_rank, enable_mention_links, packing.graph_seed_words, enable_owner_name_carry and packing.session_context_skip_entities).
  • A store gains two empty tables, privacy_admissions and privacy_rotation_jobs, the first time this release opens it, and reingest() and a few other entry points read it (see the privacy entry). Code from earlier releases does not use the table.
  • Install: cryptography is pinned to exactly 50.0.2 (see Changed). If another package in your environment needs a different release, the resolver will refuse this alpha; a later alpha may relax the pin.
  • Known weaknesses are as in 0.16.0a5. On the fast cycle three answers rank one place lower with scoring.rrf_graph_seed_count on (travel 2 to 3, coffee 4 to 5, job-now 7 to 8), and the reingest() limits of 0.16.0a5 stand.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a5  - 2026-10-19

This is an alpha. It passed CI and the fast tiers, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a5.

Added

  • Add python -m benchmarks.diagnostics.product_packing gate-conversations (prism-monorepo issue 272), which gives LoCoMo’s conversation-level 95% intervals for the five measures gate-compare headlines, by resampling whole conversations with the bootstrap the answer track and the mid tier use. The scheduled full benchmark runs it after each LoCoMo gate-compare, and its report shows the question-level and conversation-level intervals side by side. The verdict reads “loss” when either interval of a measure lies wholly below zero and names the level, and it cannot read “no loss” without both, as the stable gate in CLAUDE.md asks.
  • Add MemoryEngine.reingest() (and MemoryClient.reingest(), POST /v1/reingest, the memory_reingest MCP tool and prme reingest), which re-derives one owner’s stored memories after an engine change improved how they are derived (prism-monorepo #278). Until now the only way to refresh a memory was to store its text again, and a memory that store() saved plainly could not be extracted at all: it has no extraction job for retry_extraction() or process_extractions() to run, and the derivation journal keys one extraction, plan and receipt to each event. reingest() runs the batched extraction ingest() runs over the owner’s sources in event order and publishes what the present engine derives from each, with the old claims of a source superseded by the claims that restate them or archived with metadata.reingest.replaced_by naming their replacements. The source events and raw notes are never changed or deleted, and only the named owner’s sources are touched. It is idempotent and resumable: each source gets a marker naming the engine version, the extraction model and prompt and the options that decide what is derived, so a second run does nothing and a stopped run continues, and the model’s output is saved before anything is published, so it is never asked twice. limit and cursor run it in capped steps, and dry_run=True counts the memories, the model requests and the source tokens without calling a model. It is off by default: set enable_reingest (PRME_ENABLE_REINGEST=true), or the call raises ReingestDisabledError and the route answers 409. The hosted service turns it on. The fast cycle has a reingest stage (opt-in, --stages reingest) that stores the 75-memory fixture plainly, re-derives it and compares the 17 answer ranks before and after, and RFC-0016 describes the records it adds (REINGEST_EXTRACTED and REINGEST_COMMITTED).

Documentation

  • Specify the evidence privacy contract (RFC-0019, “Sanitized memory, protected evidence and erasure”; engine issue 319, part of the epic in issue 318). It is a frozen v1 specification and changes no code and no default. It defines the data classes and what each protection claim does and does not promise, and it ships three machine-readable artifacts: an inventory of every place the engine keeps a copy of memory content, a registry of sanitizer presets, and a manifest of 40 privacy scenarios and 12 more. RFC-0002 and RFC-0014 gain amendments for erasure and for portability of protected stores. No store sanitizes, seals or erases content yet: the implementation is tracked in issues 320 to 327, and a store must not call itself protected until it and its migration gates pass.

Tooling and tests

These do not ship in the wheel and change no behavior.

  • The scheduled full benchmark report shows LoCoMo’s conversation-level intervals beside the question-level ones (see Added), with new tests in tests/test_weekly_benchmark.py and tests/test_product_packing_diagnostic.py.
  • New tests/test_reingest.py, a reingest stage test in tests/test_fast_cycle.py and the public API typing check for the new call.

Upgrade notes

  • No default changed since 0.16.0a4. enable_reingest is new and off, so an engine refuses reingest() until it is set (PRME_ENABLE_REINGEST=true). A hosted service that wants to re-derive its owners’ memories sets it. The call makes one batched extraction request per batch of up to extraction_batch_target_tokens (2,048 by default) tokens of source, so run it with dry_run=True first to see the count.
  • The defaults of 0.16.0a4 are unchanged: lexical_term_weights (PostgreSQL only), enable_ingest_claim_fixes, packing.folded_records_keep_rank, enable_mention_links, packing.graph_seed_words, enable_owner_name_carry and packing.session_context_skip_entities are on, each with the environment variable that turns it off (see the notes of 0.16.0a3 and 0.16.0a4). enable_store_extraction, enable_extraction_corrections, enable_superseded_source_suppression, enable_owner_group_links and scoring.rrf_graph_weight stay off.
  • Known weaknesses are as in 0.16.0a4.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a4  - 2026-10-19

This is an alpha. It passed CI, the fast gate, the fast cycle and the mid tier, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a4.

Added

  • Add three options, all off by default, that make the graph count in ranking (prism-monorepo issues 259 and 260). On the hosted dev service (prme 0.16.0a3) the note and the claim that answer “which cities will Alex and Jordan visit in Japan?” ranked 11th and 12th of 21 results, though the graph search found 13 candidates. Rank fusion ignored the graph, the three notes of the chat tied at 0.6 times the introduction note’s score through session context, entities took the places that inheritance gave them, and the claim “We chose Tokyo, Kyoto, Kanazawa, and Osaka.” was attached to the group, not to the owner, so a walk from the owner never reached it.

    • enable_owner_group_links (PRME_ENABLE_OWNER_GROUP_LINKS): ingest() links a claim a user turn states about we, us or our to the owner’s entity (a MENTIONS edge with metadata.basis group_member), after the derivation commits and outside the plan. The claim keeps its group subject. It needs enable_owner_name_carry, and never invents a name.
    • packing.session_context_skip_entities (PRME_PACKING__SESSION_CONTEXT_SKIP_ENTITIES): an entity neither starts a session context expansion nor receives an inherited score.
    • scoring.rrf_graph_weight (PRME_SCORING__RRF_GRAPH_WEIGHT): the graph path becomes a third rank fusion list, ranked by hop count, with a replayable graph_rank in the saved score inputs. Receipts that set it are version 19.

    With all three on, the cities claim is 3rd for both spellings of the question on PostgreSQL (12th without them) and no smaller set of them gets it into the top 3. The graph weight is not ready to turn on: it fails the fast cycle (it moves two chat-v1 answers out of the top 3 at 0.25, one at 0.15 and 0.1) and lowers LoCoMo evidence in the top 25 by 5.4 points on the mid tier. The group links change no gated measure. Skipping entities passes the fast tiers and raises LoCoMo evidence in the top 25 by 2.1 points (48 wins, no loss), so it is turned on below. With the options off, results, contexts, receipts and configuration dumps keep their bytes. BENCHMARKS.md (“Graph fusion”) has the measurements.

  • Add a scheduled full benchmark on the Mac mini (prism-monorepo issue 272). A LaunchAgent runs scripts/full-benchmark/weekly-run.sh on Monday and Thursday at 00:30 local time: it builds LoCoMo and LongMemEval-S with the defaults, runs the offline evidence gate, compares the result with v0.15.0 and with the previous run, and writes a report whose first line reads “no loss” or names the measures that lost. It skips when origin/main has not changed or a run is going, refuses to start with less than 120 GB free, and keeps the packs of the newest 3 runs. The scripts: install-schedule.sh, uninstall-schedule.sh, status-weekly.sh, deploy-schedule.sh and publish-results.sh (opens the results pull request from the MacBook). The release rule: an alpha or beta needs the fast tiers, a stable release needs the latest scheduled run to show no loss. run-builds.sh gains BUILDS=defaults and EXTRACTION_CACHE. See BENCHMARKS.md and memory_bank/HANDOFF-2026-10-19-WEEKLY-BENCHMARK.md.

Changed

  • Turn on packing.session_context_skip_entities by default, in the alpha channel (prism-monorepo issues 259 and 260), by the owner’s directive of 2026-10-19: ship what passes the fast tiers and audit on the full benchmark at the stable release. An entity record now neither starts a session context expansion nor receives an inherited score, so the entities that ingest() creates beside a claim stop taking places above the notes and claims that answer. It acts on retrieve() over memories that have entities, so it changes nothing for a store made with store() alone, and it rewrites no memory. PRME_PACKING__SESSION_CONTEXT_SKIP_ENTITIES=false restores the expansion of 0.16.0a3. A PackingConfig built in code keeps it off, and an off setting stays out of receipts, so a receipt of a retrieval that does not use it keeps its bytes; a receipt written with the default names the field, which releases before 0.16.0a4 ignore when they read it. On the fast tiers (deepseek-v4.1-flash:cloud, extraction replayed from the committed cache), with it on against the defaults: the defect probes rank 7 of 7 answers in the top 3 and the chat probe 14 of 18 (top 5: 15 of 18), with every probe rank equal; the fast cycle passes with 168 measures unchanged, on DuckDB and on the PostgreSQL store stage; the offline evidence gate shows no change; and on the mid tier LoCoMo evidence in the top 25 rises by 2.1 points (95% interval +1.5 to +2.7, 48 wins, 0 losses). All evidence packed does not move clear of zero (+0.1, 7 wins and 6 losses; single-hop -0.4, interval -0.8 to +0.0). The full benchmark audit and the default-change rule’s answer runs are pending. BENCHMARKS.md (“Graph fusion”) has the tables.

Tooling and tests

These do not ship in the wheel and change no behavior.

  • The scheduled full benchmark on the Mac mini (prism-monorepo issue 272), under scripts/full-benchmark/, runs the full build twice a week (see Added).
  • The release rule in CLAUDE.md, AGENTS.md and CONTRIBUTING.md now names the alpha, beta and stable channels: what each needs, the fast tiers with their commands, and how to cut a prerelease (#312).
  • A test pins the owner name carry on the batched extraction path with the recorded dev run (#314). The carry already worked there, so no source changed.
  • tests/test_summary_publication.py::test_operator_scan_is_explicit_and_can_cross_tenants now reads the operator scan page by page. It read one page of 10,000 nodes in UUID order, and the database of a whole suite run holds more than that, so it failed on engine CI for the release branch of this alpha (5 of 16 runs on a database of 13,000 nodes, none after the change). It tests the scan, not a size, and no source changed.

Upgrade notes

  • One default changed since 0.16.0a3: packing.session_context_skip_entities is on (see Changed). It affects retrieve() over memories that ingest() extracted, not stores made with store() alone, and it rewrites nothing. Set PRME_PACKING__SESSION_CONTEXT_SKIP_ENTITIES=false to get the expansion of 0.16.0a3 back. A receipt written with the default names the field, which releases before 0.16.0a4 ignore when they read it. A PackingConfig built in code keeps it off.
  • The three options of the graph fusion work are new, and two of them stay off: enable_owner_group_links (neutral on every tier) and scoring.rrf_graph_weight (it moves two chat-v1 answers out of the top 3 and lowers LoCoMo evidence in the top 25 by 5.4 points on the mid tier). Leave them off.
  • The five options turned on in 0.16.0a3 and lexical_term_weights (PostgreSQL only, 0.16.0a2) are still on, each with the environment variable that turns it off (see the 0.16.0a3 notes).
  • Known weaknesses are as in 0.16.0a3: the Austin-to-Denver case of the corrections probe on one live extraction sample, and single answer ranks one or two places lower on the fast cycle. On the mid tier the entity skip does not raise all evidence packed (+0.1, not clear of zero) and single-hop all evidence packed is 0.4 points lower (-0.8 to +0.0).
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a3  - 2026-10-19

This is an alpha. It passed CI, the fast gate and the fast cycle, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a3.

Added

  • Add packing.folded_records_keep_rank (PRME_PACKING__FOLDED_RECORDS_KEEP_RANK), added off and turned on below (prism-monorepo issues 258 and 259). With enable_ingest_claim_fixes on, the answer to a question ranked worse than with it off (the chat probe’s answer in the top 3 fell from 14 of 18 to 8 of 18, the defect probes’ from 5 of 7 to 4 of 7). The cause is the statement that option builds for a claim whose sentences another claim already holds (“Dana has pet Biscuit.”): it is short and repeats the names and verbs of a question, so it outranks the claim that holds the sentence, and 39 of the 56 records above the chat probe’s answers were such statements. With the new option on, retrieve() folds each statement into the claim of the same evidence that holds its object: the claim takes the better of the two scores through a replayable claim_statement_fold score adjustment that names the statement, and the statement leaves the results with the exclusion reason claim_statement_folded. In the reader context, the record that folds earlier packed records (fold_repeated_text) also takes the place of the earliest one, so folding no longer moves a claim’s text down to its source note’s place. With all four options on, the defect probes rank the answer in the top 3 for 7 of 7 questions and the chat probe for 14 of 18, as with them all off, and the fast cycle passes its rules. With the option off, or for memories without statements, nothing changes: results, contexts, receipts and configuration dumps keep their bytes. It has not been through the default-change rule on the ingest() answer track. BENCHMARKS.md has the evidence (“Why the claim fixes lost rank”).
  • Add enable_owner_name_carry (PRME_ENABLE_OWNER_NAME_CARRY), added off and turned on below (part of prism-monorepo issue 258). A user turn is the memory’s owner by definition. enable_ingest_claim_fixes binds the I of the turn that introduces them (“Hi, I am Alex Rivera”) and enable_speaker_references binds the I of a turn with a caller’s speaker, for that turn only, so a later turn that names nobody (“I prefer trains to flights”) kept an I entity of its own, one per message. With the option on, the person entity a user turn creates under the owner’s name carries owner_name metadata, written once with the node (no node is changed, so no plan that cites it goes stale), and a later user turn of the same owner and scope that names nobody binds its I to the newest such entity, through the same binding; its claims record "basis": "owner_name". It never applies to participant, assistant, tool or system turns, a caller’s speaker or first_person_owner on the turn wins, a later name replaces an earlier one for the turns after it without rewriting earlier claims, and a name no user turn gave is never invented. It needs enable_speaker_references, sends the model the same request, makes no extra call, and a failed lookup leaves the old behavior. It marks only an entity the turn creates, so a person an earlier message already named is not marked. A statement written for a duplicate sentence keeps “the speaker” instead of the carried name: with the name in every statement, packing.folded_records_keep_rank lifts all of them for any question that names the owner, and the chat probe’s top-3 answers fell from 14 to 10 of 18. Chat probe, replaying the cached extraction of deepseek-v4.1-flash:cloud with enable_ingest_claim_fixes, enable_mention_links, packing.graph_seed_words and packing.folded_records_keep_rank on, and the option off then on: defects-v1 user claims on the named owner against I or We go from 2 against 15 to 12 against 5, chat-v1 from 4 against 35 to 35 against 3, chat-v1 key links linked from 7 to 14 of 14 and entities with no link from 1 to 0; answers in the top 3 and top 5 do not change (7 and 7 of 7, 14 and 15 of 18). BENCHMARKS.md, “Owner name carry”, has the table and the fast cycle result.
  • Add the store-contextual stage to the fast cycle (BENCHMARKS.md, “Fast cycle: situating sentences at store time”; part of prism-monorepo issue 260). It stores the 75 memories with a one-sentence situating context written by a local model from the memory’s session and passed as retrieval_content, beside the plain store stage, on that fixture and on a second fixture of 30 memories and 10 questions. The sentences are cached, so a rerun calls no model. It is opt in (--stages store,store-contextual) and changes nothing in the engine or the default run. It found no lift that meets the conditions set for building the option, so the engine does not have one.

Changed

  • Turn on five options by default, in the alpha channel (prism-monorepo issues 258 and 259), by the owner’s directive of 2026-10-19: ship what passes the fast gate and leave the full benchmark audit for later. They apply to ingest() and to retrieve() over what it stores, not to store(), and memories ingested before the upgrade are not rewritten. Each has an environment variable that turns it off and restores the behavior of 0.16.0a2:
    • enable_ingest_claim_fixes (PRME_ENABLE_INGEST_CLAIM_FIXES=false): a plural first-person claim is kept, a user turn that says who it is binds its I, and a claim whose sentences another claim already holds gets a statement of its own. A custom extraction provider whose extract does not take plural_first_person is not given it, so the default breaks none; its I for a we is discarded as before.
    • packing.folded_records_keep_rank (PRME_PACKING__FOLDED_RECORDS_KEEP_RANK=false): retrieve() folds each such statement into the claim it restates, which takes the better score, and the packer keeps a folded record’s place. It is what stops the statements from lowering the rank of an answer.
    • enable_mention_links (PRME_ENABLE_MENTION_LINKS=false): a claim and the raw source note are linked to every entity whose name they hold.
    • packing.graph_seed_words (PRME_PACKING__GRAPH_SEED_WORDS=false): graph search seeds from any word of the question that is a whole word of an entity’s name, whatever its case.
    • enable_owner_name_carry (PRME_ENABLE_OWNER_NAME_CARRY=false): the owner’s name, once a user turn gave it, binds the I of their later user turns. On the fast gate (deepseek-v4.1-flash:cloud, extraction replayed from the committed cache), with the five on against off: the defect probes rank the answer in the top 3 for 7 of 7 questions (5 of 7 off) and the chat probe for 14 of 18 and in the top 5 for 15 of 18, as with them off; user claims on the named owner against “I” or “We” go from 0 against 17 to 12 against 5 on the defect probes and from 0 against 39 to 35 against 3 on the chat probe; claims whose text repeats another’s go from 12 and 39 to 0; entities with no link from 7 and 12 to 0; key links linked from 1 and 4 to 4 and 14. The fast cycle passes with no gated measure worse (49 measures moved, 9 single answer ranks one to two places lower, none gated), and the offline evidence gate is unchanged. A PackingConfig built in code keeps its fields off, and an off packing setting is left out of receipts, so receipts of retrievals that do not use them keep their bytes; a receipt written with the defaults names the two packing fields, which releases before 0.16.0a3 ignore when they read it. The full benchmark audit and the default-change rule’s DeepSeek answer runs are pending. BENCHMARKS.md has the tables.

Fixed

  • Aggregation coverage no longer reports candidate_limited when the individual keyword searches recovered every candidate and only a keyword pair’s any-term search reached its limit of 50 (#295, from an outside contributor; part of issue #293). Only an individual keyword search that reaches 50 sets the LEXICAL_AGG limit marker now. Results, their order and their scores do not change, only the coverage report and its limitations.

Upgrade notes

  • Five defaults changed since 0.16.0a2: enable_ingest_claim_fixes, packing.folded_records_keep_rank, enable_mention_links, packing.graph_seed_words and enable_owner_name_carry are on (see Changed). They shape what ingest() stores and what retrieve() returns over it; they do not touch store(), and memories already stored are not rewritten. Set PRME_ENABLE_INGEST_CLAIM_FIXES=false, PRME_PACKING__FOLDED_RECORDS_KEEP_RANK=false, PRME_ENABLE_MENTION_LINKS=false, PRME_PACKING__GRAPH_SEED_WORDS=false and PRME_ENABLE_OWNER_NAME_CARRY=false to get the behavior of 0.16.0a2 back. The fast gate passed with the five on together; the full benchmark audit has not been run.
  • A retrieval receipt written with the defaults names graph_seed_words and folded_records_keep_rank. Releases before 0.16.0a3 ignore the two fields when they read it. A PackingConfig built in code keeps both off.
  • A dump of the configuration now always names enable_ingest_claim_fixes and enable_owner_name_carry.
  • A custom extraction provider whose extract does not take plural_first_person is not given it, so the new default breaks none.
  • Known weakness: on one live extraction sample of the corrections probe, the Austin-to-Denver case shows the old value before the new one with the five on (the committed sample of the fast cycle does not move). The fast cycle also shows nine single answer ranks one or two places lower. Neither is gated.
  • enable_owner_name_carry marks only an entity that a turn creates, so a memory built before it was on has no marked entity, and an owner who returns to an earlier name keeps the later one.
  • Still off: enable_store_extraction, enable_extraction_corrections and enable_superseded_source_suppression. lexical_term_weights (PostgreSQL only) stays on from 0.16.0a2.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a2  - 2026-10-19

This is an alpha. It passed CI and the fast gate and fast cycle, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a2.

Added

  • Add the store-extract stages to the fast cycle (part of prism-monorepo issue 260; BENCHMARKS.md, “Store with extraction”), opt-in with --stages. store-extract stores the store stage’s 75 memories with extract=True (extraction by deepseek-v4.1-flash:cloud, drained by hand so the requests do not depend on the 30 second timer) and asks the same 17 questions; store-extract-session does so with each memory’s session as session_id; store-session is the control with no extraction. They report the extraction requests and tokens, the entities made, the graph candidates per question and the repeated records in the contexts next to the store measures. The extraction cache leaves a turn’s wall-clock time out of its key so a stored memory replays, rules gain per-stage live tolerances (live_by_stage), and the committed baseline gains the store-extract stage (the committed cache now holds 166 responses). Three live runs on main: extraction ranks the answer first for 12 questions instead of 6 and lifts the reciprocal rank from 0.56 to 0.73, but ranks 12 answers in the top 3 instead of 14, pushes five questions down, doubles the context tokens and costs 75 requests and 462,000 tokens (7 requests and 71,000 tokens with sessions). Nothing in the engine changes.
  • Add enable_superseded_source_suppression (part of prism-monorepo issue 270), off by default. ingest() keeps a message as a note beside the claims extracted from it, so retiring a claim (a correction, supersede()) left the note that stated the old value, and any claim written from the same sentence, active and able to reach the context ahead of the correction. With the option on, default retrieval leaves out a note or claim when a superseded claim that cites one of its source messages was replaced by a record from another message with a different statement and the record’s text holds that claim’s text; a bare entity name goes when every message that named it does. Nothing is written or deleted, the records stay readable by id, and RetrievalMode.EXPLICIT keeps them. The receipt records the option only while it is on. The corrections probe gains two measures, “old-only lines in the context” and “old value shown before the new value”.
  • Add a mid-tier benchmark, python -m benchmarks.mid_tier (run, compare, select, check). It builds packs for all of LoCoMo and a fixed, stratified sample of 12 LongMemEval-S questions through ingest() with deepseek-v4.1-flash:cloud on the local Ollama server, replays them through the offline evidence gate and writes one report: all evidence packed, evidence recall, evidence in the first 25 candidates and projected accuracy, overall and by category, with 95% intervals (LoCoMo also by conversation), and the calls, tokens, wall time and load average of each stage. A cold run took 25 and 34 minutes, a warm one 11, and --reuse-build gates an earlier run’s packs for retrieval and packing changes in about 3. compare pairs two reports question by question. It makes no reader or judge call, so it measures evidence coverage, not answers (BENCHMARKS.md, “Mid tier”). extracted_packs build gains --cache-dir and --in-flight so builds under different labels share one extraction cache and several build processes cap their live model calls.

Changed

  • Turn on lexical_term_weights by default (part of prism-monorepo issue 254), on PostgreSQL only; DuckDB ignores it. It is on in the alpha channel on the fast cycle and the offline evidence gate, by the owner’s directive of 2026-10-19 (defaults flip on the fast gate and the full benchmark audits later). The full benchmark audit and the default-change rule’s DeepSeek answer runs are pending. PRME_LEXICAL_TERM_WEIGHTS=false restores the any-term word search of 0.16.0a1. Since that search (see Fixed under 0.16.0a1), a question that holds the owner’s name and a few common words lifts unrelated memories as much as the memory with the rare word: on the fast cycle’s 75 memory fixture the Rust question ranked 4th and the dog question fell from 1st to 5th. A query word now weighs ln(N / df), where N is the number of the owner’s searched memories and df the number that hold the word, and a memory is a candidate (with more than one query word) only when the words it holds would be held together by chance by at most 0.2% of the memories, and at least 2 memories. The owner’s name or a common verb cannot select candidates, and candidates rank by the weight of the words they hold. A memory that holds every query word is still returned alone, and a one-word query is unchanged. On PostgreSQL, the 75 memory fixture goes from 8 answers ranked first and 13 in the top 3 to 11 and 15, the Rust question from 4th to 1st and the dog question from 5th to 1st, with no question ranked lower. The 28 questions of a second set, written before the first run, go from 22 and 25 to 24 and 25. With 2,000 or more memories the effect is within noise. The search takes the same time within 0.2 ms at 5,000 memories, and the offline evidence gate shows no change. The fast cycle also takes --store-fixture and ships the second question set as benchmarks/fixtures/fast-cycle/store-heldout-v1.json. BENCHMARKS.md, “Lexical term weights”, has the table.

Upgrade notes

  • One default changed since 0.16.0a1: lexical_term_weights is on, and it affects PostgreSQL only (DuckDB ignores it). Word search weighs each query word by how rare it is and keeps weak matches out of the candidates. On the fast cycle’s 75 memory fixture it moved the Rust question from 4th to 1st and the dog question from 5th to 1st, with no question ranked lower, and the offline evidence gate did not change. The effect is within noise from about 2,000 memories. The default-change rule’s DeepSeek answer runs have not been done. Set PRME_LEXICAL_TERM_WEIGHTS=false to get the word search of 0.16.0a1 back.
  • enable_superseded_source_suppression is new and off. enable_ingest_claim_fixes, enable_mention_links, packing.graph_seed_words, enable_store_extraction and enable_extraction_corrections stay off.
  • The new store-extract stages measured store(extract=True) on the fast cycle’s 75 memories: answers ranked first went from 6 to 12 and stale values above the answer from 4 to 2, but answers in the top 3 went from 14 to 12, five questions ranked lower, and the context used about twice the tokens. The mid-tier benchmark (all of LoCoMo and 12 LongMemEval-S questions through ingest(), evidence coverage only) runs in 25 to 35 minutes cold.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.16.0a1  - 2026-10-19

This is an alpha. It passed CI and the fast gate for ingest changes, and it has not been through the full benchmark. The stable release stays at 0.15.0 until the full benchmark audit, so pip install prme still installs 0.15.0. Install the alpha with pip install --pre prme or pip install prme==0.16.0a1.

Added

  • Add the fast cycle (part of prism-monorepo issues 258 and 259; BENCHMARKS.md, “Fast cycle”): uv run python -m benchmarks.fast_cycle run --label <name> --out <dir> stores 75 memories with store() and asks 17 questions (rank of each answer, stale values, nodes per call, context tokens), then runs the three scripted chats through ingest() with extraction by deepseek-v4.1-flash:cloud, replaying a committed extraction cache (84 responses, 113 KB) while the prompt is unchanged. It writes a JSON and a Markdown report with the measures in three groups (storage, meaning, results) and the time of each stage, and judges them against the committed baseline by benchmarks/fixtures/fast-cycle/rules.json: exact on a replay, with noise tolerances measured from three live runs when the model is asked. compare <baseline> <report> exits 1 on a failed rule. A cold run takes about 3 minutes and a warm one under 30 seconds. The store stage can run on PostgreSQL (--database-url). It is a regression screen, not an accuracy estimate. Nothing in the engine changes.
  • Add enable_ingest_claim_fixes (PRME_ENABLE_INGEST_CLAIM_FIXES), off by default (prism-monorepo issue 258). With it on, ingest() makes three changes to what a turn’s claims say, and with it off nothing changes: records, plans, receipts and configuration dumps keep their bytes. It has not been through the default-change rule, so turning it on by default needs that rule on the ingest() answer track.
    1. A claim of a plural first-person statement is kept. Given “We chose Tokyo, Kyoto, Kanazawa, and Osaka. We dropped Hiroshima to save time.”, Claude Haiku 4.5 returned two facts whose subject was I, a word the sentences do not contain, so grounding discarded both and the turn’s extraction had empty facts. When a built-in provider writes I, me, my, mine or myself for a claim’s subject, object or relationship endpoint and the cited sentences use no singular first-person form but do use the plural counterpart (we, us, our, ours, ourselves), the claim keeps the turn’s own word. Singular and plural forms still do not match each other in grounding, and a plural reference still stays local to its message. A custom provider is passed plural_first_person=True only while the option is on.
    2. A speaker who introduces themselves is linked to their own entity. A user turn with no speaker that says who it is (“Hi, I am Alex Rivera”, “I’m Alex Rivera”, “My name is Alex Rivera”, “Call me Alex”) binds its I, me and my to the person entity of that name, the way a caller-named speaker does under enable_speaker_references, when the extraction lists the name as a person. The claim’s speaker_reference metadata has "basis": "self_introduction", and the turn no longer creates a message-local I entity. A possessive (“I am Jordan’s partner”), a name the extraction did not list as a person, a turn that introduces two different people, an assistant turn, a speaker the caller gave, and first_person_owner=True bind nothing new. The binding covers that turn only: a later turn that names nobody keeps its own I.
    3. With enable_claim_sentence_text, no two claims of one message share a text. A claim whose sentences are already the text of an earlier claim of the same message, such as the partner claim and the trip claim of “My partner Jordan and I are planning a trip to Japan.”, has a one-sentence statement of its own, “Alex Rivera partner Jordan.”, built from its subject, predicate and object (the speaker’s name, “the speaker” or “the speaker’s group” for a first-person reference, “It is not true that …” for a denial, the condition in parentheses). It records claim_statement_v1 as its grounding method. The first claim keeps the source sentences and the paragraph stays every claim’s evidence_quote. The statement is not source text, so the packed context shows it as its own line instead of folding it into the message.
  • Add extract to store(), store_with_receipt(), POST /v1/store, the memory_store MCP tool and the sync client (#260), and enable_store_extraction to set its default (off). extract=True saves an extraction job with the stored event and starts the extraction ingest() runs in the background, in the same batches, so a stored memory gets entities and claims too. The call still returns at once with the stored node, which keeps its id and content and stays the only note for the text. Extraction over a stored event no longer stops at “Source has legacy derived nodes”, which it did for a direct node whose id is not the event’s. Without the argument and the setting, store() makes no model call, as before.
  • Add a fast gate for ingest changes (part of prism-monorepo issues 258 and 259; BENCHMARKS.md, “Fast gate for ingest changes”). The chat probe takes a second conversation, benchmarks/conversations/defects-v1.json: 14 turns over three sessions that reproduce both issues in 22 seconds and 14 model calls, with 7 probe questions. Every probe now also reports the rank of its answer, the 1-based place of the first retrieve() result whose text holds it, because a short chat fits the context whole and passes a context-only check. The summary and compare count the probes ranked in the top 3 and in the top 5. The existing measures are unchanged, and a report from before the rank reads “not measured”. Nothing in the engine changes.
  • Add enable_extraction_corrections (#277), off by default. With batched extraction, each request also shows the model a few stored claims of the same owner and scope that resemble its turns, labeled m1, m2 and on, and an extracted fact may name the one it corrects (replaces_memory). Ingestion keeps a label only when the request showed it, saves the matched node ID with the extraction record, and supersedes that claim only when the new fact is an observed or asserted, unconditional update and the earlier claim is an active, observed or asserted, unconditional claim of the same owner, scope and source type that is not effective after it. This lets a correction that does not repeat the old value retire it. With the option off, requests, schemas and saved records keep their bytes. The corrections probe takes --set and --label to build such a variant.
  • Add embedding.threads (PRME_EMBEDDING__THREADS) and FastEmbedProvider(threads=...), the ONNX Runtime thread count of a fastembed model. The default, None, keeps ONNX Runtime’s own choice of one thread per core the machine shows, which ignores a container’s CPU limit; nothing changes unless it is set. On an 18-core machine one thread embedded a short text with 4.9 ms of CPU against 42.5 ms by default, in about the same wall time, and gave bit-identical vectors for 300 texts.
  • Add enable_mention_links and packing.graph_seed_words (prism-monorepo issue 259), both off by default, so graph search can find the claims and notes that name an entity. After ingest(), the neighborhood of an entity such as Japan or Tokyo was empty, because extraction linked an entity only to the claims it is the subject of (HAS_FACT) and to a claim’s resolved object (MENTIONS), and graph search seeded only from the capitalized words of a question. With both off, ingestion, graph candidates and receipts keep the bytes they had.
    • enable_mention_links (PRME_ENABLE_MENTION_LINKS) makes ingest() add a MENTIONS link from each claim and from the raw source note to every entity its extraction lists whose name occurs in its text as a whole word, ignoring case. That covers a claim whose object text holds a name (“Japan in late March next year”) and a note that no claim covers (“We chose Tokyo, Kyoto, Kanazawa, and Osaka.”). The edge metadata marks these links basis: text_mention, and claim merging ignores them. A name that two listed entities share and a literal I or we are not linked. The links are made after the derivation commits and after the note exists, in either order, and are not part of the derivation plan, so a failure to link never fails an extraction and saved plans keep their bytes. Memories ingested before the option was on keep the links they have.
    • packing.graph_seed_words (PRME_PACKING__GRAPH_SEED_WORDS) makes graph candidate generation independent of capital letters. The capitalized names and quoted strings it matched before still match as substrings. Every other word of the question seeds the graph when it has three or more letters, is not a common function word and is a whole word of an entity’s name, so which cities will Alex and jordan visit in Japan? and the same question with Jordan find the same seeds. Receipts record it when it is on.
    • On the conversation of that issue (stub extraction), with both on, the neighbors within two hops of Tokyo, Japan and Alex Rivera went from 0, 0 and 0 to 5, 6 and 3, and candidates_generated.GRAPH for the two spellings of the question from 0 and 4 to 11 and 11, on DuckDB and PostgreSQL. Under the default rank fusion a candidate found only through the graph still scores 0 (RFC-0005), so this adds candidates and weighted-score evidence, not a graph channel. Neither option has been through the default-change rule.

Changed

  • The neighborhood walk (get_neighborhood, get_neighborhood_with_depth and graph candidates) on DuckDB and PostgreSQL returns the same nodes at the same depths but expands each node once per depth instead of once per path. It followed every simple path, so its work multiplied with each hub entity on the way, which a more densely linked graph makes expensive. On 3,000 events over 40 entities in PostgreSQL with links from every claim and note to the entities its text names, graph candidates took a median 1,346 ms with the old walk and 156 ms with this walk, against 148 ms for the old walk without those links. A test checks the walk against a reference breadth-first search on random graphs with cycles, parallel edges and hubs.
  • Retrieval does less work and returns the same results, contexts and receipts. FastEmbedProvider.model_version reads fastembed’s package metadata once per process instead of on every access, and the PostgreSQL vector index reads the provider’s identity once per search instead of once per returned row. pack_context no longer renders and counts the whole context for every candidate it tries: it counts each record line once and adds the lines up, which gives the same count under every encoding that tiktoken 0.12 provides. Any other encoding, or a tiktoken build that splits a few probe lines differently, is still counted whole, with a warning logged once. On a store of 3,435 LoCoMo turns, the median retrieval went from 202 ms to 88 ms on PostgreSQL and from 267 ms to 214 ms on DuckDB, and packing from 57 ms to 14 ms and from 64 ms to 23 ms. A test packs 260 cases, including scenarios from the packing tests, against digests that the earlier implementation wrote.

Fixed

  • PostgreSQL lexical search no longer requires every term of the query (prism-monorepo issue 254). A question written as a sentence, such as “what coding language is alex learning?”, shares only some words with the memory that answers it, so it found no lexical candidates and the vector ranking alone decided the order. When no document contains every term, a document that contains any of them now matches, and hits rank by the number of distinct terms they match and then by ts_rank_cd. When some document contains every term, only those documents match, as before. In a query with more than one term, a term that matches at least half of the searched documents, such as the owner’s name, does not select candidates while another term matches (it still counts when ranking them), so it does not make every memory a lexical candidate. Only the first 32 distinct terms are used. Queries with a single term (one lexeme) return the same hits and scores as before. In the three memory example from the issue, the memory that answers the question went from a lead of 0.008 to a score of 1.0 against 0.492.

Upgrade notes

  • No default was turned on in this alpha. enable_ingest_claim_fixes, enable_mention_links and packing.graph_seed_words are off. On the fast gate (defect probes and the chat probe, with deepseek-v4.1-flash:cloud), the three together did not pass: the answer ranked in the top 3 for 4 of 7 defect probes against 5 of 7 with them off, and for 8 of 18 chat probes against 14 of 18. The claim fixes cause the lower ranks, because claims that used to share one text now each show their own. Linking and seed words alone changed no rank. They are still useful to try on a store of your own, and a hosted service can set them through its configuration.
  • store(extract=True) and enable_store_extraction start a model call in the background for each stored memory. Both are off by default, so store() makes no model call unless you ask for one.
  • On PostgreSQL, a multi-word query now also finds memories that share only some of its words (see Fixed). Single-word queries and DuckDB are unchanged.
  • Known flaky tests: tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persisted can stop the test process with a DuckDB segmentation fault while the engine closes, and tests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itself fails inside a git worktree. Neither affects the installed package.

0.15.0  - 2026-10-16

Added

  • Add batched extraction (extraction_batch_target_tokens; #254), on by default (see Changed). With a target above zero, ingest() extracts several turns of a session in one model call: the worker takes the session’s pending extraction jobs in the order they were ingested and adds turns while their text stays within the target, counted in tokens. A turn larger than the target, or without a session, goes alone, and a batch never crosses a session. Each turn is sent with its index, role, time and speaker, every claim cites its turn, and each turn’s claims are checked against that turn alone under its own role, so a quote from another turn is rejected as before and claim merge, supersedence, derivation plans and receipts see one extraction record per turn. The request and its parsed response are saved once (an EXTRACTION_BATCH_RECORDED operation) and each turn’s record names it (ExtractionRecord.batch_id), so a restart, a replay and prme rebuild never call the model; a release from before this option cannot read a record that names a batch. A response that fails validation, and a request that times out or is refused as too large, are asked again as two halves, recursively, before a turn is marked failed under the usual retry policy. ingest(wait_for_extraction=False) queues the turn: a waiting ingest(), ingest_batch(), process_extractions() or the engine’s close extracts the queue, and otherwise it is extracted 30 seconds after the first queued turn. The option cannot be combined with enable_windowed_extraction or enable_fact_text_resolution. At 0 nothing changes: one call per turn, with the same records, plans and receipts as before. benchmarks.diagnostics.extracted_packs builds packs at any target with --set extraction_batch_target_tokens=N, and its new summary command totals a build.
  • Add visual memory, phase 1 of RFC-0018: MemoryEngine.observe(), MemoryClient.observe() and prme observe <db_path> <file>... turn a photo or a saved video into observation events. Video is sampled with ffmpeg, a keyframe is kept on pixel change or every max_gap_seconds, a local vision model (Ollama, qwen3.5:9b by default) writes a facts-first description of each keyframe that quotes visible text verbatim, a description that repeats the previous kept frame’s (same quoted text, embedding cosine at or above repeat_similarity) is dropped, and each remaining description is admitted as an event of role tool with the capture clock as event_time and metadata naming the source, the frame time, the frame’s SHA-256, the thumbnail, the vision model and the prompt version. With vision.extract (on) the description goes through ingest() and a failed extraction is retried before the next frame; otherwise through store(). A frame already observed for the owner by the same model and prompt is skipped. Everything is behind the new VisionConfig (vision, PRME_VISION_*), off by default, so a pack that never observes is unchanged; observe() raises VisionDisabledError until vision.enabled is set, and prme observe sets it for its own run; it raises ObservationError, with the per-frame report attached, when a keyframe was not admitted or the model or the pack failed part way. observe() needs a local DuckDB pack; the PostgreSQL backend is not supported in this release. The spike pipeline moved from benchmarks/vision/spike.py into src/prme/vision/; the harness now drives the engine. numpy and pillow, already installed through fastembed, are declared dependencies.
  • Keep a JPEG thumbnail of each observed keyframe in a blobs table of the pack’s DuckDB database, in both layouts, keyed frames/<frame sha256>.jpg and read back with get_blob(). The table inherits the pack’s encryption, is copied by prme migrate and prme compact, is left alone by prme rebuild, and is created on first use.
  • Add opt-in single-file memory packs (#247). Set pack_path (PRME_PACK_PATH), call prme.client.config_from_pack(), or give MemoryClient a path that ends in .prme and is not an existing directory; config.single_file_pack reports the mode, and the now public prme.client.is_pack_file(path) tells which layout a path selects. The pack is one DuckDB file that holds the event log, the graph, the vector payloads and snapshot, and the pack manifest. Directory packs remain the default, and their layout and behavior are unchanged. See single-file packs and the RFC-0014 single-file pack amendment.
  • Describe each single-file pack in a prme_manifest row: the pack format version (PACK_FORMAT_VERSION = 1), the PRME version that last opened it, the embedding model name, version and dimension, the scoring version, and created and updated times. Opening warns when the configured embedding model differs from the recorded one, and prme rebuild records the model that rebuilt the vectors.
  • Keep a single-file pack’s USearch snapshot in a vector_snapshot row, reconciled on open against vector_payloads exactly as the snapshot file is, and hold its Tantivy index in memory, rebuilt from active graph nodes in id order before the engine serves any read. After every open the lexical index holds the documents the write path gave the pack, which prme rebuild also produces (see Changed), so a pack built with ingest(), or migrated from such a directory pack, keeps its lexical documents across a reopen. Tied BM25 scores can come back in a different order than from a directory index. Engines in one process that open the same pack, MemoryClient objects on their own event loops included, share one in-memory lexical index, so a node stored through one is found by BM25 through the others without a reopen; the last engine to close releases it. Vector removals count toward vector_save_interval as additions do, instead of rewriting the whole snapshot row after each removal; a removal not yet saved survives a crash because the next open drops snapshot keys whose metadata is gone. A directory pack still saves vectors.usearch after every removal. Vector hits are identical, and directory-pack retrieval is unchanged apart from prme rebuild.
  • Encrypt single-file packs with DuckDB’s native encryption. DuckDB encrypts the file and its write-ahead log with AES-256-GCM; it leaves temporary files in clear text by default even for an encrypted database, so PRME turns on DuckDB’s temp_file_encryption and keeps those files in <pack>.tmp beside the pack. Stored text therefore does not appear in clear on disk while the engine runs. A raw key (the raw_key: prefix, or an unprefixed 44-character url-safe base64 Fernet key) is used without derivation; any other value, or one with the passphrase: prefix, is derived with PBKDF2-HMAC-SHA256 (600,000 iterations) and the fixed, versioned salt prme-single-file-pack-v1, because a per-pack salt cannot live inside the encrypted file. Identical passphrases therefore give identical keys across packs. A missing or wrong key, or encryption configured for a plain pack, raises EncryptionError and leaves the file unchanged. An encrypted pack can be open in only one engine per process, and a second open, under any spelling of its path, raises PackInUseError; MemoryEngine.lock() and unlock() raise RuntimeError for a single-file pack. Directory packs keep Fernet file encryption.
  • Add prme compact <pack>, with --encryption-key KEY and --format table|json, which copies a closed pack file into a fresh one with COPY FROM DATABASE and replaces the original atomically, to give back the space DuckDB never returns. It takes a .prme file or a directory pack’s memory.duckdb, refuses while <pack>.wal exists, and keeps the source’s storage version and cipher. On a copy of a LoCoMo benchmark pack it took memory.duckdb from 743.5 MiB to 510.5 MiB in 1.0 s with identical retrieval results; 492.7 MiB of the remaining live data is 404 stored retrieval receipts.
  • Add prme migrate <directory> <pack.prme>, with --encryption-key KEY and --format table|json, which copies a directory pack into a new single-file pack through the open source engine, so a legacy Fernet-encrypted source stays encrypted at rest. It builds the pack in a hidden temporary file, opens it once, checks its vector snapshot and manifest rows, and only then moves it into place. It never overwrites a file and leaves the source in place. With a key, the new pack is encrypted natively. The LoCoMo benchmark pack migrated into a 521.5 MiB .prme file in 1.4 s.
  • Accept single-file packs across the tools. prme init <path.prme> creates exactly one file and writes no .env.example, printing a hint instead; prme doctor <path.prme> [--encryption-key KEY] checks the file, its required tables and its manifest and warns about a missing or stale vector snapshot; and every command that takes a db_path opens a .prme file. Without --encryption-key, prme doctor, prme compact and prme migrate use PRME_ENCRYPTION_KEY for a .prme pack when PRME_ENCRYPTION_ENABLED is true, as prme info does, but never for a directory pack’s memory.duckdb; an empty --encryption-key means no key. prme-mcp --db-path x.prme serves a single-file pack through PRME_PACK_PATH, ignoring an inherited PRME_DATABASE_URL, and a directory --db-path sets PRME_PACK_PATH empty so an inherited pack path cannot replace the directory. MemoryWorkspace(..., single_file_packs=True), also accepted by MemoryWorkspace.open(), keeps each namespace’s pack in one memory.prme file; the registry does not record the layout, so pass the flag consistently.
  • Add a research-only feasibility spike for images and video as memory sources: the package benchmarks/vision/, which describes video and photo keyframes with a local vision model and admits the descriptions as events, and the draft RFC-0018 that follows from it. The measurements are reported in benchmarks/results/research/2026-10-15/VISUAL-MEMORY-SPIKE.md. Nothing under src/prme/ changed: the engine, its configuration and its defaults behave as before, and there is no visual memory feature.

Changed

  • Batched extraction is on by default: extraction_batch_target_tokens defaults to 2048 instead of 0 (#254), so ingest() extracts up to 2,048 tokens of a session’s turns in one model call. This is the owner’s decision of 2026-10-16, made without the DeepSeek answer track that the project’s default-change rule asks for. On the offline evidence gate over ingest() packs built with deepseek-v4.1-flash:cloud, 2,048 cut model calls by 95% on LoCoMo (5,882 to 275) and by 84% on a 42-question LongMemEval-S slice (20,227 to 3,154), and all evidence packed went from 81.6% to 84.0% on LoCoMo (interval above zero) and from 34/38 to 35/38 on the slice (interval includes zero). All 500 LongMemEval-S questions have not been built. What changes for callers:
    • A batched request has its own timeout, the new extraction.batch_timeout (PRME_EXTRACTION_BATCH_TIMEOUT, 180 seconds), because it returns every turn’s claims; on the builds above, a request took 23 to 27 seconds on average, close to the 30-second extraction.timeout, which still applies to a turn’s own call. The engine’s close counts batch_timeout for each queued request, five minutes at most as before.
    • ingest(wait_for_extraction=False) queues the turn, so its claims can appear up to 30 seconds later unless a waiting ingest(), ingest_batch(), process_extractions() or the close extracts it first.
    • Turning on enable_windowed_extraction or enable_fact_text_resolution turns batching off unless extraction_batch_target_tokens is also set; an explicit target with either option is still refused.
    • The serialized configuration now always includes extraction_batch_target_tokens, so the defaults’ recorded identity changes: an ingest() build made with the defaults batches, and the ingest() answer track needs a new defaults baseline.
    • Set extraction_batch_target_tokens=0 (PRME_EXTRACTION_BATCH_TARGET_TOKENS=0) to extract one turn per call as before.
  • With batched extraction on (#254), the engine’s close now waits longer for the batches of turns that ingest() queued without waiting (#258): the usual drain time (10 seconds) plus extraction.batch_timeout for each request their flushes send, five minutes at most, instead of the 10 seconds alone, which a batched request could outlast. It also waits for a retry’s flush that is already running, and MemoryClient.close() waits that much longer too, so it no longer leaves such a close unfinished after 30 seconds. A turn still queued after that stays pending as before; opening an engine still never starts model work. The documentation says what this means for an MCP stdio session, whose server closes its engine when the session ends, and for a process manager’s stop timeout. Without batching, the close is unchanged.
  • With batched extraction on (#254), a turn a response gives no entry is now asked for again before it is saved (#260), instead of being saved as stating nothing. The other turns’ records are saved first, then one more request asks for the turns left out alone, with the same per-turn checks. The first request’s batch record names them in its new turns_asked_again field, which is left out when empty, so batch records without it keep their bytes; their records name the new request’s batch. That request is never followed by another, and a response that leaves out every turn of its request is saved as before. Records already saved are kept, so replay and prme rebuild still make no model call. The pack builder counts the extra requests among its calls and batches, caches and replays them like any other request, and reports turns_asked_again beside turns_without_entry. Without batching nothing changes.
  • Require duckdb>=1.4.0 (previously >=1.2.0). DuckDB 1.4.0 is the first release with native database encryption, which single-file packs use.
  • rebuild_indexes() (prme rebuild) gives lexical documents to the same nodes as the write path, through one definition shared with ingestion and the open of a single-file pack (has_lexical_document and LEXICAL_DOCUMENT_SQL in prme.storage.lexical_index): every active node with content, except an entity node that extraction created, which keeps its vector but gets no lexical document. Previously a rebuild indexed every active node, so prme rebuild on a pack built with ingest(), directory packs included, added a lexical document for each extracted entity that the pack never had before; it no longer does. An entity stored directly with store() keeps its lexical document. (#247)

Upgrade notes

  • ingest() now batches extraction by default (extraction_batch_target_tokens=2048). Extraction records written this way name their batch, and a PRME release from before 0.15.0 cannot read them, so upgrade every reader of a shared store before writing with this release, or set extraction_batch_target_tokens=0 to keep one call per turn. The default rests on the offline evidence gate alone; the DeepSeek answer track has not been run for it.
  • ingest(wait_for_extraction=False) can show a turn’s claims up to 30 seconds later than before, unless a waiting ingest(), ingest_batch(), process_extractions() or the engine’s close extracts it first. A process manager’s stop timeout should allow for the longer close.
  • Raise extraction.batch_timeout for slow models or large targets, and check that a local model’s context window holds a batch: at 2,048 tokens a request holds about 7,500 prompt tokens and its response about 4,700.
  • DuckDB 1.4.0 or later is required.
  • prme rebuild no longer gives an entity node that extraction created a lexical document, matching the write path.
  • Single-file packs (.prme) and visual memory are opt-in and new in this release; directory packs and stores that never observe are unchanged.

0.14.2  - 2026-10-08

Fixed

  • Fresh extraction plans recognize negative-polarity updates without replaces_object, retiring the same known-positive prior claim. Bounded, unambiguous whole-word targets can match pump to insulin pump, and explicit switched_to updates can replace positive uses/use FACT claims. Unrelated predicates, ambiguous objects, numeric shorthand and conditional/speculative updates remain protected; denials never assume unknown prior polarity. Retirement and validity closure use the existing atomic derivation transaction on both backends.

Added

  • Per-message first_person_owner=True on async/sync ingest, batch message dictionaries, HTTP and MCP binds singular first-person references to one owner identity per scope without a display name. It requires a user/human role and no named speaker. Leave it false for pasted first-person text.
  • Add the fast, research-only Jev memory-admission assay from PR #244: 40 authored contrast cases, a frozen protocol, cached decisions and reproducible replay. It writes no memory and enables no provider or production policy.

Upgrade notes

  • Fresh extraction records and plans use speech_act_v14; older records and saved plans retain their policies and checksums. No old claims, identities or raw notes are migrated. Upgrade all readers before writing v14 records.
  • Raw notes and shared evidence sentences can still contain retired assertions. NOTE exclusion is available from v0.14.1; structured claim state remains the authority for lifecycle. See correction limits. Validation uses scripted provider outputs; the reported Bedrock model was not rerun, and no benchmark answer-quality gain is claimed.

0.14.1  - 2026-10-08

Added

  • Add explicit retrieve() filters exclude_node_types and source_types across the async engine, synchronous client, HTTP API and MCP. Consumers can exclude raw NOTE messages and bare ENTITY nodes and allow only USER_STATED provenance. Filters apply before ranking and result limiting and to packed context, session expansion, evidence projection/augmentation and cross-scope hints, in both retrieval modes. An empty source allowlist returns no results; invalid enum values fail before pending materialization work is drained. Active filters are recorded in retrieval receipts and filter metadata.

Fixed

  • Accept unnamed first-person singular forms (I, me, my, mine, myself) as source mentions of each other. This keeps claims from “My name is Sam” and preferences from “Call me Sam” without enabling named-speaker grounding or binding those references to a personal name. Admission still requires source evidence and object support; bare imperatives without a first-person reference remain unsupported.
  • Default new assistant/system direct stores and raw-source notes to INFERRED instead of node-based ASSERTED or OBSERVED. Explicit epistemic assignments take precedence. INFERRED remains eligible in DEFAULT mode; the source allowlist can exclude generated content entirely.
  • Keep derived claims when evidence projection encounters a source excluded by the request’s node or source filters, instead of replacing them with an ineligible raw note.

Validation

  • The information-accumulation simulation now checks that its complete assistant-authored microservices decision reaches packed reader context. The new INFERRED default moved it below the old top-five keyword check while retaining its source text; no ranking weights or admission rules were relaxed.

Upgrade notes

  • Raw user notes still use their existing modality default, so questions and hypotheticals may remain ASSERTED notes. Excluding NOTE nodes is opt-in; retrieval defaults and historical rows are not automatically rewritten. Source filters do not prove that pasted first-person text describes its owner or repair older extracted claims. USER_STATED includes participant roles.
  • Fresh extraction records and prepared plans use speech_act_v13 for unnamed first-person support. Saved v11/v12 extractions retain v12 plan behavior; earlier plans preserve their policy, IDs and checksums. Earlier releases cannot read the new v13 records and plans. Upgrade every consumer of a shared store before ingesting with v0.14.1.
  • These changes address the PRME-side reports from Kio’s KAK-1114 investigation. The integration reference includes the filter call and its limits. Extraction behavior was verified with scripted provider outputs; the reported live Bedrock model was not rerun.

0.14.0  - 2026-10-06

Upgrade notes

  • ingest() and reader context defaults. Four defaults changed after v0.13.0 (all under Changed). An install upgrading from v0.13.0 gets:

    • claims whose text is their own sentences instead of the whole message (enable_claim_sentence_text=True);
    • a named speaker’s I, me and my bound to that speaker’s entity, so a speaker’s claims connect across messages (enable_speaker_references=True). Only the caller knows that the named speaker, and not someone the turn quotes, is who says I, so set PRME_ENABLE_SPEAKER_REFERENCES=false when a speaker’s turns quote others in the first person;
    • each source text packed once in reader contexts (packing.fold_repeated_text=True);
    • an exactly repeated extracted claim merged into one current record that supersedes the earlier copies (enable_claim_merge=True, #209).

    The ingest() settings apply to messages ingested after the upgrade, and records stored earlier keep their text, but a repeated claim ingested afterwards also supersedes a matching copy stored before it. Folding acts when a context is packed, so reader contexts over existing ingest() stores change too. store() is unchanged. To restore the v0.13.0 behavior, set PRME_ENABLE_CLAIM_SENTENCE_TEXT=false, PRME_ENABLE_SPEAKER_REFERENCES=false, PRME_ENABLE_CLAIM_MERGE=false and PRME_PACKING__FOLD_REPEATED_TEXT=false. docs/PACKING.md (“Default retrieval settings”) has the details.

  • Receipts. Default retrievals write receipt schema version 22, or 19 with PRME_PACKING__FOLD_REPEATED_TEXT=false. Earlier releases cannot read version 22 receipts. Stored receipts keep their bytes and replay unchanged.

Added

  • Opt-in repository coding memory through a local Docker MCP service, with source-backed lesson/handoff capture, bounded recall, shared Git-worktree configuration, and an isolated local-model experiment harness. The first four-task development pilot passed 2/8 runs with memory versus 0/8 without; both gains repeated one task and do not establish general coding improvement. Automatic use is shelved after the final registered study stopped as inconclusive after a grader defect (#208): ordinary coding tasks no longer recall or capture memory, and the commands stay available for explicit manual use. See coding memory .
  • Add packing.fold_repeated_text (PRME_PACKING__FOLD_REPEATED_TEXT, reader format only; #231), which packs each source text once and is a default since 2026-10-06 (see Changed). ingest() keeps a turn’s own record beside the claims extracted from it, so a context could show one sentence several times. With the setting on, a record whose text a packed record from the same source event already shows is left out unless its line shows another date, tag or speaker, and its budget goes to other records. Retrieval receipts record the setting in schema version 22, and other receipts keep their version and bytes. On the offline evidence gate over ingest() packs of all of LoCoMo, it raised the share of questions with all annotated evidence packed by 4.7 points with the defaults of the time and by 2.8 with speaker references and claim sentences.
  • Add opt-in windowed extraction (enable_windowed_extraction, with extraction_window_turns, default 4; #218, part of #91). ingest() shows the extractor the preceding turns of the same session, so it can read what the new turn refers to. Only the new turn is extracted: grounding still checks each claim against it alone, and the evidence quote stays a span of it. A turn without a session gets no window. Providers receive the new context argument only when a window exists, so a provider written for the earlier signature keeps working while the option is off.
  • Add opt-in self-contained fact text (enable_fact_text_resolution; #219, part of #91). ingest() asks the extractor for each fact’s text with a pronoun for a named person replaced by the name and a relative date by the date it means. A deterministic check keeps that text only when those replacements are its sole changes to whole sentences of the new turn; otherwise the fact keeps its source passage, which stays its evidence either way. An accepted text records grounding method resolved_text_v1 and lists each replacement in metadata.resolution. With the option off, the prompt, the response schema and saved extraction output are unchanged. On the offline evidence gate over all of LoCoMo, a window of 4 turns with resolution raised evidence recall by 1.6 points, and the share of questions with all evidence packed by 1.0 point, which is not clearly above zero (#225).
  • Add opt-in speaker grounding (enable_speaker_grounding, #230). In a turn that names its speaker, the speaker’s own I, me, my, mine or myself counts as a mention of the speaker when grounding checks that a claim’s subject and object occur in its quoted sentences, so a claim such as “Dana, lives in, Denver” for “I live in Denver” is no longer discarded. Extractions checked this way record grounding policy speech_act_v12. On all of LoCoMo it kept 10,599 claims instead of 7,136, but the extra claims restate turns that retrieval already reaches: with the current defaults it lowered the share of questions with all evidence packed by 0.8 points on the offline evidence gate, so it stays off.
  • Add a browser memory explorer (web/, #211). From a source checkout, uv run --extra api python -m web.server serves a read-only interface beside the HTTP API, with filters, source evidence, each extracted claim’s subject, predicate and object (#226), and a relationship map. Its server adds a paged one-hop endpoint, GET /v1/explorer/nodes/{id}/connections, that checks owner and scope before paging. It needs no frontend build, keeps the API’s authentication and loopback default, and opens the pack in PRME_CHAT_DATA_DIR, the folder examples/chat.py uses; a missing folder stops it with an error instead of creating an empty pack. uv run python -m web.demo ./my_memories_demo builds a small connected demo pack through the real ingest() pipeline with scripted extraction output, so it needs no model or network.
  • benchmarks.diagnostics.extracted_packs builds LoCoMo and LongMemEval-S memory packs through ingest() with an Ollama extraction model (local by default; an Ollama cloud model with --cloud), and the offline evidence gate replays them with gate --packs (#102, part of #91). Until now the gate could replay only packs built with store(), which never runs extraction, so it could not measure changes to extraction or the graph.
  • Add a DeepSeek answer track over packs built through ingest() (#232). benchmarks.integrations.gpt54_baselines prepare --contexts ingest --packs LABEL answers an extracted_packs build instead of the saved store() packs, which cannot show an option that only changes ingest(). The track keeps its own contexts, answers, run logs, A/A checks and verdicts, compare refuses to pair results from the two tracks, and the default-change rule applies to it unchanged. The 2026-10-06 default change passed on it, and its baseline for the new defaults, prme@3389586b, scores LoCoMo 1,190/1,540 (77.3%) and LongMemEval-S 440/500 (88.0%) (#234). Its scores are reported separately from the store() track’s.
  • Add a fixed development slice, benchmarks/slices/dev-v1.json (#224): three LoCoMo conversations (466 questions) and 42 LongMemEval-S questions, chosen by rules in benchmarks.diagnostics.dev_slice and tagged from committed answer runs as stable failures, stable passes or noisy. gate --slice and extracted_packs build --slice work on the slice alone for quick iteration; the full benchmarks stay the gates before a merge or a default change.
  • Add a chat probe (benchmarks/diagnostics/chat_probe.py, #226). A 54-turn scripted chat with an answer key goes through ingest() in about three minutes, and the report counts repeated claim text, unlinked entities and the key’s links, and checks whether the answers to 18 probe questions reach the reader’s context.
  • The offline evidence gate reports each LoCoMo speaker separately (#217, part of #84), and gate-compare compares two replays of built packs (#225).

Changed

  • ingest() now stores a claim’s own sentences as its text (enable_claim_sentence_text=True) and binds a named speaker’s I, me and my to the speaker’s entity (enable_speaker_references=True). The reader context packs each source text once (packing.fold_repeated_text=True in the product packing defaults). Together they passed the default-change rule twice on the DeepSeek answer track over ingest() packs (#227, #229, #231, #233): LoCoMo +1.69 and +1.82 points, LongMemEval-S +3.4 and +4.2, every interval excluding zero. Folding outside the reader format is ignored instead of refused. Default retrieval receipts are version 22. Set PRME_ENABLE_CLAIM_SENTENCE_TEXT=false, PRME_ENABLE_SPEAKER_REFERENCES=false or PRME_PACKING__FOLD_REPEATED_TEXT=false to restore the earlier behavior.

  • ingest() now merges a repeated extracted claim into one current record instead of adding another copy (#209). The new fact keeps the earlier copies’ evidence and supersedes them, so their history stays readable. Only exact claim identity counts; quantities, conditional claims, unresolved relative dates and claims that are not observed or asserted stay separate. store() is unchanged. Set PRME_ENABLE_CLAIM_MERGE=false to restore one fact per extraction.

Fixed

  • Auditable and compact contexts escape Unicode line separators so stored text cannot introduce apparent records or section headings (#108). Exact token accounting includes the escapes. New contexts containing these characters have different rendered bytes; persisted receipt checksums, source text and reader-format output remain unchanged. See packing compatibility.
  • The extracted-pack builder’s cache returns a cached extraction as it was stored (#226). A hit validated the stored result against its source again, which widened evidence quotes and discarded claims the live run had kept: 261 claims on 75 replayed turns in the first LongMemEval-S baseline build. The LongMemEval-S slice baseline was rebuilt from the same cache (#228). Since #230 the cache also keeps the provider’s parsed output and checks it again on replay, so one set of model calls can be measured under several grounding rules.
  • Benchmark tools find the datasets from any clone or Git worktree instead of the original research machine’s absolute path (#214). PRME_ORIGINAL_ROOT and PRME_MATRIX_ROOT override the two roots, and the registered harness files are unchanged, so they still match their recorded digests.
  • scripts/download_benchmarks.py also downloads the LongMemEval-S full history (longmemeval_s_cleaned.json, about 265 MB) that the harnesses and the evidence gate read, and verifies both pinned datasets by SHA-256 (#215).

0.13.0  - 2026-09-25

Upgrade notes

  • Retrieval defaults. The retrieval defaults changed twice on 2026-09-25 (both entries are under Changed). An install upgrading from v0.12.0 gets:

    • rank fusion scoring (scoring.fusion="rrf", rrf_k=60) with a current-state recency boost of 0.25 and an event-time tie-break, and a rank fusion session decay of 0.6 (#177). PRME_SCORING__FUSION=weighted restores the weighted formula; it also drops the recency boost and tie-break, and weighted scoring never applies the 0.6 session decay;
    • the reader context format, one plain line per record without node IDs or source types (#177). PRME_PACKING__CONTEXT_FORMAT=auditable restores JSON records, which callers that parse the context as JSON need. Answerability checks and verify_bundle() need that setting or PRME_PACKING__CONTEXT_CITATIONS=true;
    • balanced multi-path ordering, as in v0.12.0. #177 had made score order the default and #187 made balanced the default again. PRME_PACKING__MULTIPATH_ORDERING=score keeps #177’s score order.

    To go back to the v0.12.0 retrieval defaults, set PRME_SCORING__FUSION=weighted, PRME_PACKING__CONTEXT_FORMAT=auditable and PRME_PACKING__MULTIPATH_ORDERING=balanced (the last only makes the current default explicit), or in code pass scoring=ScoringWeights() and packing=PackingConfig() to PRMEConfig. docs/PACKING.md (“Default retrieval settings”) has the details.

  • HTTP API network binds. python -m prme.api and prme.api.server.run_server() refuse a non-loopback address without PRME_API_USER_KEYS or PRME_API_API_KEY (#120); configure credentials before upgrading a network-reachable server (see Changed).

Added

  • Add an opt-in cross-encoder rank order for the reranked candidates (PRMEConfig.reranker_prior_weight, or PRME_RERANKER_PRIOR_WEIGHT; issue #88). The reranker always ordered its prefix by 0.7 model score plus 0.3 of the candidate’s own score. At 0.0 the model’s score alone orders it, and with reranker_policy="score_envelope" or "anchored_score_envelope" the prefix keeps its rank-fused scores in that order. The default, 0.3, changes nothing. A weight other than 0.3 needs an envelope policy (configuration with "legacy" is refused) and is recorded in the receipt’s execution parameters and the reranker’s feature identity; at 0.3 neither records it, so receipts keep their bytes, and no new receipt schema is needed. The offline evidence gate now refuses reranker settings without enable_reranker, times the reranker per question (p50 and p95) and records the model runtime of a reranker run. On the gate, rank order over the top 100 packed all evidence for more LoCoMo questions at 4K (+1.7 points, multi-hop +3.9) and, with the anchored policy, more LongMemEval-S questions (+1.3 points), but no variant was higher on LongMemEval-S at 8K, and the top 300 lost LongMemEval-S evidence at both budgets. The reranker stays off until an answer comparison supports it.

  • Add opt-in session context packing (PackingConfig.session_context_packing, or PRME_PACKING__SESSION_CONTEXT_PACKING; issue #86). Session expansion gave a turn it added a single path and never counted its path on a turn another search had found, so packing put those neighbors behind every multi-path candidate, and in an exact replay of the 2026-09-23 LoCoMo retrievals none of the turns only expansion found was packed. trigger_tier counts the session path when it joins a candidate and gives a turn that expansion added the multi-path tier of the result that brought it in. adjacent packs the same records and places each neighbor beside that result in session order. Unset (the default), nothing changes. Results and bundles carry each neighbor’s session_context_link only when the setting is on. Receipts that record it use schema version 21 under either scoring formula; to stop using it, unset the variable rather than returning to an earlier release, which cannot read version 21 receipts. The offline evidence gate now reports, per benchmark and category, how many packed records session expansion reached, found alone or scored, and gate-compare shows them before and after. On the gate at 4K both values packed all evidence for more questions with no category lost: LoCoMo +0.9 points (95% interval +0.1 to +1.8) and LongMemEval-S +0.4 (+0.0 to +1.1). Packing a trigger’s neighbors right after it instead lost LongMemEval-S evidence and was not kept. The setting stays off until the epic #77 paired answer run supports it.

  • Add an opt-in reader context format (PackingConfig.context_format="reader", or PRME_PACKING__CONTEXT_FORMAT=reader) that renders each packed record as one line: its event time or caller-supplied validity range, tags for non-default epistemic and lifecycle states, and the complete text as a JSON string. The renderer adds no node IDs, source type, created_at or admission-time valid_from, and it excludes records that fit only as a fallback without their own text. The full record stays in MemoryBundle.sections and the retrieval receipt. context_citations=True adds [m3] references and fills context_references, which MCP now returns with include_context. Reader receipts use schema version 14; versions 1 to 13 keep their canonical bytes. It is now the default (see Changed).

  • Add opt-in reciprocal rank fusion for retrieval ranking (ScoringWeights.fusion="rrf", or PRME_SCORING__FUSION=rrf, with rrf_k, default 60). Each candidate is ranked within the pool on the semantic and lexical channels and scored by 1/(k + rank) per channel, scaled so first place on both is 1.0. Epistemic, node-type and temporal adjustments then apply relative to the pool’s largest value. Graph proximity, recency, salience and confidence, which were constant or uninformative in the 2026-09-23 benchmark archive, are not used (recency is available through an opt-in, below). Score provenance records formula version 2 with the saved ranks and factors, and these receipts use schema version 16 (version 15 receipts from earlier development builds, which lack semantic_relevance, stay readable). Because a fused score says where a result ranks rather than how relevant it is, min_score under rank fusion compares against each result’s semantic_relevance instead: the semantic cosine similarity of the memory behind it (for session, episode and evidence context, the memory that pulled it in), recorded on every result, cross-scope hint, selection exclusion and receipt candidate, and in the LangChain and LlamaIndex result metadata. A floor tuned on weighted scores does not carry over, cosine ranges depend on the embedding model, and a low-cosine exact keyword match is filtered out when a floor is set. Non-neutral request ranking multipliers are rejected before retrieval (HTTP 422); weighted ranking profiles are inapplicable with reason rank_fusion_scoring; learning evaluation skips rank-fused receipts (rank_fusion_receipt_records); and the feedback_apply job reports not_applicable and keeps its signals. PRMEConfig now uses it by default (see Changed); ScoringWeights() remains the weighted sum, whose serialized settings, version ID and receipts are unchanged. As with earlier receipt versions, a release without version 16 support cannot read rank-fusion receipts.

  • Add an opt-in session decay for rank fusion (PackingConfig.session_context_rank_fusion_score_decay, or PRME_PACKING__SESSION_CONTEXT_RANK_FUSION_SCORE_DECAY). Rank-fused scores are compressed, so at the default session decay of 0.85 a first-place result’s adjacent turns outrank every result from about twelfth place down, and they crowded LongMemEval-S multi-session evidence out of the context. When set, it replaces session_context_score_decay for results scored by rank fusion; the offline evidence gate favored 0.6, which is now the default (see Changed). None restores the previous behavior. Receipts that record it use schema version 17.

  • Add a fail-open for a rank fusion min_score floor, which used to empty every retrieval while vector search was failing or had detected an embedding model mismatch. No result has a semantic cosine then, so the floor is skipped for that request: results keep their fused order, and RetrievalMetadata.min_score_skipped (in the HTTP and MCP metrics) is set next to the backend_failures entry for the vector path. Cross-scope hints skip it too unless one of them has a cosine. The floor still applies whenever a result has a cosine, and weighted scoring is unchanged. Receipts of these retrievals use schema version 18, which records min_score_skipped. The LangChain and LlamaIndex retrievers accept an optional min_score and add min_score_skipped: True to each result’s metadata when the floor was skipped.

  • Add two opt-in rank fusion settings for conflicting memories (ScoringWeights.rrf_recency_boost and ScoringWeights.rrf_tie_break, or PRME_SCORING__RRF_RECENCY_BOOST and PRME_SCORING__RRF_TIE_BREAK). Rank fusion ignores recency, so on a current-state question the older of two conflicting memories could rank above the newer one. With the recency boost set, those questions multiply each fused score by 1 + boost x recency, relative to the pool’s largest value, where recency is the weighted formula’s current-state recency (with lambda at least 0.05). With rrf_tie_break="event_time", equal fused scores are ordered newest first instead of by path count and node ID. Both apply only under rank fusion. PRMEConfig now sets a boost of 0.25 and the tie-break by default (see Changed); ScoringWeights() leaves both unset, and then nothing changes. A boost of 0.25 with the tie-break passed every simulation with the reader format, score order and a 0.6 rank fusion session decay, and changed no benchmark’s share of questions with all evidence packed significantly on the offline evidence gate. Receipts that record either use schema version 19.

  • Add conversation participants (#84). store(), store_with_receipt(), ingest(), ingest_batch(), ingest_fast() and FastIngestItem accept an optional speaker name, as do HTTP POST /v1/store, /v1/ingest and /v1/ingest/fast and the MCP memory_store, memory_ingest and memory_ingest_fast_many tools. The new participant role marks a human in the conversation other than the memory’s owner: like user, its sources are USER_STATED (with the default confidence matrix, 0.80 for an asserted claim instead of the 0.60 an assistant source gets), but its statements do not reinforce the owner’s instructions. The speaker is kept in the reserved metadata.prme_speaker_v1 key of the source event and its direct node or raw note, not yet on extracted claims; passing that key in metadata is now rejected. The reader context format prints it as "Caroline": "text" unless the text already begins with it, and the auditable format adds a speaker key. With enable_qa_pairing=True, a change of speaker within one role also creates a merged Q-A node, whose halves start with their speakers’ names. Contexts for records without a speaker keep their exact bytes. The legacy LoCoMo harness (benchmarks/locomo.py) now stores both people as participants, so its scores are not comparable with its earlier runs; the registered 2026-09-23 comparison and its saved packs are unchanged.

  • Add an opt-in event-time clock for the weighted formula’s recency (ScoringWeights.recency_time="event_time", or PRME_SCORING__RECENCY_TIME=event_time with PRME_SCORING__FUSION=weighted). Unset, the weighted formula measures recency on questions that are not about the current state from when a memory was last updated or created, so history imported with a past event time and retrieved at a past reference time scored full recency on every memory (issue #83). With the setting, every memory is dated by when it was stated: its event time, else when it was stored, never when it was last updated and never its validity start. Questions that are not about the current state measure that back from the request’s reference time; current-state questions keep measuring back from the newest candidate. Memories without an event time, such as entity, consolidation and profile nodes, count from when they were stored. Rank fusion, the default, already uses this clock for its recency boost and tie-break, so it drops the setting with a warning, and nothing changes by default. Receipts that record it use schema version 20; to stop using it, unset the variable rather than returning to an earlier release, which cannot read version 20 receipts. On the offline evidence gate it lost LoCoMo evidence (-4.8 points of questions with all evidence packed with the previous auditable format and balanced order, -4.5 with the reader format, both intervals excluding zero) and left LongMemEval-S unchanged, so it stays off.

  • Add an opt-in temporal-first query intent (PRMEConfig.query_intent_order="temporal_first", or PRME_QUERY_INTENT_ORDER=temporal_first). By default a question that names a person, place or organization, such as “When did Caroline go to the support group?”, is classified as an entity lookup before its temporal wording is checked, so it never gets temporal affinity scoring (issue #85). With the setting, temporal wording or a date in the question makes it temporal first, so it gets temporal affinity. A present-tense question that only its wording put on the current-state path leaves it unless it says current, now, latest or similar, and the temporal context guidance is added where the question’s wording does not already select it. A lone capitalized month or weekday word that is part of a name (“Who is June dating?”) is read as the name, not a date. Entity names are extracted either way. Receipts record the setting only when it is temporal_first, so default receipts keep their bytes. The offline evidence gate now records, for each replayed question, whether its candidates share one temporal affinity and whether the current-state path applied; its comparison counts the first per category and lists the questions that enter or leave the path. On the gate the setting packed all annotated evidence for 5 more LoCoMo questions and 1 more LongMemEval-S question and for none fewer (+0.3 and +0.2 points); it stays off until the paired answer run.

  • Publish the complete 2026-09-23 GPT-5.4 benchmark comparison, with gpt-5.4-2026-03-05 as reader and judge, the retrieval defaults of that date and a 3,996-token context: LongMemEval-S 430/500 (86.0%) and LoCoMo 985/1,540 (64.0%, categories 1 to 4, strict judge), with its registered protocol and evidence-retention diagnostics under benchmarks/results/research/2026-09-23/. These remain the published reference. benchmarks.integrations.gpt54_baselines adds full-context and plain-RAG (plain-vector, plain-bm25, plain-rrf) baseline arms that reuse its reader, judge, prompts and questions (issue #95). No arm has been answered with GPT-5.4 yet.

  • Add an offline evidence gate for retrieval, packing and representation changes (issue #78). python -m benchmarks.diagnostics.product_packing gate replays the public retrieve() for all 1,540 LoCoMo and 500 LongMemEval-S questions over copies of the saved 2026-09-23 memory packs and measures the context the product renderer produced: records per context, the memory-text share, packed records without text, the questions with all annotated evidence packed, evidence ranks, and a projected accuracy that is a planning estimate, not an answer score. gate-compare pairs two runs question by question with a bootstrap interval, and --plain vector, bm25 or rrf measures a plain RAG reference (#95). It makes no reader, judge or paid API calls. Later changes added session expansion counts (#86), temporal and current-state observations (#85), and candidate recall per channel and retrieval latency (#87). A gate comparison of balanced and score order under the reader format is recorded in benchmarks/results/research/2026-09-24/READER-PACKING-ORDER-GATE-V1.md (#81). See BENCHMARKS.md.

  • Add a DeepSeek answer track for benchmark answer runs with no API cost (issue #117). benchmarks.integrations.gpt54_baselines --provider ollama answers with deepseek-v4.1-flash:cloud as reader and judge through a local Ollama server, with the registered prompts, questions and official LongMemEval-S judge. It prepares the defaults as the prme arm and a change as a named variant (--variant with --set). run-pair answers a variant and a fresh run of the defaults interleaved question by question (#129), compare reports the paired difference with a 95% interval, and verdict decides a variant from the pairs compare recorded (#144). The track measured its run-to-run floor (#118), records an interleaved A/A check (#129, #137), scores a looping answer or a garbled verdict as incorrect after one retry (#132), records a new defaults baseline after a default changes (#123), and ties each pair to the current baseline, the 4K budget, and the variant’s settings and context text (#125, #127, #130, #139, #143). Its scores are reported separately and are never compared directly with the GPT-5.4 results. On this track the defaults of the GPT-5.4 run scored LongMemEval-S 423/500 (84.6%) and LoCoMo 1,014/1,540 (65.8%) (#126). A new baseline was recorded after each default change (#182, #191); the current one, prme@d811e3ed, scores LongMemEval-S 453/500 (90.6%) and LoCoMo 1,250/1,540 (81.2%). See BENCHMARKS.md.

  • Add a lenient LoCoMo judge score beside the strict one (issue #96). benchmarks.integrations.lenient_judge sends the saved answers of a complete LoCoMo run to Mem0’s published LoCoMo judge prompt, with the judge model and settings that gave the strict verdicts; no reader is called. On the DeepSeek baseline prme@46647825 it accepts 1,095/1,540 (71.1%) against the strict judge’s 1,007/1,540 (65.4%). The strict judge stays the primary score, and the lenient pass over the GPT-5.4 answers has not been run.

Fixed

  • Accepted temporal relation guidance (TemporalRelationConfig.enabled, opt-in) now keeps the bundle’s earlier packing exclusions after its repack and adds the records it displaced, without duplicate IDs. Aggregation coverage therefore still reports context limits when records were excluded before enrichment; the temporal metadata’s dropped_record_ids still lists only the records that stage displaced.
  • The configuration reference now gives the candidate limit defaults that retrieval uses: PRME_PACKING__VECTOR_K and PRME_PACKING__LEXICAL_K 500, PRME_PACKING__AGGREGATION_K_MULTIPLIER 3.0 and PRME_PACKING__AGGREGATION_K_MAX 2000. It listed 250, 250, 2.5 and 500. The offline evidence gate now reports each channel’s candidate recall at k and the retrieval latency (issue #87); smaller limits lost LongMemEval-S evidence there, so the defaults are unchanged.
  • The LlamaIndex chat store reads a session turn whose role LlamaIndex has no value for, such as participant or human, as a user turn instead of failing to read or clear the session.
  • The HTTP API docs and the example Dockerfile now start the server with python -m prme.api. The uvicorn prme.api:app command they showed never loaded the app, because prme.api:app names the prme.api.app module.
  • Query reformulation (enable_query_reformulation=True) now uses the extraction endpoint, credential and timeout as well as its provider and model. It used to build its own client from the provider and model alone, so its requests went to the provider’s default endpoint (for OpenAI, api.openai.com with OPENAI_API_KEY from the process environment) even when extraction was pointed at another endpoint or credential. It also ignored provider settings in .env, used Instructor’s tool mode with Ollama models that list tool support, and had no time limit of its own. It now reaches the extraction base_url with the extraction api_key (or the provider’s own settings from the environment or .env, as extraction does), and returns no alternate queries when a call takes longer than the extraction timeout (30 seconds by default). With Ollama it uses JSON mode and a reasoning effort of "none", as extraction does, and it passes the model on each call, which Bedrock requires. Its other sampling is unchanged: the provider’s default temperature and two schema-validation retries. Each engine keeps its own reformulation clients, so they are not reused on another event loop.
  • Extraction, answerability and reformulation now build their Instructor clients in one place, prme.model_runtime. An Anthropic endpoint (base_url or ANTHROPIC_BASE_URL) now works: Instructor 1.14 built Anthropic clients without it and passed it to every call, which failed.
  • ExtractionConfig.timeout must now be a positive, finite number. Zero or a negative value made every extraction time out.
  • Context ablation now keeps a compact or reader bundle’s format and references instead of re-rendering the counterfactual as auditable JSON.
  • A text-bearing PackingConfig.min_fidelity (full, prose or structured, set in code, with PRME_PACKING__MIN_FIDELITY or per request) now keeps every packed record’s memory text in the auditable and compact formats too. A record whose stored text is blank is excluded and listed in MemoryBundle.excluded_ids instead of being packed with empty text, and it no longer takes the reserved first place under balanced ordering. Set such a floor to keep the text-free key_value and reference fallbacks out of the context. The default floor stays reference, so default contexts are unchanged.
  • Aggregation coverage no longer reports a blank record’s exclusion as a token_budget limit when the reader format or a text-bearing floor excludes it.
  • A per-request min_fidelity passed to MemoryEngine.retrieve() as a plain string is now converted to RepresentationLevel, so receipts serialize it without warnings and an unknown value fails before retrieval runs.

Changed

  • Retrieval defaults. PRMEConfig() now retrieves with rank fusion (scoring.fusion="rrf", rrf_k=60) with a current-state recency boost of 0.25 (scoring.rrf_recency_boost) and an event-time tie-break (scoring.rrf_tie_break="event_time"), the reader context format (packing.context_format="reader", previously auditable), score ordering (packing.multipath_ordering="score", previously balanced; balanced is the default again, see the next entry) and a rank fusion session decay of 0.6 (packing.session_context_rank_fusion_score_decay, previously unset). The context budget and session expansion are unchanged. The combination (the DeepSeek-track variant prme-reader-rrf-sd06-rec) passed the epic #77 default-change test twice on the DeepSeek answer track at a 3,996-token context: LoCoMo went from 65.7% to 80.9% and from 65.8% to 81.6% (+15.2 and +15.7 points, 95% intervals +13.0 to +17.4 and +13.5 to +17.9), and LongMemEval-S went from 85.8% to 86.8% and from 86.4% to 86.6% (+1.0 and +0.2 points, intervals -1.6 to +3.6 and -2.6 to +3.0). See BENCHMARKS.md. What this changes for existing installs:
    • Contexts are one plain line per record instead of JSON objects, so more records fit in the same budget. The lines carry no node ID or source type. Callers that parse the context text as JSON must set PRME_PACKING__CONTEXT_FORMAT=auditable.
    • Answerability checks and verify_bundle() raise ValueError for a reader bundle without citations. Set PRME_PACKING__CONTEXT_CITATIONS=true or use the auditable format for those callers.
    • Rank fusion ignores the additive scoring weights, graph proximity, salience and confidence, and uses recency only through the current-state recency boost, so salience decay, reinforcement and confidence changes no longer move a result’s rank. Candidates found only through the graph or a pin score 0 (pins are still packed first), and min_score is compared with the semantic cosine instead of the composite score. Non-neutral request ranking_multipliers are rejected (HTTP 422, MCP error), learned ranking profiles are not applied (rank_fusion_scoring), and feedback_apply reports not_applicable. Set PRME_SCORING__FUSION=weighted to keep the weighted formula, including any tuned PRME_SCORING__W_* weights.
    • The new defaults live in PRMEConfig (default_scoring_weights() and default_packing_config()), and PRME_SCORING__* and PRME_PACKING__* environment variables, .env entries and secrets that set only some values keep them. Setting PRME_SCORING__FUSION=weighted also drops the rank fusion recency boost and tie-break, which weighted scoring cannot use. ScoringWeights() and PackingConfig() built in code keep their field defaults (the weighted formula; auditable, balanced and no rank fusion session decay), because stored receipts and configurations omit some of those values and must keep their meaning; code that builds them from scratch gets the previous behavior. Copy config.packing or config.scoring with model_copy(update=...) to change one setting and keep the new defaults.
    • Default retrievals write receipt schema version 19. Stored receipts keep their bytes and replay unchanged.
    • To restore the previous defaults, set PRME_SCORING__FUSION=weighted, PRME_PACKING__CONTEXT_FORMAT=auditable and PRME_PACKING__MULTIPATH_ORDERING=balanced. The recency boost and tie-break are then dropped, and weighted scoring never applies the 0.6 session decay (its receipts omit it), so retrieval, contexts and receipts are those of the previous defaults. In code, pass scoring=ScoringWeights() and packing=PackingConfig() to PRMEConfig.
  • Multi-path ordering default. PRMEConfig() now orders the ordinary multi-path tier with balanced (packing.multipath_ordering="balanced") instead of score order. The other retrieval defaults above are unchanged. balanced was also the ordering in v0.12.0, so an install upgrading from that release sees only the scoring, context format and session decay change above. Balanced reserves the highest-scored ordinary multi-path candidate and then orders the rest by score / full-entry tokens**0.25, so a context holds more short records than under score order. With the other defaults (the DeepSeek-track variant prme-rrf-rec-balanced), it passed the epic #77 default-change test twice on the DeepSeek answer track at a 3,996-token context, each pair against a fresh run of the score-order defaults: LongMemEval-S went from 86.8% to 90.0% in both pairs (+3.2 points, 95% intervals +0.6 to +6.0 and +0.6 to +5.8), and LoCoMo went from 80.1% to 80.6% and from 80.6% to 80.4% (+0.5 and -0.2 points, intervals -0.9 to +2.0 and -1.5 to +1.1). LongMemEval-S multi-session questions gained 7.5 and 11.3 points; LongMemEval-S knowledge-update questions (73 to 70 of 78 in both pairs, #169) and LoCoMo multi-hop questions (-1.8 and -4.3 points) fell, with intervals including zero. See BENCHMARKS.md. Candidate retrieval and scoring, the context budget and the receipt schema version (19) are unchanged; receipts record the ordering as before.
    • Upgrade note: to keep score order, set PRME_PACKING__MULTIPATH_ORDERING=score. In code, copy the packing configuration with multipath_ordering="score": config.packing.model_copy(update={"multipath_ordering": "score"}).
  • Simulation checkpoints can require memory text in the context the reader gets (SimCheckpoint.context_keywords, reported as CheckpointResult.context_missing). Four checks that tested the weighted formula’s order rather than what reaches the reader now use it: the infrastructure question in consolidation, the tools question in remention, and the database and observability questions in surprise_gating.
  • Receipt versions from 4, 6 and 8 on must state their ordering, guidance and episode settings instead of taking the current defaults. Stored receipts always include them.
  • Installs that already set min_fidelity to full, prose or structured and store records with blank text (for example tool-call-only chat turns) now see those records excluded from the auditable and compact contexts instead of packed with empty text.
  • Upgrade note: python -m prme.api and prme.api.server.run_server() now refuse to start on a non-loopback address (such as 0.0.0.0, including inside a container) when neither PRME_API_USER_KEYS nor PRME_API_API_KEY is set. They used to log a warning and serve every request without authentication. The command exits with status 2 and run_server() raises prme.api.server.UnauthenticatedBindError (a ValueError), before the app, the engine or a listener is created. Configure credentials before upgrading a network-reachable server. Per-user keys bind each request to its owner, so a backend that acts for many users needs the global key. Only literal loopback addresses (127.0.0.0/8, ::1 and IPv4-mapped loopback) and localhost (when it resolves only to loopback) count as loopback; other host names count as network addresses. A server that only an authenticating reverse proxy can reach can pass --allow-unauthenticated-external-bind (allow_unauthenticated_external_bind=True), which starts it with a warning and gives every caller operator access. The command no longer accepts abbreviated options. Hosting the app under another ASGI server skips the check, as the HTTP and deployment guides now explain.
  • The development docker-compose.yml publishes its PostgreSQL test database (fixed prme_test credentials) on 127.0.0.1:5432 only, instead of on all interfaces. CI connects to 127.0.0.1. An existing container keeps its old mapping until it is recreated: run docker compose up -d once. The deployment guide now separates this test database from a network-reachable one.

0.12.0  - 2026-09-22

Experimental retrieval and evaluation

  • Add explicit reranker score_envelope and anchored_score_envelope policies to preserve the scored prefix’s original score scale during downstream packing. Add opt-in alternate-query max_signals merging for existing candidates, with exact source-snapshot checks and atomic failure behavior. Both storage engines receive these through PRMEConfig; ordinary defaults remain unchanged.
  • Record neural ordinal assignments in receipt schema 13, with exact score replay and distinct execution identities for ranking-profile compatibility. Stored schemas 1–12 keep their canonical bytes; older readers cannot read schema 13.
  • Retain complete registered LongMemEval-S development results, including negative interactions, inactive features, provider failures and failed primary controls. Production scored 437/500. The anchor-preserving trial scored 430/500 versus its new control’s 429/500 (+0.2 points, 95% CI [−1.6,+2.0]); better source retention did not establish an answer gain. Ongoing ingestion/reformulation trials remain explicitly unfinished, with no partial-arm score or new default.
  • Update the archived milestone-checklist test to its moved source path, fixing the existing mainline CI failure without weakening its 29 historical assertions.

Fixed

  • Grounded fact objects with one exact currency or bounded measurement unit no longer depend on model-authored quantity fields. PRME can derive the verbatim value and unit from the grounded object and evidence, while preserving the existing exclusions for approximations, ranges, identifiers and unsupported notation. A separate user-only path recovers one complete first-person conditional quantified action when the model drops the whole claim. Fresh outputs recorded speech_act_v10. V10 also suppresses conditional fallback when an existing validated fact has the same evidence, predicate, polarity and quantity identity but quotes a different valid condition substring. Saved v9 through v6 outputs keep their bytes and continue to prepare v12 plans. Registered 20-case runs passed all zero-tolerance gates on both hosted DeepSeek and a distinct local Qwen 35B-A3B artifact: 40/40 cases, 24/24 expected quantity instances, no unexpected quantities and no provider errors.

  • Fresh speech_act_v11 extraction can recover one leading exact measure from a user-authored first-person completed action in a bounded verb lexicon. The path retains the verbatim measured phrase as the fact object and runs the existing evidence, quantity and reference checks. It excludes modals, negations, examples, questions, conditions, approximations and ranges. Saved v10 through v6 extraction records remain byte-compatible and prepare v12 plans.

  • Quantity aggregation accepts optional predicate_prefixes for explicit, token-bounded composition of model-authored predicate detail. A normalized prefix such as raised matches raised and raised_*, but not fundraised; no synonyms or embeddings are inferred. Responses distinguish this mode with semantic_equivalence="normalized_exact_and_predicate_prefix". A preregistered end-to-end Qwen 35B-A3B confirmation passed all ten source contracts and all aggregation gates, returning the exact $3,750 total from four real charity memories while excluding unrelated, negated, conditional, approximate, incompatible-unit and cross-owner controls.

  • Python, HTTP and MCP now expose a fail-closed natural-language quantity planner/executor for a fixed set of complete, qualifier-free amount and count questions. It returns the exact structured query and assumptions alongside the result and preserves the question’s exact I or we subject. Unsupported qualifiers, negation, future wording, named subjects, actions or units return a typed refusal without scanning memory; ordinary retrieval remains unchanged. A preregistered confirmation over a frozen end-to-end pack passed five executable questions and six refusal controls with zero refusal scans.

  • Assertion and quantity aggregation queries now round-trip their serialized default empty scope selector. An empty scope list consistently means all scopes; duplicate scopes remain invalid.

  • A user-authored sentence such as My final score was 3 no longer disappears when the model omits its claim. A narrow deterministic path admits terminal exact score, count, rating and level attributes with a source-literal concept subject, then applies the normal evidence, quantity and reference checks. It excludes assistant messages, examples, unsupported attributes, approximations, ranges and multi-number values. This behavior was introduced under speech_act_v8; fresh v10 outputs retain it, while saved v9 through v6 outputs retain their original bytes and still prepare v12 plans.

  • Quantified claims now remain exact when a provider serializes a decimal as a JSON float: PRME discards the float and recovers only a single supported decimal token from the grounded source phrase. Surrounding approximation and range cues fail closed even when the provider clips them from source_text. This behavior was introduced under speech_act_v7; fresh v10 outputs retain it, while saved v9 through v6 outputs keep their original bytes and still prepare v12 materialization plans.

  • Built-in extraction now preserves literal first-person attempts and intentions instead of admitting them under completed or current-state predicates, including component relationships inside the attempted action. The preregistered confirmation outputs recorded speech_act_v6 and prepared speech_act_v12 plans. A narrow source/entity recovery retains the target of an explicit nonactual clause when the model returns the entity but omits its claim, while preserving explicit conditions. Saved v5 outputs remain v11, v4 remain v10, v3 remain v9, v2 remain v8, and legacy extraction records recover under temporal_validity_v7, so restart cannot claim a validator that did not run. A preregistered 14-case real-model development confirmation passed all 15 safety and utility targets with zero unsafe claims on both a hosted DeepSeek profile and a distinct local Qwen 35B-A3B artifact.

  • Registered extracted BEAM ingestion now completes both durable raw-source materialization and structured extraction before acknowledging a source. Source-list timestamps come from the latest cited evidence event, and exact sibling passages are capped with max_per_source=1. Schema-5 validation inspects each registered owner’s DuckDB work rows after execution, so a scored run with pending raw notes or extractions cannot pass again.

  • Built-in extraction now sends source messages literally even when they contain Jinja expressions or blocks. Grounding context is task-local and isolated across concurrent calls, so Instructor cannot evaluate user code as a prompt template or alter the source before inference.

  • BEAM schema-4 extracted-run registrations now bind the extraction retry count, and adapter schema 3 reports the applied value. A library-default change can no longer silently alter an attested extraction run.

  • Exact integer and string decimal quantities from Instructor JSON responses now survive strict extraction validation. JSON float approximations and malformed quantities are still discarded without losing an otherwise grounded fact. Missing or ambiguous named references now discard only their affected claim, so one omitted entity declaration cannot exhaust retries and lose every grounded, reference-closed sibling in the provider response.

  • Preserve BEAM source-session boundaries when the upstream request exposes an observation timestamp, preventing adjacent-turn expansion from treating a complete multi-session history as one session.

  • Store-time oscillation dampening now revalidates the complete owner-scoped supersedence chain under lock and commits its confidence update with a deterministic, checksummed PENALTY record in one DuckDB or PostgreSQL transaction. Concurrent, restarted, failed, and cancelled attempts cannot apply the same penalty twice or leave an unjournaled confidence change.

  • Automatic question/answer pairing is now disabled by default. Every registered quality benchmark already excluded the in-process heuristic, whose extra derived node is not atomically journaled or recovered after restart. enable_qa_pairing=True remains available for explicit hypothesis testing.

  • TTL expiration now revalidates policy under lock and commits archival with a deterministic, checksummed TOMBSTONE_SWEEP record in one DuckDB or PostgreSQL transaction. The record retains complete before/after state and RFC-0007 policy fields; concurrent, restarted, failed, and cancelled attempts cannot leave an unlogged archive or a tombstone without its transition.

  • Unverified organizer alias proposals now publish their deterministic RELATES_TO edge and a checksummed complete ALIAS_PROPOSED record in one DuckDB or PostgreSQL transaction. Exact, concurrent and restarted retries reuse one pair identity; incompatible current nodes are rejected, and legacy random-ID links are reused without fabricating historical journal inputs.

  • Built-in embedding configuration now infers registered FastEmbed and OpenAI dimensions when omitted, selects a coherent OpenAI model/dimension pair when only that provider is chosen, and rejects unknown model dimensions before opening an index. Direct FastEmbedProvider construction follows the same fail-early contract without loading model weights.

  • Repeated single-vector archive cycles no longer stall in the native HNSW deletion path after roughly 192 operations. PRME now requires USearch 2.26.2, whose deletion implementation completes this churn pattern; a subprocess regression test keeps a native stall bounded and observable. A preregistered AgentMemBench confirmation completed 200/200 archive-retirement cases, with every memory visible before archival and absent from retrieval afterward.

Added

  • Added an optional local ClaimVerifier that checks explicit declarative claims against exact packed passages with a revision-pinned NLI cross-encoder. It scores individual evidence first, explores only bounded minimal groups, exposes supporting and refuting groups plus every model score and digest, preserves typed source/time/lifecycle provenance, and returns incomplete without a model call for derived exhaustive counts or lists. The verifier is available through prme[verification]; it remains opt-in while representative claim evaluations establish domain thresholds. Its first registered 20-case real-model assay matched 17 cases and failed the all-cases gate. It produced zero unsafe supports and recovered both unseen two-passage claims, but overcalled two neutral passages as refutations. The default verifier now requires an explicit correction cue or incompatible concrete value in the exact evidence group before accepting a model contradiction; blocked raw contradictions remain auditable and return insufficient. A registered same-cohort confirmation passed all eight targeted gates and changed only the two intended false refutations, yielding 19/20 expected statuses with zero unsafe supports. This is regression evidence on an observed development set, not a held-out accuracy result. A later 48-case previously unexecuted development assay then failed its safety gate with two unsafe supports: one desire was treated as a completed action and one explicit ownership conflict was missed. The failed result is retained. The verifier now blocks entailment when evidence has a nonactual speech act absent from the claim, retaining the raw score and an explicit limitation, and can surface narrowly matched explicit negation with a distinct deterministic decision basis. Claim verification remains opt-in. A registered same-cohort safety confirmation passed all eight gates: the raw scores were unchanged, exactly the two intended statuses changed, and unsafe supports fell from two to zero. Four implicit contradictions and two relation-composition cases remained unresolved. A follow-up diagnosed that bounded candidate ranking had also reordered evidence before NLI; group scoring now preserves caller order after selecting candidates. Its registered same-cohort confirmation passed all eight gates, recovered all 6/6 minimal groups and changed exactly the two intended statuses, reaching 44/48 with zero unsafe supports. A later 44-case previously unexecuted guard assay failed: four negative distractors about a different relation were treated as refutations or conflicts. The result is preserved and blocks promotion of claim verification. Evidence-side negation now requires every normalized non-generic claim token in the negated clause before either deterministic or model-assisted refutation, and reported questions share the direct-question speech-act mode. A registered same-cohort confirmation passed all eight gates at 43/44 with identical model scores: all four wrong-relation negatives became insufficient, the reported question became supported, and every true refutation and conflict was retained. The first external WiCE oracle-retrieval assay then failed precision, recall and balanced-accuracy gates across all 358 human-annotated test claims. It accepted 0/32 fully unsupported claims, but passage-wide speech-act and negation guards reduced supported recall to 10.91%. Result schema 3 now localizes those guards within multi-sentence passages and rechecks the retained exact text with the same model. It records one-based retained sentence numbers, premise digests, probabilities and a distinct localized_model_entailment basis; schemas 1 and 2 remain readable. The registered confirmation preserved all eight authored gates and improved WiCE supported recall from 10.91% to 30.91% and F1 from 19.35% to 43.59%, with no fully unsupported accepts. It still failed the fixed precision, recall and balanced-accuracy gates, so verification remains opt-in. A pinned MIT MiniCheck DeBERTa capacity trial improved WiCE F1 to 65.97% and balanced accuracy to 75.01% with no fully unsupported accepts, but failed the fixed 90% precision and 60% recall gates. No frozen-score threshold meets both, so that model is not integrated as a standalone verifier. The registered MIT MiniCheck Flan-T5 trial raised WiCE recall to 72.73%, F1 to 74.77%, and balanced accuracy to 81.52% with no fully unsupported accepts, but failed the 90% precision gate on 24 partially supported compound claims. No threshold meets both gates. A preregistered follow-up applied the unchanged model to all 958 WiCE human subclaims and required every subclaim to pass. Gold decomposition reconstructed 357/358 parent labels, but model errors limited parent precision to 76.04% and recall to 66.36%; no frozen threshold met both gates. This validates atomic decomposition as an architecture boundary on the named cohort while rejecting this verifier for integration. A fresh registered 100-case SummEdits trial tested a 675B reasoning model with exhaustive atoms and exact proof quotations across ten domains. It failed at 75.00% precision, 6.00% recall, 52.00% balanced accuracy and 23.00% quote integrity. The checks safely rejected paraphrased or invented proof, but the model also decomposed facts from the document instead of only the summary. This rejects free-form proof copying and motivates a separated, segment-ID protocol. The same-cohort causal v2 split summary-only decomposition from fixed-atom document verification. Reference integrity rose from 23.00% to 99.00%, supported recall from 6.00% to 72.00%, and balanced accuracy from 52.00% to 76.00%. It still failed safety at 78.26% precision and a 20.00% false-support rate, chiefly on entity changes and antonym swaps. A different-family DeepSeek critic over all 46 primary support candidates reduced false support to 8.00% but left precision at 78.95% and collapsed recall to 30.00%. A balanced 1,100-claim LLM-AggreFact development trial then rejected direct FactCG classification: the best threshold retaining 60% recall reached 79.44% precision, while 90% precision retained only 28.73% recall. A registered FactCG-ranked local-evidence Mistral cascade raised the corresponding operating point to 85.12% precision at 63.45% recall, but 90% precision still retained only 36.55% recall. Seven structured responses remained fail-closed, and the bound test cohort remains untouched. No provider verifier is integrated.

  • Added an optional, provider-agnostic AnswerabilityEvaluator for grounded abstention after retrieval. It decomposes compound questions and optional draft answers into independent requirements, resolves cited compact refs or default auditable UUIDs to exact packed memory IDs, fails closed on missing or invented citations, and derives answerable, partial, insufficient, or conflicting in code. Empty bundles avoid a model call; provider failures raise explicitly; every result binds the prompt version and query/context/answer hashes, plus separate fixed-input and actual-output digests, without changing retrieval receipts or memory state. Its first registered 120-assessment BEAM development trial failed every promotion gate. A speech-act-aware same-cohort rerun with exact evaluator and machine-gate bindings also failed all five gates, including 2 unsafe full answers and stable actions on only 26/40 questions, so it remains explicit and opt-in. A registered frozen-draft follow-up also failed all four gates: it fully accepted 5/36 samples from incorrect drafts, fully accepted only 13/84 samples from correct drafts, produced 7 citation errors, and was action-stable on 22/40 questions. Its systematic failures separate temporal task-label disagreement from exhaustive-count verification and block automatic enforcement of the current single-call architecture.

  • Opt-in direct evidence projection lets extracted claims route retrieval while returning the complete active source passage. It replaces bounded exact evidence groups only after owner, scope, temporal, validity and epistemic checks, records replayable score inheritance, and reserves projected sources during packing. Receipt schema version 10 records the full disabled-by-default policy while versions 1–9 preserve their canonical bytes and disabled meaning. A registered all-top-50 development ablation improved event ordering to 2/2 but regressed three previously passing questions and reduced overall score to 11/20, so wholesale projection is explicitly rejected as a default.

  • Opt-in evidence augmentation preserves concise derived candidates while adding bounded direct sources under the same owner, scope, temporal, validity, lifecycle and epistemic checks. Projection and augmentation are mutually exclusive. Receipt schema version 11 records augmentation settings and exact score inheritance; versions 1–10 retain disabled semantics. A registered tuned BEAM ablation improved the accepted result from 13/20 to 14/20 with one pass-level win, zero losses and a +0.02916 mean-score delta. A preregistered untouched confirmation then scored 14/20 versus the fresh baseline at 15/20, with zero wins, one contradiction-resolution loss and a -0.03375 mean delta. Unconditional top-10 augmentation remains opt-in and is rejected as a default. A new evidence_augmentation_anchor_policy="non_entity" mode prevents entity name matches from authorizing whole-passage injection and backfills the quota from non-entity evidence groups. Receipt schema version 12 records the policy; versions 1–11 preserve all semantics and their canonical bytes. A registered two-conversation diagnostic fixed the observed contradiction regression but produced one win, one temporal loss and a -0.03000 combined mean delta, so the policy remains opt-in and is not a default candidate. A preregistered frozen-context follow-up rejected a stable interpretation of that temporal loss: the identical baseline context passed 3/5 repeats, the candidate passed 4/5, and both mean scores were 0.40. The BEAM integration now includes an answer-stability runner that pins both retrieval artifacts and alternates repeated answer/judge calls without retrieving again.

  • A registered fixed-pack BEAM ablation found that a hard one-node cap per exact evidence set held pass accuracy at 13/20 but reduced mean rubric score from 0.56750 to 0.54833, with one pass-level win and one loss. The failure blocks a default cap and motivates the source-preserving projection policy.

  • The corrected schema-5 extracted-memory BEAM development run completed at 13/20 pass (65.0%) with a 0.56750 mean rubric score. Fail-closed validation confirmed 188/188 durable raw materializations and 188/188 extractions. Exact passage diversity rose from a 15-of-50 median in superseded dev3 to 48-of-50, while median retrieval latency fell from 486.0 ms to 198.05 ms. Against the accepted raw profile it gained three pass-level questions, lost two and tied fifteen. This tuned one-conversation result does not establish a default or cross-product leadership.

  • The schema-5 extracted BEAM dev4 run is retained as rejected evidence. A 14,181-character source exhausted four 120-second DeepSeek extraction calls; the run was stopped during ingestion and publishes no score. Controlled probes on the exact saved source completed with Mistral Large 3 in 44.63 seconds and DeepSeek V4.1 Flash in 106.42 seconds, identifying hosted latency variance and the registered timeout as the failure boundary.

  • Python, HTTP, and MCP retrieval now accept optional max_per_source and max_per_evidence result-selection bounds. The first collapses only byte-identical passages with the same exact nonempty evidence set. The second can cap differently worded nodes that cite the same exact evidence set. Both fill the requested count from later groups, record every exclusion, leave nodes without evidence untouched, and remain opt-in pending broader answer trials.

  • The first scored extracted-memory BEAM execution produced 12/20 pass (60.0%) with a 0.51875 mean rubric score, but a later pack audit found only 66 of 188 raw-source materializations complete. The original validator checked all 188 structured extractions but missed the second durable work stream. The result is retained as superseded diagnostic evidence and is no longer accepted as a complete extracted-profile quality result.

  • Registered BEAM evaluation can now attest scored extracted-memory runs. The schema-3 launcher binds and verifies the extraction model and settings before ingestion, launches the selected durable extraction profile, and rejects adapter-profile or extraction-config drift while preserving earlier raw registration semantics.

  • The first registered extracted-memory BEAM attempt is retained as rejected evidence: DeepSeek V4.1 Flash completed 8 durable extractions but failed the complete schema on a longer source after the registered retry budget. The run was stopped before retrieval and publishes no score.

  • The second registered extracted-memory BEAM attempt is also retained as rejected evidence. It completed 11 durable extractions before a stored Jinja code example exposed Instructor prompt-context evaluation ahead of the model. The run was stopped during ingestion and publishes no score.

  • The first fail-closed scored BEAM execution now provides complete answer-quality evidence for one 100K raw-memory conversation: 12/20 pass (60.0%) with a 0.49833 mean rubric score, 94/94 ingested chunks, 20/20 nonempty answers, and 53/53 complete judge verdicts. Registration binds the source, dataset, selection, endpoint, and Ollama manifests before execution. Three precursor attempts remain published as rejected evidence, including a completed upstream result that the validator rejected for six silent empty answers. The accepted cohort identifies abstention and broad summarization as the largest measured gaps and does not establish official-scale or cross-product leadership.

  • Registered BEAM evaluation now supports complete scored runs as well as the earlier predict-only workflow. Schema-2 registrations bind distinct local answerer and judge models, full Ollama digests, endpoint, selection, concurrency, dataset, and runtime source before the first model call. The launcher and fail-closed validator reject model or protocol drift while preserving schema-1 predict-only artifacts unchanged.

  • AgentMemBench retrieval registrations now bind the exact Ollama judge model digest, endpoint, and generation/retry controls before execution. Verification checks both the saved harness arguments and the live model identity. The installed pinned harness disables reasoning for its 32-token JSON decision and aborts after malformed output or exhausted retries instead of silently scoring judge failures as retrieval misses. The first registered 100-record Qwen 35B development run completed at 95/100 recall@5 with a 90% to 99% bootstrap interval and 100/100 durable materializations. The preregistered official-size confirmation scored 979/1,000 with a 97.0% to 98.7% interval, including 98.8% personal-fact and 97.0% task-request recall, with all 1,000 writes durably materialized.

  • Current-state scoring now recognizes the newest record when it explicitly presents itself as an update and applies a configurable, relevance-capped, replayable multiplier. A 100-pair AgentMemBench development diagnostic moved new-fact-first retrieval from 20% to 100%; the default 1.30 multiplier remains provisional. Receipt schema version 9 records configured and applied values, while versions 1–8 preserve their canonical bytes and disabled semantics.

  • Atomic ingest_fast_many() raw-source admission across the asynchronous engine, synchronous client, HTTP, MCP, DuckDB, and PostgreSQL. One owner is bound to the complete ordered batch, every item is validated before I/O, and all immutable events and restart-safe materialization jobs commit together or roll back together. An optional owner-scoped request UUID makes exact retries return the retained event IDs after restart and rejects changed inputs. MemoryAgentBench adapter schema 10 uses this path with a stable request UUID and drains the existing bounded materialization queue before publishing a pack.

  • Opt-in deterministic two-stage episode routing for sources stored under meaningful session boundaries. It uses BM25 to route candidate-backed (scope, session_id) groups, promotes a bounded local evidence set with replayable score provenance, and reserves that evidence during packing. It is disabled by default while matched answer trials test the EventQA hypothesis. Receipt schema version 8 records the complete policy while versions 1–7 retain their canonical bytes and disabled semantics. MemoryAgentBench schema 8 can preserve upstream source chunks as distinct registered episode partitions. New execution descriptors also hash the episode-routing module alongside the other retrieval stages.

  • The registered EventQA episode-routing development trial scored 19/20 versus BM25 at 20/20 while using 90.48% fewer retrieved-context tokens. Against the earlier flat-session PRME arm on the same 20 questions and reader, it recovered three of four failures with no paired losses and essentially unchanged context size and query time. The one remaining miss contained the strongest routed evidence but not the answer verbatim. This supports the technique on the named slice. Registered follow-up arms preserved Banking77 at 20/20 and improved DetectiveQA from 13/20 to 15/20, with every receipt verified and no context expansion. The default remains disabled pending a larger cohort and another reader.

  • MemoryAgentBench registrations now bind the complete retrieval source map reported by execution descriptors. Verification compares every durable receipt with that frozen map, rejecting runs whose adapter came from the declared revision but whose PRME runtime resolved from another checkout.

  • The MemoryAgentBench installer now upgrades its previously generated reader configuration block in place. Reinstalling after a new reader contract no longer leaves both the old and new validators in agent.py; managed markers make subsequent installs idempotent, and ambiguous edits still fail closed.

  • MemoryAgentBench DetectiveQA trials can register a shared choice-only-v1 reader contract. It resolves the pinned task prompt’s conflict between an example JSON reasoning object and its option-text exact-match reference by requiring one A. choice text line in both arms. Registration, runtime initialization and verification reject the contract outside DetectiveQA, and saved model output remains untouched.

  • The matched DetectiveQA development trial completed on 20 questions under that contract. PRME and BM25 each scored 13/20, with one paired win and one loss. PRME used 90.52% fewer retrieved-context tokens and 89.27% fewer reader input tokens. Two PRME responses and three BM25 responses contained a reference answer but violated the exact format; saved outputs were scored without rewriting. The verified tie completes a first matched development pass across all four MemoryAgentBench competency families, with task-specific gaps and no general leadership claim.

  • A matched MemoryAgentBench Conflict Resolution development trial completed on 20 questions under the shared answer-only-v1 contract. PRME scored 1/20 and BM25 scored 0/20. Post hoc inspection found reference-answer text in 19 PRME contexts and all 20 BM25 contexts, isolating the Qwen 9B reader and task interpretation as the dominant failure. A separately preregistered check with the installed Qwen 35B A3B reader produced the same 1/20 and 0/20 scores, ruling out model size alone as the remedy. Both verified negative results are published without treating the single paired win as evidence of useful conflict-resolution quality.

  • MemoryAgentBench trials can register a shared answer-only-v1 reader contract for strict-answer tasks where a local OpenAI-compatible reader otherwise adds explanations. PRME and BM25 receive the same prompt instruction, registrations bind it before inference, and saved model output is never parsed or rewritten.

  • Real extraction diagnostics can now run the same qualifier and entity-reference probes through either Ollama or OpenAI. Checked worker processes forward the provider explicitly and record it in the report, enabling provider-diverse validation without changing the cases or weakening grounding checks.

  • MemoryAgentBench ICL trials may opt into a registered numeric-label-v1 reader contract shared by PRME and BM25. It resolves the pinned harness’s conflict between a label: N prompt and its documented digits-only exact-match metric by appending the same explicit instruction to both arms. Retrieval queries and raw model outputs remain unchanged, and manifests and captures bind the selected contract. The paired comparator now rejects cross-arm contract drift and derived retrieval-query drift, and reports the matched contract with the other reader settings. The matched BM25 path uses the same terminal ICL question extractor as PRME, with both derived queries bound before inference.

  • The first corrected matched MemoryAgentBench answer trial completed on 20 Banking77 development questions: PRME scored 20/20 versus BM25 at 17/20 under the official strict metric while using 90.52% fewer retrieved-context tokens. Both independent verifiers and the strengthened paired comparator accepted the exact artifact chain. The paired interval includes zero, and PRME’s 5,897-node pack took 467.549 seconds to construct, so this remains bounded development evidence and exposes typed bulk ingestion as the next performance target.

  • The matched MemoryAgentBench EventQA development trial completed on 20 questions with independently verified PRME, BM25 and paired artifact chains. PRME scored 16/20 versus BM25 at 20/20 while using 90.48% fewer retrieved-context tokens. All four PRME misses omitted the reference answer from the packed context even though answer-bearing records remained in the durable store; flat limit and token-budget increases did not recover them consistently. This records episodic context reconstruction as a quality gap and preserves the negative result rather than treating compression as success.

  • MemoryAgentBench verification now authenticates each claimed durable retrieval receipt directly from the completed DuckDB pack, including its checksum, owner/request identity, replayable ranking, context hash, packing contract, scope, and included count. Reports commit an aggregate receipt hash instead of trusting the adapter’s persistence flag. Registration and both verifiers require their executing module bytes to match the frozen source they report.

  • MemoryAgentBench source ingestion now preserves blank-line units and serial-numbered facts as independent records, splitting only an overlong unit at the configured hard limit. Test-time-learning retrieval embeds the terminal classification question instead of its repeated reader instructions. Versioned manifests, preregistrations, captures, and durable receipt checks bind both policies and the exact derived retrieval query.

  • Session-context expansion now promotes adjacent turns even when broad vector or lexical generation already placed them in the candidate pool. Previously, those candidates were skipped before packing, making the default session window a no-op on sufficiently complete candidate sets. Inherited scores keep their trigger and decay in replayable receipt provenance. Built-in DuckDB and PostgreSQL stores fetch exact bounded neighborhoods, so older hits in sessions longer than 2,000 nodes are no longer silently missed; reused session IDs remain partitioned by exact memory scope.

  • Contract-correct LangChain and LlamaIndex chat persistence over the immutable event log. Versioned control events make clear, replacement, and deletion visible to framework callers without erasing source messages or creating retrievable control nodes. The adapters page beyond 1,000 events, isolate exact scopes, preserve structured message payloads, and now run against the locked current framework releases in a dedicated CI job.

  • Automatic adjacent question-answer pairing now includes exact scope in its session cache identity. A personal turn followed by a project turn can no longer produce a combined cross-scope memory, and chat-history control events reset the applicable cache before later messages are stored.

  • A registered, fail-closed MemoryAgentBench BM25 control for matched-reader trials. It binds the exact prepared sources, formatted BM25 documents, questions, answers, harness code, configuration, NumPy and rank-bm25 versions, and ranking source hash before inference. The verifier reconstructs every BM25 ranking and rejects missing, stale, reordered, or altered captures. Explicit retrieval run IDs isolate scratch outputs, while a fixed registered memory timestamp removes wall-clock text from otherwise reproducible indexes.

  • Explicit MemoryAgentBench reader reasoning and seed controls shared by the PRME and pinned BM25 request paths. The installer validates both settings, forwards the task generation limit for non-OpenAI model names, and the PRME adapter binds the values into its pack identity and retrieval captures. This prevents a local common-reader comparison from silently using different thinking or sampling behavior between memory arms. Registrations also bind the dataset, NLTK and tiktoken versions, the English Punkt data, and the exact tokenizer table so preprocessing dependency drift cannot silently change task chunks. The canonical PRME agent name now selects the same upstream RAG question template as BM25 while still dispatching to the PRME adapter, removing a reader-prompt confound from matched trials.

  • Isolated MemoryAgentBench PRME state through a validated experiment slug. prme_run_id participates in the saved-agent path, pack identity, retrieval capture, and verifier so multiple configurations using one reader cannot reuse or overwrite each other’s memory packs.

  • Opt-in compact packed context for short-record workloads. It replaces repeated JSON keys and full in-prompt UUIDs with one declared array schema and deterministic bundle-local references while retaining type, scope, epistemic state, lifecycle, provenance, representation, temporal fields, and complete selected text. MemoryBundle.context_references and resolve_context_ref() map citations back to full node IDs. Exact token accounting covers the complete output, and receipt schema version 7 records the format while versions 1–6 preserve their canonical bytes and historical auditable semantics. The auditable format remains default.

  • A registered 149-question LongMemEval-V2 4K-budget development study. PRME scored 49/149 versus 10/149 without memory, while mean reader memory context fell 83.05% from the earlier 32K arm. The same-cohort PRME score fell from 80/149 to 49/149, so the result preserves the 4K preset as explicit and makes intermediate-budget answer retention the next gate rather than supporting a quality-default change.

  • Explicit LongMemEval-V2 renderer selection. The adapter accepts and reports context_format, the full preset declares auditable, and the compact preset now declares compact so future runs cannot confuse a 4K budget with compact serialization. The registered comparator reports the effective format and rejects a declared format that differs from the saved configuration.

  • A pinned MemoryAgentBench adapter for the benchmark’s four incremental memory competencies. It uses the public PRME client with the product-default 4K balanced packer, pins the upstream dataset revision, fences completed source ingestion for restart, and saves exact retrieved contexts and request identities. Task-qualified state paths prevent cross-suite pack reuse. The installer is idempotent, rolls back partial changes, and preserves list-valued references across upstream resume. Outcome-free registration hashes every prepared source, query, answer, context assignment, configuration and harness file; post-run verification requires exact registered inputs, complete metrics, bounded recounted contexts, durable receipts, and completed source manifests. Matched BM25 registrations bind preprocessing, ranking, and LangChain wrapper dependencies; the installer updates the pinned harness to the current BM25Retriever.invoke() API. A paired comparator accepts only exact registrations, verified-complete arms and result hashes, applies each task’s official accuracy metric, and reports paired uncertainty without raw answers. Both arm verifiers accept the harness’s native boolean match metrics while retaining finite numeric validation for graded metrics. This is an evaluation path; task results retain their individual claim bounds.

  • Durable, atomic hierarchical summary publication. Daily, weekly and monthly excerpts use stable owner/scope/level/period lineages, deterministic request generations, checksummed preparation, fenced index staging and transactional graph publication. Unchanged runs reuse one active identity; late selected sources atomically replace and archive the prior generation; interrupted runs recover without re-embedding; concurrent engines converge; and the first managed run atomically retires legacy excerpts for the same period. Stable-ID paging removes the prior 5,000-node daily and 1,000-summary rollup caps while keeping unscoped scans an explicit internal operator action.

  • Immutable owner/exact-scope ranking profiles with a two-stage activation gate. A profile must bind a positive explicit-feedback proposal to a separately positive full-retrieval holdout before it can be persisted and activated. Retrieval applies compatible profiles per request, records their identity and status in metadata and receipts, and safely falls back on feature or base-score drift. Append-only, retry-safe activation, deactivation and rollback work across DuckDB and PostgreSQL and are exposed through Python, HTTP and MCP. Serialized evaluations revalidate their coverage, aggregates, decisions and full-retrieval input identity; the query path reads a compact checksummed activation record rather than reparsing the complete evidence report.

  • A conservative full-pipeline retrieval holdout evaluator for learned ranking proposals. It compares separately executed baseline and candidate receipts, requires complete caller-supplied relevant-node sets, verifies identical owner, scope, clock, filters, limits, scoring, packing, execution parameters, and feature identity outside the declared multipliers, and reports grouped recall, NDCG, MRR, bootstrap uncertainty, regressions, and immutable input identities. Positive observed-candidate learning alone cannot activate a profile.

  • A preregistered MELT lifecycle launcher and fail-closed report validator. The launcher pins the held-out final profile, five-seed schedule, source commits, adapter and upstream file hashes before execution. Validation uses MELT’s own report loader and independently requires final status, exact protocol and SUT identity, five complete runs, all case checkpoints, and retained case I/O.

  • Opt-in prme doctor --verify-extraction provider diagnostics. It verifies endpoint reachability, hosted credentials, and the configured model through read-only model metadata APIs without generating or storing content, and reports sanitized status categories without response bodies or URLs. It explicitly leaves generation quota unverified.

  • Explicit half-open validity intervals for direct writes across async/sync Python, HTTP, and MCP. New extracted facts start validity at their resolved source-effective time, and a source-grounded replacement closes the prior interval atomically when doing so cannot invert a legacy interval. Invalid or timezone-free intervals fail before source admission; historical derivation plans retain their original bytes and behavior.

  • Exact temporal assertion state across async/sync Python, HTTP, and MCP. A required owner, scope, structured subject/predicate, and timezone aware validity instant produce auditable current candidates plus stored history, supersedence, contradiction, epistemic, lifecycle, clock, and evidence data. The operation preserves differing unresolved values and never selects truth from recency; historical knowledge cutoffs retain an explicit non-replay boundary.

  • Exact decimal quantity aggregation across Python, HTTP, and MCP, with totals, minima, maxima, counts, temporal bounds, and source/evidence samples. Every group must retain its normalized unit, stored quantities are revalidated at read time, incompatible units are never mixed, and scaled-integer addition avoids decimal-context rounding.

  • Source-grounded decimal quantities for extracted facts. Built-in and custom grounding retain a quantity only when its exact decimal, quantified phrase, and verbatim unit occur in the claim object and evidence. New derivation plans version the metadata policy while historical plans and checksums remain unchanged; invalid optional quantities do not discard otherwise valid claims.

  • Exact owner-scoped assertion aggregation across Python, HTTP, and MCP. It counts every matching structured claim for an unchanged store, groups normalized subject/predicate/object/polarity values, retains bounded evidence samples and time/epistemic filters, and distinguishes stored-set completeness from extraction, semantic, and real-world completeness.

  • A fail-closed operational benchmark for p50/p95/p99 retrieval latency at declared pack sizes, exact fixed-clock repeatability, and 10,000-query owner isolation through public store/retrieve paths, with runtime and execution provenance in its artifact.

  • HTTP and MCP access to the existing bounded, owner-scoped offline learning evaluator. Both remote surfaces return the complete validation, uncertainty, coverage, exclusion, and input-identity report without activating weights.

  • Owner-required, cursor-based stored-record enumeration over HTTP and MCP. GET /v1/nodes/scan and memory_scan_nodes expose scope, type, and lifecycle filters, immutable UUID ordering, explicit has_more/next_cursor fields, and page-level consistency so remote exports and counts do not depend on a bounded top-k query.

  • A pinned BEAM benchmark service with source-only ingestion boundaries, owner isolation, source-time query clocks, restart-safe request idempotence, and separate raw and durable-extraction profiles. Its fail-closed validator rejects the upstream runner’s otherwise silent failed-chunk and empty-model completion paths and hashes every accepted artifact. A registered raw retrieval launcher additionally binds the clean PRME and upstream revisions, exact source files, normalized dataset cache, run identity, profile, and complete question selection before ingestion; validation checks the same registration, execution, dataset, and adapter identities.

  • Structured store_with_receipt() results on async and synchronous Python. They expose the durable event, exact created node, and materialization status in one call while preserving the historical store() event-ID return. HTTP and MCP store responses now use the same provenance-based receipt path and expose direct materialization status.

  • An official LongMemEval-V2 adapter for agent-trajectory memory. It preserves ordered state and transition semantics, creates compact hierarchical procedure traces alongside raw states, bounds downstream context items, returns source screenshots, fingerprints portable adapter state, and rejects changed or interrupted trajectory inserts. A pinned one-question web-small pipeline smoke covers the full 100-trajectory save/load path. Its installer verifies the pinned upstream revision, copies the adapter and configuration atomically, registers the backend, and safely handles repeat installation. A companion launcher durably checkpoints each reader response, preserves exact prompt rows across retries, rejects configuration drift, and supports explicit reasoning_effort: none for Ollama development runs. Schema 3 packs persist a stable retrieval reference clock; schema 2 packs derive it without mutation and remain read-only, preventing saved-run ranking drift across wall-clock dates. Registered paired comparisons now bind the PRME and no-memory arms to their declared configuration paths, context budgets, frozen saved-memory configuration and pack manifest; a baseline that returns memory context fails validation. New schema-2 registered runs also write a launch-time execution manifest that binds both arms to clean PRME and pinned upstream revisions and hashes the exact launcher, installer, adapter, configuration, and harness; changed-source resumes and retroactive attribution fail before generation.

  • The first preregistered held-out LongMemEval-V2 web-small answer comparison completed all 149 deterministic questions for PRME and the official no-memory adapter. With one fixed local Qwen 9B reader, PRME scored 80/149 versus 10/149, with 75 paired wins, 5 losses and a +37.58 to +55.70 point bootstrap interval. The fail-closed comparator verified both arms, official scores, source inputs, reader settings, saved pack/config bindings and the baseline’s zero-memory contract. The result used a mean 43,195 memory-context tokens and remains a named-cohort memory-utility result rather than a competitive leadership claim.

  • LongMemEval-V2 registered execution manifests now bind the configuration actually selected for each arm, including its launch-time hash, instead of attesting only the installed default. The installer atomically supplies both the 32K maximum-evidence profile and an explicit 4K product-default compact profile. Before opening loaded memory, the launcher also hashes every regular file by relative path, size and content and rejects symlinks; the comparator requires the complete initial artifact and both per-arm configuration identities for new schema-2 studies.

  • A matched raw-store versus real ingest() evaluation profile for LongMemEval source evidence. Extracted runs freeze the local model digest and full configuration before generation, require durable extraction completion, report source/node materialization coverage, and support an explicit paired profile comparison without presenting source lineage as answer accuracy.

  • Optional, token-counted context guidance with a non-displacement guarantee. The packer includes guidance only when it fits after record selection, exposes whether it was included, and preserves it through controlled ablation.

  • Exact, nonmutating packed-context ablation and citation-checked presence credit. Applications can re-answer after removing one cited memory, preserve both context hashes and token accounting, and distinguish load-bearing, redundant, misleading, and noncuring outcomes without changing retrieval, ranking, or retention.

  • Immutable, owner-scoped answer citation records across async/sync Python, HTTP, MCP, DuckDB, and PostgreSQL. Citations bind to saved content-bearing context, preserve optional answer digests, and use caller UUIDs for exact retry safety without changing memories or ranking.

  • Durable retry IDs for promotion and archival across sync/async Python, HTTP, and MCP. HTTP uses Idempotency-Key; conflicting key reuse returns 409.

  • Atomic, checksummed explicit corrections across sync/async Python, HTTP, MCP, DuckDB, and PostgreSQL. State, deterministic edge, and complete before/after audit record commit together; exact retries remain safe after restart.

  • A complete tenant-scoped contradiction lifecycle on sync/async Python, HTTP, and MCP. Marking and resolution atomically update claims, edges, and audit records; exact retries are idempotent across restarts and resolution evicts the deprecated claim from derived search indexes.

  • Atomic, evidence-aware condition evaluation across Python, HTTP, MCP, DuckDB, and PostgreSQL. Retry IDs are durable, state changes retain checksummed before/after records, and confirmed conditions receive asserted retrieval weight without losing their conditional classification.

  • MCP memory_store parity for role, session, metadata, confidence, epistemic type, and source type, allowing agents to create qualified memories directly.

  • Tenant-scoped node provenance on sync/async Python, HTTP, and MCP, including owned source events, explicit missing-evidence signals, valid contradiction links, and bounded chronological operation pages with opaque cursors.

  • Explicit aggregation coverage on Python, HTTP, MCP, durable retrieval records, and packed model context. Natural-language counts/lists report candidate, selection and context truncation without claiming exhaustive enumeration.

  • A reproducible 8K-context Ollama profile for qwen3.5:35b-a3b, with two successful strict extraction reports and an equal-context 9B comparison.

  • Typed claim polarity and exact explicit conditions for built-in LLM extraction. Conditional claims retain an auditable state and stay out of default retrieval until confirmed.

  • MemoryWorkspace and lease-scoped NamespaceMemory for named local projects, with stable pack identity, bounded idle-engine eviction, shared embeddings, process ownership and cancellation-safe lease cleanup.

  • MemoryWorkspace.open_postgres() for named PostgreSQL projects sharing one bounded connection pool. Identity checks and a private-only search path protect routing; registry and schema creation publish atomically. Hosted grants remain separate. pgvector symbols now resolve explicitly even outside public.

  • Optional duckdb_threads / PRME_DUCKDB_THREADS control for each open local database. The default preserves DuckDB’s setting; conflicting concurrent opens fail without reconfiguring the active pack.

Changed

  • Supplementary aggregation and entity-focused lexical searches now normalize their BM25 scores before composite ranking. Unbounded raw Tantivy scores can no longer overwhelm semantic, graph, recency, salience, and confidence signals or crowd relevant hybrid candidates out of a bounded result set.

  • Packing configuration now rejects negative candidate limits and graph depths outside the RFC’s 1-3 range. The legacy no-op chars_per_token and cross_scope_token_budget fields remain parseable for receipt compatibility, but non-default use warns that exact context tokenization and cross_scope_top_n are the active controls. Setting cross_scope_top_n=0 now skips the secondary hint search entirely.

  • The organizer registry now contains only implemented jobs. The unimplemented, unvalidated centrality_boost no-op is no longer advertised or run during default maintenance; explicit requests fail as an unknown job instead of returning a misleading success result.

  • Temporal reasoning guidance is now enabled by default when it fits after evidence selection. A registered 70-context confirmation improved temporal answers from 31/50 to 34/50. Personalization and current-state guidance remain experimental behind packing.context_guidance_mode="all"; "off" restores pre-guidance behavior. Receipt schema version 6 records the chosen mode while versions 1–5 preserve their canonical bytes and mean guidance was off. Elapsed-time questions no longer receive aggregation coverage warnings; time totals across records remain aggregation queries.

  • knowledge_at now exposes a machine-readable historical_coverage boundary in Python, HTTP, MCP, retrieval receipts, and the token-counted model context. It is explicitly an aware ingestion-time cutoff over current indexes; prior lifecycle and index state are not replayed. Naive datetimes fail immediately.

  • Balanced multi-path context packing is now the default. A complete fixed 119-question answer trial scored 83 correct with balanced contexts versus 67 with density. A separately registered 381-question answer confirmation scored 250 versus 185, with 88 paired wins, 23 losses, and no lower category total. Explicit density and score policies remain available, while historical receipts retain their original density meaning and checksums.

  • Current-state retrieval now recognizes ordinary present-tense state questions, while inferred recency reweighting requires explicit update evidence and dated temporal queries remain historical. Decisions and instructions receive the same default semantic-node boost as facts and preferences.

  • Structured extraction now sends a configurable sampling temperature, defaulting to zero, and rejects uncertain or contingent future actions mislabeled as completed decisions. Explicit known-negative updates can retire the same known-positive claim without guessing legacy polarity.

  • PostgreSQL now honors the default vector_exact_search=True, materializing eligible rows before distance ordering to prevent filtered HNSW starvation. False explicitly permits approximate search. Broad exact queries may cost more; new receipts report the requested mode.

  • Default organizer passes exclude the legacy global feedback tuner; session completion runs promotion only. Explicit scoped feedback_apply requests now raise ValueError before any work. Trusted operators can retain the legacy behavior with explicit organize(jobs=["feedback_apply"]) and no user scope.

Fixed

  • Extraction workers now immediately reclaim an expired, uncontested lease once before falling back to scheduled retry. Event-loop starvation after a provider response can no longer leave durable work indefinitely in running state; a lost reclaim race or expired inline recovery also schedules the ordinary durable retry, while generation fencing prevents stale publication.

  • Forgotten synchronous clients now close before Python shuts down its shared thread-pool executor, allowing vector and lexical indexes to flush cleanly at normal interpreter exit without late-executor errors.

  • Historical assistant and system events are now submitted to extraction models as input text while retaining their stored source role for provenance and epistemic typing. Role-aware admission guidance prevents local chat providers from returning an empty continuation and avoids promoting generic assistant recommendations, explanations, and examples into durable claims.

  • Extracted source-lineage benchmarks now complete and map the durable raw NOTE path before retrieval. Reports distinguish raw source nodes from derived semantic coverage, so an intentionally empty extraction remains retrievable as episodic evidence instead of disappearing from evaluation accounting.

  • Mixed-quality structured responses now retain source-supported claims while dropping malformed or unsupported proposals. A response with no admitted claims completes with an empty claim set while preserving the raw event and any grounded named mentions; missing or ambiguous named references on admitted claims still enter bounded validation retries. Condition and uncertainty validation is scoped to the cited source sentence and directly following qualifiers, preventing unrelated language elsewhere in a paragraph and indirect questions such as “see if” from contaminating the claim. Polite request language such as “could you help” is also excluded from claim modality checks while real possibilities remain hypothetical. Model overreach therefore reduces derived recall instead of failing ingestion availability.

  • Ollama structured extraction now defaults reasoning_effort to none, with an environment and typed-config override. This prevents thinking traces from exhausting the same context window needed for the validated response. Ollama also uses constrained JSON output, avoiding rejection of valid JSON that lacks a tool-call envelope.

  • Built-in extraction now accepts source-grounded literal personal references such as I and we without requiring them to be named entities. Each unlisted reference receives one provenance-bound identity within its source event and cannot merge across messages; missing named entities and unsupported citations remain validation errors. Provider citations that differ only in straight or curly quote marks are canonicalized back to the exact source span.

  • Recreate a loaded empty USearch index before its first new insertion, avoiding a reproducible native crash after deleting the last vector, closing, reopening, and storing another memory.

  • Make greedy consolidation clustering and source selection stable across equivalent histories with different generated UUIDs. Singular relational state questions use semantic answer-class relevance so generic subject words do not swamp the requested relation.

  • Make extractive consolidation publication durable and idempotent. Unchanged clusters reuse one summary; changed source snapshots atomically publish a new generation with its provenance edges and archive the predecessor. DuckDB journals and fences external index staging for restart and multi-engine recovery; PostgreSQL serializes concurrent publications through the generation head.

  • Keep unresolved personal references local to their source event during new ingestion. Historical prepared derivations retain their original replay policy.

  • Prevent organizer similarity matches from merging different claim text, validity or provenance; semantic entity aliases remain unverified links.

  • Initialize PostgreSQL vector columns and indexes against the selected table, without treating names in other schemas as an existing local installation.

  • Preserve relationship validity and provenance during organizer merges; failed copies keep the source active, and deterministic copy IDs make retries converge.

  • Publish organizer evidence unions, relationship copies, retirement and a single supersedence edge atomically, with a checksummed operation record and scoped, idempotent retries. Concurrent PostgreSQL merges lock shared nodes before reads.

0.11.0  - 2026-09-11

Added

  • Tenant-scoped organizer execution through organize(user_id=...) and prme organize --user-id, with cross-owner merge guards.

Benchmark measurement

  • A registered exact-context ablation over all 37 development questions with one annotated source in the balanced bundle reduced the fixed reader from 29/37 to 7/37 correct. Twenty-three correct answers became wrong. The audit retained six redundant non-flips, seven noncuring reader errors, and one benchmark-reference inconsistency rather than converting them into negative memory labels.
  • Preserve per-question evaluation failures in reports and retry selection.
  • Report scored coverage and whole-benchmark failures; weight summary accuracy by measured questions and fail the CLI when any repeated run is incomplete.
  • Propagate abstention-provider failures during evaluation instead of treating the application’s fallback as a measured verdict.

Fixed

  • Report the package version consistently in REST metadata and MCP resources.

  • Enforce node ownership for engine operations that accept node IDs.

  • Reapply scope, bi-temporal, and epistemic filters after late retrieval stages.

  • Serialize organizer SQL through the shared DuckDB connection lock.

  • Restore CI coverage for main, optional API/MCP dependencies, simulations, and live PostgreSQL tests on supported Python versions.

  • Disable redundant Tantivy background reader reloads so a closed lexical index cannot recreate metadata lock files during pack cleanup or movement.

  • Expand vector searches when tenant, scope, lifecycle, or temporal filters leave fewer than the requested number of distinct memory nodes.

Changed

  • Schedule opportunistic organizer passes in the background and drain them on close.
  • Pin dateparser language and gate temporal parsing to reduce retrieval overhead.
  • Remove dataset observations, answer-revealing prompt examples, and benchmark-only query expansion from real-data evaluation.
  • Replace stale accuracy headlines with a measurement contract and reviewed roadmap.

0.10.0  - 2026-07-15

Added

  • Deterministic vector search + prme rebuild (issue #45) — Reproducible vector retrieval and full reconstruction of derived indexes (vector, lexical) from the durable event/graph store via prme rebuild
  • Multi-query reformulation (issue #43) — Opt-in retrieval mode that expands a query into multiple reformulations for broader candidate generation

Changed

  • REST API hardening (issue #34) — Authentication, loopback-only bind by default, CORS restrictions, and error-message sanitization
  • Encryption-at-rest lifecycle hardening (issue #37) — Tightened key handling and encrypt/decrypt lifecycle for memory pack files
  • Stored memory neutralized in LLM context (issue #36) — Retrieved memory is delimited and neutralized before entering the LLM context to prevent prompt injection from stored content
  • Ingestion index write path batched (issue #39) — Batched writes across the ingestion indexing path
  • Retrieval hot path DB round-trips reduced — Fewer database round-trips on the retrieval hot path
  • Benchmark measurement rigor (issue #44) — Pinned judge model, cached verdicts, and multi-run scoring for reproducible benchmark numbers

Fixed

  • Bedrock provider extraction (issue #59) — Pass model through to client.create() so the Bedrock provider works during extraction
  • Context formatter token budget + dedup (issue #42) — Enforce the token budget and deduplicate entries in the context formatter
  • Index eviction on supersedence/archival (issue #41) — Evict superseded/archived content from the lexical and vector indexes
  • Auto-promotion coverage (issue #40) — Auto-promote now reaches older eligible nodes, not just the newest batch

0.4.0  - 2026-03-16

Added

  • Bi-temporal data model (issue #21) — event_time field distinguishes when something happened vs when system learned about it; knowledge_at parameter on retrieve() for point-in-time knowledge snapshots
  • Encryption at rest (issue #14) — Transparent Fernet (AES-128-CBC + HMAC) encryption of memory pack files; PBKDF2 key derivation; encrypt on close, decrypt on create
  • Deduplication and entity alias resolution (issue #11) — Organizer jobs for vector-similarity-based duplicate detection (threshold 0.92) and alias resolution (threshold 0.85); merge logic with SUPERSEDES edges
  • Evaluation harness (issue #16) — Precision@k, recall@k, nDCG@k, MRR metrics; ground truth support in simulation checkpoints; 3 evaluation scenarios (factual, temporal, supersedence)
  • HTTP API layer (issue #17) — FastAPI REST API with endpoints for store, retrieve, organize, node operations, graph traversal, health, and stats
  • CLI tooling (issue #15) — prme command-line tool for memory inspection: info, nodes, edges, search, chain, organize, stats, export
  • Dual-stream ingestion (issue #25) — ingest_fast() guaranteed sub-50ms path (event store + vector only); materialization queue for deferred graph writes
  • Memory quality self-assessment (issue #24) — Feedback signal tracking, gradient-free weight auto-tuning, per-namespace scoring profiles, quality metrics
  • Procedural memory (issue #23) — INSTRUCTION node type for system instructions and procedural knowledge; Priority 0 packing into system_instructions section; epistemic inference support
  • Entity snapshot generation (issue #13) — generate_entity_snapshot() produces structured entity state views from graph neighborhood; snapshot_generation organizer job; simulation scenario
  • Predictive forgetting / consolidation (issue #22) — Semantic clustering of episodic memories; summary abstraction creation; redundant memory archival; consolidate organizer job
  • TTL-based archival (issue #12) — ttl_days field on memory nodes; per-type default TTL configuration; tombstone_sweep organizer job with operation logging; policy-based retention enforcement
  • Summarization pipeline (issue #10) — Hierarchical daily -> weekly -> monthly summarization; configurable thresholds; time-budget-aware processing; summarize organizer job
  • Benchmark suite (issue #26) — LoCoMo long-conversation benchmark, LongMemEval 5-ability evaluation, custom epistemic benchmark (supersedence correctness, confidence calibration, contradiction detection, belief revision, abstention quality)
  • Hybrid retrieval pipeline v2 — supersedence-aware scoring, lifecycle filtering (SUPERSEDED/ARCHIVED exclusion), query reformulation with LLM-generated alternative queries, session context expansion (top-20 with ±3 adjacent turns)
  • Context formatter — temporal annotations (days-ago, COMPUTED offsets), chronological sorting for temporal queries, relevance-ranked formatting with date annotations
  • Benchmark infrastructure — LLM-as-judge with configurable generation model, concurrent evaluation (semaphore-based throttling), resilient structured output for reasoning models

Fixed

  • Integration test fixture (issue #18) — Added examples/conftest.py with engine/log fixtures
  • DuckDB segfault in concurrent tests (issue #19) — Isolated DuckDB connections per test with conn_lock protection
  • GeneratedAnswer schema resilient to reasoning models (gpt-5-mini) that embed answers in reasoning field

0.3.0  - 2026-03-08

Added

  • engine.reinforce() method — bumps reinforcement_boost (+0.15, cap 0.5) and confidence_base (+0.05, cap 0.95), updates last_reinforced_at, appends evidence refs
  • Re-mention reinforcement in store() — opt-in via reinforce_similarity_threshold config; vector-searches for similar existing nodes and reinforces them automatically
  • Keyword-based supersedence detection in store() — opt-in via enable_store_supersedence config; ContentContradictionDetector with 10 regex patterns for migration/replacement language
  • Oscillation detection for flip-flop supersedence patterns — OscillationDetector using Jaccard keyword similarity on supersedence chains; applies confidence penalty (0.1 per cycle, cap 0.3)
  • update_node() method on GraphStore protocol and all implementations (DuckPGQ, PostgreSQL) for field-level node updates
  • Ranking assertions in simulation harness (SimCheckpoint.ranking_assertions)
  • Lifecycle assertions in simulation harness (SimCheckpoint.lifecycle_assertions)
  • Deterministic rebuild verification (SimulationRunner.run_deterministic_check())
  • Surprise-gated storage — opt-in via enable_surprise_gating config; NoveltyScorer computes novelty of incoming content against existing memory, boosting salience for novel content and penalizing redundant content
  • Four new simulation scenarios: reinforcement, remention, oscillation, surprise_gating

Fixed

  • promotion_evidence_count default aligned with store() behavior (default 1, matching the single evidence ref created per node)

Changed

  • store() pipeline now has 6 steps: event persistence, graph node creation, vector/lexical indexing, re-mention reinforcement (opt-in), supersedence + oscillation detection (opt-in), surprise gating (opt-in)

0.2.0  - 2026-02-27

Added

  • Self-organizing memory system (RFC-0015) with virtual decay, maintenance runner, and organizer jobs
  • Simulation harness for validating memory behavior without LLM dependencies
  • Decay mechanics: exponential salience/confidence decay with per-type decay profiles
  • Organizer jobs: promote, decay_sweep, archive, feedback_apply (plus stubs for future jobs)
  • Opportunistic maintenance during retrieve/ingest operations
  • Three simulation scenarios: changing_facts, decay_mechanics, information_accumulation

0.1.0  - 2026-02-19

Added

  • Append-only event store (DuckDB)
  • Graph-based relational model with typed nodes and edges
  • Vector index (usearch HNSW) with fastembed embeddings
  • Lexical full-text search (Tantivy)
  • Hybrid retrieval pipeline with deterministic scoring and context packing
  • Epistemic state model with lifecycle transitions and confidence tracking
  • LLM-powered ingestion pipeline (OpenAI, Anthropic, Ollama)
  • Entity merge and supersedence handling
  • Namespace and scope isolation
  • Optional PostgreSQL backend
  • Terminal chat example with persistent memory
  • Quickstart example

Source: CHANGELOG.md  in PRME at bd92166f.