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 toextraction_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_contextlists the memories that wrote the names, and no other claim of the message changes. It needs nothing else on, works iningest(),store(extract=True)andreingest(), and counts in the re-derivation settings. With the option off every extraction is unchanged. The measurements are inBENCHMARKS.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 fixedvector(N)column, had no way to switch at all (rebuild_indexes()raised while the mismatch error told you to runprme 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’svectors.usearchstays the first model’s), and anode_embeddingstable with one partial HNSW index per model on PostgreSQL (the reasons are indocs/EMBEDDING-MODELS.md).embedding.model_nameis the active model that searches read, and the newembedding.additional_models(PRME_EMBEDDING__ADDITIONAL_MODELS) are also written on every store, including whatingest(),reingest()and consolidation derive.MemoryEngine.reembed()(MemoryClient.reembed(),POST /v1/reembed, thememory_reembedMCP tool andprme 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,remainingandfailedas its progress, safe to stop and resume and to run while the store is read and written, and it calls no model service. Settingembedding.model_nameto a model whose index is complete is the switch, and the old index stays untildrop_embeddings()(prme embeddings drop);embedding_status()andprme embeddings statuslist 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 carryembedding_incompletewith the counts of memories without a vector (beside the existingembedding_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’snodes.embeddingvectors 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 rebuildstays DuckDB only, and its error on PostgreSQL now namesreembed.benchmarks.embedding_models jobtimes the job. A protected store (one under a privacy policy) refusesreembed(),embedding_status()anddrop_embeddings(), as it refusesrebuild_indexes()andembed_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 thefastembedmodel embeds, andembedding.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_repairis 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(orenable_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_repairis 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=falserestores 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, andreingest()reads every memory derived before this release as stale, because the repair request and its window are part of the re-derivation settings; onereingest()makes them current again. - New and off by default, with no effect until you set them:
enable_multi_model_embeddingsandembedding.additional_models(withreembed(),POST /v1/reembed, thememory_reembedtool,prme reembedandprme embeddings statusanddrop), and the two prefix optionsembedding.query_prefixandembedding.document_prefix. With the first on, a PostgreSQL store gets anode_embeddingstable and copies its existing vectors into it once, in resumable batches, leavingnodes.embeddingas it was.prme rebuildstill works on DuckDB only, and its error on PostgreSQL now namesreembed. 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
cryptographyto 50.0.2 and theprivacyextra 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_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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 carriesDERIVATION_VERSION, an integer insrc/prme/ingestion/reingest.pythat 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.pyderives a recorded extraction (a fixture undertests/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 morereingest()it is current under the new fingerprint and stays so across releases.CONTRIBUTING.mdandCLAUDE.mdsay 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 inBENCHMARKS.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 inBENCHMARKS.md.
Upgrade notes
- One change since 0.16.0a6, and it is not a default: the settings fingerprint of
reingest()now carriesDERIVATION_VERSION(1) instead of the package version. A source thatreingest()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 morereingest()it is current, and it stays current across later releases that leave what is derived as it was. Stores that never ranreingest()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
cryptographyto 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_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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 thedefects-v1,store-v1andchat-v1corpora (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 withstore()and ranks every question byretrieve(), 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, acomparecommand and afidelitycommand 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. SeeBENCHMARKS.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 inBENCHMARKS.md. -
Add
enable_owner_window_extraction(PRME_ENABLE_OWNER_WINDOW_EXTRACTION, off by default; dwamianm/prism#345 and #347): withenable_windowed_extraction, a turn with nosession_idgets 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-v1and the fast cycle’singestverdict), 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, andchat-v1fell 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. Whileenable_windowed_extractionorenable_fact_text_resolutionhas 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 acceptsbefore, 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 acceptuntil, 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 withingraph_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 isrrf_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 thedefects-v1chat 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 asgraph_seed_reach: backend_errorand keeps every graph candidate. - Upgrading: if you set
PRME_SCORING__RRF_GRAPH_WEIGHTfor the hop count list of 0.16.0a4, setPRME_SCORING__RRF_GRAPH_SEED_COUNT=falsetoo to keep it; otherwise that weight now drives the seed count list.falserestores 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.
- Limits: only a record the graph search keeps among its candidates
(
-
Add explicit evidence capture, retention and admission policies (dwamianm/prism#342, issue #320), opt in.
PRMEConfig.privacy_policy(an immutable policy, default none) or apolicyon one request ofstore(),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 andreingest()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 ofstore(),ingest()andretrieve(). Opening a store now also creates an empty tableprivacy_admissions(CREATE TABLE IF NOT EXISTS, DuckDB and PostgreSQL), and a few legacy entry points (fast ingest, adapter control events andreingest()) 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
privacyinstallspresidio-analyzerandspacy; the modelen_core_web_sm3.8.0 is not part of it, because PyPI does not accept a direct URL dependency. Install it from its release (seedocs/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-keysprovisions and diagnoses local keys, and the optional extralocal-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_extractionorenable_fact_text_resolutionis 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 namedWethat 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 auserturn keeps its literal “We” and its group subject, and gains agroup_memberlink 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 innamed_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. Withenable_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).falserestores 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
cryptographyis 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 anothercryptographyrelease cannot install this release next to it. The optional extralocal-privacypinskeyringto 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_linksis on (PRME_ENABLE_OWNER_GROUP_LINKS=false) andscoring.rrf_graph_seed_countis on (PRME_SCORING__RRF_GRAPH_SEED_COUNT=false). Neither rewrites a stored memory. With the second on, aPRME_SCORING__RRF_GRAPH_WEIGHTyou 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_weightand the other options that were off in 0.16.0a5 stay off. The defaults turned on in earlier alphas are still on (lexical_term_weightsfor PostgreSQL,enable_ingest_claim_fixes,packing.folded_records_keep_rank,enable_mention_links,packing.graph_seed_words,enable_owner_name_carryandpacking.session_context_skip_entities). - A store gains two empty tables,
privacy_admissionsandprivacy_rotation_jobs, the first time this release opens it, andreingest()and a few other entry points read it (see the privacy entry). Code from earlier releases does not use the table. - Install:
cryptographyis 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_counton (travel 2 to 3, coffee 4 to 5, job-now 7 to 8), and thereingest()limits of 0.16.0a5 stand. - Known flaky tests:
tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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 measuresgate-compareheadlines, by resampling whole conversations with the bootstrap the answer track and the mid tier use. The scheduled full benchmark runs it after each LoCoMogate-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 inCLAUDE.mdasks. - Add
MemoryEngine.reingest()(andMemoryClient.reingest(),POST /v1/reingest, thememory_reingestMCP tool andprme 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 thatstore()saved plainly could not be extracted at all: it has no extraction job forretry_extraction()orprocess_extractions()to run, and the derivation journal keys one extraction, plan and receipt to each event.reingest()runs the batched extractioningest()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 withmetadata.reingest.replaced_bynaming 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.limitandcursorrun it in capped steps, anddry_run=Truecounts the memories, the model requests and the source tokens without calling a model. It is off by default: setenable_reingest(PRME_ENABLE_REINGEST=true), or the call raisesReingestDisabledErrorand the route answers409. The hosted service turns it on. The fast cycle has areingeststage (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_EXTRACTEDandREINGEST_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.pyandtests/test_product_packing_diagnostic.py. - New
tests/test_reingest.py, areingeststage test intests/test_fast_cycle.pyand the public API typing check for the new call.
Upgrade notes
- No default changed since 0.16.0a4.
enable_reingestis new and off, so an engine refusesreingest()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 toextraction_batch_target_tokens(2,048 by default) tokens of source, so run it withdry_run=Truefirst 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_carryandpacking.session_context_skip_entitiesare 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_linksandscoring.rrf_graph_weightstay off. - Known weaknesses are as in 0.16.0a4.
- Known flaky tests:
tests/test_ingested_corrections.py::test_mcp_owner_declaration_is_strict_and_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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 auserturn states about we, us or our to the owner’s entity (aMENTIONSedge withmetadata.basisgroup_member), after the derivation commits and outside the plan. The claim keeps its group subject. It needsenable_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 replayablegraph_rankin 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-v1answers out of the top 3 at0.25, one at0.15and0.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.shon 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 whenorigin/mainhas 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.shandpublish-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.shgainsBUILDS=defaultsandEXTRACTION_CACHE. SeeBENCHMARKS.mdandmemory_bank/HANDOFF-2026-10-19-WEEKLY-BENCHMARK.md.
Changed
- Turn on
packing.session_context_skip_entitiesby 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 thatingest()creates beside a claim stop taking places above the notes and claims that answer. It acts onretrieve()over memories that have entities, so it changes nothing for a store made withstore()alone, and it rewrites no memory.PRME_PACKING__SESSION_CONTEXT_SKIP_ENTITIES=falserestores the expansion of 0.16.0a3. APackingConfigbuilt 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.mdandCONTRIBUTING.mdnow 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_tenantsnow 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_entitiesis on (see Changed). It affectsretrieve()over memories thatingest()extracted, not stores made withstore()alone, and it rewrites nothing. SetPRME_PACKING__SESSION_CONTEXT_SKIP_ENTITIES=falseto 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. APackingConfigbuilt 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) andscoring.rrf_graph_weight(it moves twochat-v1answers 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_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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). Withenable_ingest_claim_fixeson, 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 replayableclaim_statement_foldscore adjustment that names the statement, and the statement leaves the results with the exclusion reasonclaim_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 theingest()answer track.BENCHMARKS.mdhas 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). Auserturn is the memory’s owner by definition.enable_ingest_claim_fixesbinds theIof the turn that introduces them (“Hi, I am Alex Rivera”) andenable_speaker_referencesbinds theIof a turn with a caller’sspeaker, for that turn only, so a later turn that names nobody (“I prefer trains to flights”) kept anIentity of its own, one per message. With the option on, the person entity a user turn creates under the owner’s name carriesowner_namemetadata, 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 itsIto the newest such entity, through the same binding; its claims record"basis": "owner_name". It never applies toparticipant,assistant,toolorsystemturns, a caller’sspeakerorfirst_person_owneron 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 needsenable_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_ranklifts 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 ofdeepseek-v4.1-flash:cloudwithenable_ingest_claim_fixes,enable_mention_links,packing.graph_seed_wordsandpacking.folded_records_keep_rankon, and the option off then on:defects-v1user claims on the named owner againstIorWego from 2 against 15 to 12 against 5,chat-v1from 4 against 35 to 35 against 3,chat-v1key 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-contextualstage 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 asretrieval_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 toretrieve()over what it stores, not tostore(), 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 itsI, and a claim whose sentences another claim already holds gets a statement of its own. A custom extraction provider whoseextractdoes not takeplural_first_personis not given it, so the default breaks none; itsIfor aweis 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 theIof 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. APackingConfigbuilt 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.mdhas the tables.
Fixed
- Aggregation coverage no longer reports
candidate_limitedwhen 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 theLEXICAL_AGGlimit 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_wordsandenable_owner_name_carryare on (see Changed). They shape whatingest()stores and whatretrieve()returns over it; they do not touchstore(), and memories already stored are not rewritten. SetPRME_ENABLE_INGEST_CLAIM_FIXES=false,PRME_PACKING__FOLDED_RECORDS_KEEP_RANK=false,PRME_ENABLE_MENTION_LINKS=false,PRME_PACKING__GRAPH_SEED_WORDS=falseandPRME_ENABLE_OWNER_NAME_CARRY=falseto 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_wordsandfolded_records_keep_rank. Releases before 0.16.0a3 ignore the two fields when they read it. APackingConfigbuilt in code keeps both off. - A dump of the configuration now always names
enable_ingest_claim_fixesandenable_owner_name_carry. - A custom extraction provider whose
extractdoes not takeplural_first_personis 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_carrymarks 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_correctionsandenable_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_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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-extractstores the store stage’s 75 memories withextract=True(extraction bydeepseek-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-sessiondoes so with each memory’s session assession_id;store-sessionis 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 thestore-extractstage (the committed cache now holds 166 responses). Three live runs onmain: 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, andRetrievalMode.EXPLICITkeeps 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 throughingest()withdeepseek-v4.1-flash:cloudon 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-buildgates an earlier run’s packs for retrieval and packing changes in about 3.comparepairs 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 buildgains--cache-dirand--in-flightso builds under different labels share one extraction cache and several build processes cap their live model calls.
Changed
- Turn on
lexical_term_weightsby 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=falserestores 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-fixtureand ships the second question set asbenchmarks/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_weightsis 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. SetPRME_LEXICAL_TERM_WEIGHTS=falseto get the word search of 0.16.0a1 back. enable_superseded_source_suppressionis new and off.enable_ingest_claim_fixes,enable_mention_links,packing.graph_seed_words,enable_store_extractionandenable_extraction_correctionsstay 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 throughingest(), 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_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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 withstore()and asks 17 questions (rank of each answer, stale values, nodes per call, context tokens), then runs the three scripted chats throughingest()with extraction bydeepseek-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 bybenchmarks/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 theingest()answer track.- 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 emptyfacts. When a built-in provider writesI,me,my,mineormyselffor 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 passedplural_first_person=Trueonly while the option is on. - A speaker who introduces themselves is linked to their own entity. A user
turn with no
speakerthat says who it is (“Hi, I am Alex Rivera”, “I’m Alex Rivera”, “My name is Alex Rivera”, “Call me Alex”) binds itsI,meandmyto the person entity of that name, the way a caller-namedspeakerdoes underenable_speaker_references, when the extraction lists the name as a person. The claim’sspeaker_referencemetadata has"basis": "self_introduction", and the turn no longer creates a message-localIentity. 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, aspeakerthe caller gave, andfirst_person_owner=Truebind nothing new. The binding covers that turn only: a later turn that names nobody keeps its ownI. - 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 recordsclaim_statement_v1as its grounding method. The first claim keeps the source sentences and the paragraph stays every claim’sevidence_quote. The statement is not source text, so the packed context shows it as its own line instead of folding it into the message.
- 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
- Add
extracttostore(),store_with_receipt(),POST /v1/store, thememory_storeMCP tool and the sync client (#260), andenable_store_extractionto set its default (off).extract=Truesaves an extraction job with the stored event and starts the extractioningest()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 firstretrieve()result whose text holds it, because a short chat fits the context whole and passes a context-only check. The summary andcomparecount 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--setand--labelto build such a variant. - Add
embedding.threads(PRME_EMBEDDING__THREADS) andFastEmbedProvider(threads=...), the ONNX Runtime thread count of afastembedmodel. 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_linksandpacking.graph_seed_words(prism-monorepo issue 259), both off by default, so graph search can find the claims and notes that name an entity. Afteringest(), 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) makesingest()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 linksbasis: 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, sowhich cities will Alex and jordan visit in Japan?and the same question withJordanfind 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.GRAPHfor 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_depthand 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_versionreads 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_contextno 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_linksandpacking.graph_seed_wordsare off. On the fast gate (defect probes and the chat probe, withdeepseek-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)andenable_store_extractionstart a model call in the background for each stored memory. Both are off by default, sostore()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_persistedcan stop the test process with a DuckDB segmentation fault while the engine closes, andtests/test_checkout.py::TestMainCheckout::test_a_plain_clone_resolves_to_itselffails 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 (anEXTRACTION_BATCH_RECORDEDoperation) and each turn’s record names it (ExtractionRecord.batch_id), so a restart, a replay andprme rebuildnever 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 waitingingest(),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 withenable_windowed_extractionorenable_fact_text_resolution. At 0 nothing changes: one call per turn, with the same records, plans and receipts as before.benchmarks.diagnostics.extracted_packsbuilds packs at any target with--set extraction_batch_target_tokens=N, and its newsummarycommand totals a build. - Add visual memory, phase 1 of RFC-0018:
MemoryEngine.observe(),MemoryClient.observe()andprme observe <db_path> <file>...turn a photo or a saved video into observation events. Video is sampled withffmpeg, a keyframe is kept on pixel change or everymax_gap_seconds, a local vision model (Ollama,qwen3.5:9bby 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 aboverepeat_similarity) is dropped, and each remaining description is admitted as an event of roletoolwith the capture clock asevent_timeand metadata naming the source, the frame time, the frame’s SHA-256, the thumbnail, the vision model and the prompt version. Withvision.extract(on) the description goes throughingest()and a failed extraction is retried before the next frame; otherwise throughstore(). A frame already observed for the owner by the same model and prompt is skipped. Everything is behind the newVisionConfig(vision,PRME_VISION_*), off by default, so a pack that never observes is unchanged;observe()raisesVisionDisabledErroruntilvision.enabledis set, andprme observesets it for its own run; it raisesObservationError, 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 frombenchmarks/vision/spike.pyintosrc/prme/vision/; the harness now drives the engine.numpyandpillow, already installed throughfastembed, are declared dependencies. - Keep a JPEG thumbnail of each observed keyframe in a
blobstable of the pack’s DuckDB database, in both layouts, keyedframes/<frame sha256>.jpgand read back withget_blob(). The table inherits the pack’s encryption, is copied byprme migrateandprme compact, is left alone byprme rebuild, and is created on first use. - Add opt-in single-file memory packs (#247). Set
pack_path(PRME_PACK_PATH), callprme.client.config_from_pack(), or giveMemoryClienta path that ends in.prmeand is not an existing directory;config.single_file_packreports the mode, and the now publicprme.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_manifestrow: 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, andprme rebuildrecords the model that rebuilt the vectors. - Keep a single-file pack’s USearch snapshot in a
vector_snapshotrow, reconciled on open againstvector_payloadsexactly as the snapshot file is, and hold its Tantivy index in memory, rebuilt from active graph nodes inidorder before the engine serves any read. After every open the lexical index holds the documents the write path gave the pack, whichprme rebuildalso produces (see Changed), so a pack built withingest(), 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,MemoryClientobjects 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 towardvector_save_intervalas 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 savesvectors.usearchafter every removal. Vector hits are identical, and directory-pack retrieval is unchanged apart fromprme 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_encryptionand keeps those files in<pack>.tmpbeside the pack. Stored text therefore does not appear in clear on disk while the engine runs. A raw key (theraw_key:prefix, or an unprefixed 44-character url-safe base64 Fernet key) is used without derivation; any other value, or one with thepassphrase:prefix, is derived with PBKDF2-HMAC-SHA256 (600,000 iterations) and the fixed, versioned saltprme-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, raisesEncryptionErrorand 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, raisesPackInUseError;MemoryEngine.lock()andunlock()raiseRuntimeErrorfor a single-file pack. Directory packs keep Fernet file encryption. - Add
prme compact <pack>, with--encryption-key KEYand--format table|json, which copies a closed pack file into a fresh one withCOPY FROM DATABASEand replaces the original atomically, to give back the space DuckDB never returns. It takes a.prmefile or a directory pack’smemory.duckdb, refuses while<pack>.walexists, and keeps the source’s storage version and cipher. On a copy of a LoCoMo benchmark pack it tookmemory.duckdbfrom 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 KEYand--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.prmefile 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 adb_pathopens a.prmefile. Without--encryption-key,prme doctor,prme compactandprme migrateusePRME_ENCRYPTION_KEYfor a.prmepack whenPRME_ENCRYPTION_ENABLEDis true, asprme infodoes, but never for a directory pack’smemory.duckdb; an empty--encryption-keymeans no key.prme-mcp --db-path x.prmeserves a single-file pack throughPRME_PACK_PATH, ignoring an inheritedPRME_DATABASE_URL, and a directory--db-pathsetsPRME_PACK_PATHempty so an inherited pack path cannot replace the directory.MemoryWorkspace(..., single_file_packs=True), also accepted byMemoryWorkspace.open(), keeps each namespace’s pack in onememory.prmefile; 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 inbenchmarks/results/research/2026-10-15/VISUAL-MEMORY-SPIKE.md. Nothing undersrc/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_tokensdefaults to 2048 instead of 0 (#254), soingest()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 overingest()packs built withdeepseek-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-secondextraction.timeout, which still applies to a turn’s own call. The engine’s close countsbatch_timeoutfor 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 waitingingest(),ingest_batch(),process_extractions()or the close extracts it first.- Turning on
enable_windowed_extractionorenable_fact_text_resolutionturns batching off unlessextraction_batch_target_tokensis 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: aningest()build made with the defaults batches, and theingest()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.
- A batched request has its own timeout, the new
- 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) plusextraction.batch_timeoutfor 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, andMemoryClient.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_againfield, 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 andprme rebuildstill 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 reportsturns_asked_againbesideturns_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_documentandLEXICAL_DOCUMENT_SQLinprme.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, soprme rebuildon a pack built withingest(), 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 withstore()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 setextraction_batch_target_tokens=0to 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 waitingingest(),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_timeoutfor 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 rebuildno 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 matchpumptoinsulin pump, and explicitswitched_toupdates can replace positiveuses/useFACT 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=Trueon 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()filtersexclude_node_typesandsource_typesacross 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_v13for 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 setPRME_ENABLE_SPEAKER_REFERENCES=falsewhen 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 existingingest()stores change too.store()is unchanged. To restore the v0.13.0 behavior, setPRME_ENABLE_CLAIM_SENTENCE_TEXT=false,PRME_ENABLE_SPEAKER_REFERENCES=false,PRME_ENABLE_CLAIM_MERGE=falseandPRME_PACKING__FOLD_REPEATED_TEXT=false.docs/PACKING.md(“Default retrieval settings”) has the details. - claims whose text is their own sentences instead of the whole message
(
-
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 overingest()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, withextraction_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 newcontextargument 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 methodresolved_text_v1and lists each replacement inmetadata.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 policyspeech_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.serverserves 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 inPRME_CHAT_DATA_DIR, the folderexamples/chat.pyuses; a missing folder stops it with an error instead of creating an empty pack.uv run python -m web.demo ./my_memories_demobuilds a small connected demo pack through the realingest()pipeline with scripted extraction output, so it needs no model or network. benchmarks.diagnostics.extracted_packsbuilds LoCoMo and LongMemEval-S memory packs throughingest()with an Ollama extraction model (local by default; an Ollama cloud model with--cloud), and the offline evidence gate replays them withgate --packs(#102, part of #91). Until now the gate could replay only packs built withstore(), 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 LABELanswers anextracted_packsbuild instead of the savedstore()packs, which cannot show an option that only changesingest(). The track keeps its own contexts, answers, run logs, A/A checks and verdicts,comparerefuses 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 thestore()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 inbenchmarks.diagnostics.dev_sliceand tagged from committed answer runs as stable failures, stable passes or noisy.gate --sliceandextracted_packs build --slicework 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 throughingest()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-comparecompares 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=Truein the product packing defaults). Together they passed the default-change rule twice on the DeepSeek answer track overingest()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. SetPRME_ENABLE_CLAIM_SENTENCE_TEXT=false,PRME_ENABLE_SPEAKER_REFERENCES=falseorPRME_PACKING__FOLD_REPEATED_TEXT=falseto 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. SetPRME_ENABLE_CLAIM_MERGE=falseto 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_ROOTandPRME_MATRIX_ROOToverride the two roots, and the registered harness files are unchanged, so they still match their recorded digests. scripts/download_benchmarks.pyalso 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=weightedrestores the weighted formula; it also drops the recency boost and tie-break, and weighted scoring never applies the 0.6 session decay; - the
readercontext format, one plain line per record without node IDs or source types (#177).PRME_PACKING__CONTEXT_FORMAT=auditablerestores JSON records, which callers that parse the context as JSON need. Answerability checks andverify_bundle()need that setting orPRME_PACKING__CONTEXT_CITATIONS=true; balancedmulti-path ordering, as in v0.12.0. #177 had made score order the default and #187 madebalancedthe default again.PRME_PACKING__MULTIPATH_ORDERING=scorekeeps #177’s score order.
To go back to the v0.12.0 retrieval defaults, set
PRME_SCORING__FUSION=weighted,PRME_PACKING__CONTEXT_FORMAT=auditableandPRME_PACKING__MULTIPATH_ORDERING=balanced(the last only makes the current default explicit), or in code passscoring=ScoringWeights()andpacking=PackingConfig()toPRMEConfig.docs/PACKING.md(“Default retrieval settings”) has the details. - rank fusion scoring (
-
HTTP API network binds.
python -m prme.apiandprme.api.server.run_server()refuse a non-loopback address withoutPRME_API_USER_KEYSorPRME_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, orPRME_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 withreranker_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 withoutenable_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, orPRME_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_tiercounts 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.adjacentpacks the same records and places each neighbor beside that result in session order. Unset (the default), nothing changes. Results and bundles carry each neighbor’ssession_context_linkonly 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, andgate-compareshows 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", orPRME_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_ator admission-timevalid_from, and it excludes records that fit only as a fallback without their own text. The full record stays inMemoryBundle.sectionsand the retrieval receipt.context_citations=Trueadds[m3]references and fillscontext_references, which MCP now returns withinclude_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", orPRME_SCORING__FUSION=rrf, withrrf_k, default 60). Each candidate is ranked within the pool on the semantic and lexical channels and scored by1/(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 lacksemantic_relevance, stay readable). Because a fused score says where a result ranks rather than how relevant it is,min_scoreunder rank fusion compares against each result’ssemantic_relevanceinstead: 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 reasonrank_fusion_scoring; learning evaluation skips rank-fused receipts (rank_fusion_receipt_records); and thefeedback_applyjob reportsnot_applicableand keeps its signals.PRMEConfignow 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, orPRME_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 replacessession_context_score_decayfor 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_scorefloor, 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, andRetrievalMetadata.min_score_skipped(in the HTTP and MCPmetrics) is set next to thebackend_failuresentry 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 recordsmin_score_skipped. The LangChain and LlamaIndex retrievers accept an optionalmin_scoreand addmin_score_skipped: Trueto each result’s metadata when the floor was skipped. -
Add two opt-in rank fusion settings for conflicting memories (
ScoringWeights.rrf_recency_boostandScoringWeights.rrf_tie_break, orPRME_SCORING__RRF_RECENCY_BOOSTandPRME_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 by1 + 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). Withrrf_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.PRMEConfignow 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()andFastIngestItemaccept an optionalspeakername, as do HTTPPOST /v1/store,/v1/ingestand/v1/ingest/fastand the MCPmemory_store,memory_ingestandmemory_ingest_fast_manytools. The newparticipantrole marks a human in the conversation other than the memory’s owner: likeuser, its sources areUSER_STATED(with the default confidence matrix, 0.80 for an asserted claim instead of the 0.60 anassistantsource gets), but its statements do not reinforce the owner’s instructions. The speaker is kept in the reservedmetadata.prme_speaker_v1key of the source event and its direct node or raw note, not yet on extracted claims; passing that key inmetadatais now rejected. The reader context format prints it as"Caroline": "text"unless the text already begins with it, and the auditable format adds aspeakerkey. Withenable_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", orPRME_SCORING__RECENCY_TIME=event_timewithPRME_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", orPRME_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 istemporal_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-05as 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 underbenchmarks/results/research/2026-09-23/. These remain the published reference.benchmarks.integrations.gpt54_baselinesadds 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 gatereplays the publicretrieve()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-comparepairs two runs question by question with a bootstrap interval, and--plain vector,bm25orrrfmeasures 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 inbenchmarks/results/research/2026-09-24/READER-PACKING-ORDER-GATE-V1.md(#81). SeeBENCHMARKS.md. -
Add a DeepSeek answer track for benchmark answer runs with no API cost (issue #117).
benchmarks.integrations.gpt54_baselines --provider ollamaanswers withdeepseek-v4.1-flash:cloudas reader and judge through a local Ollama server, with the registered prompts, questions and official LongMemEval-S judge. It prepares the defaults as theprmearm and a change as a named variant (--variantwith--set).run-pairanswers a variant and a fresh run of the defaults interleaved question by question (#129),comparereports the paired difference with a 95% interval, andverdictdecides a variant from the pairscomparerecorded (#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%). SeeBENCHMARKS.md. -
Add a lenient LoCoMo judge score beside the strict one (issue #96).
benchmarks.integrations.lenient_judgesends 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 baselineprme@46647825it 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’sdropped_record_idsstill lists only the records that stage displaced. - The configuration reference now gives the candidate limit defaults that
retrieval uses:
PRME_PACKING__VECTOR_KandPRME_PACKING__LEXICAL_K500,PRME_PACKING__AGGREGATION_K_MULTIPLIER3.0 andPRME_PACKING__AGGREGATION_K_MAX2000. 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
participantorhuman, 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. Theuvicorn prme.api:appcommand they showed never loaded the app, becauseprme.api:appnames theprme.api.appmodule. - 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.comwithOPENAI_API_KEYfrom 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 extractionbase_urlwith the extractionapi_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 extractiontimeout(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_urlorANTHROPIC_BASE_URL) now works: Instructor 1.14 built Anthropic clients without it and passed it to every call, which failed. ExtractionConfig.timeoutmust 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,proseorstructured, set in code, withPRME_PACKING__MIN_FIDELITYor 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 inMemoryBundle.excluded_idsinstead 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-freekey_valueandreferencefallbacks out of the context. The default floor staysreference, so default contexts are unchanged. - Aggregation coverage no longer reports a blank record’s exclusion as a
token_budgetlimit when the reader format or a text-bearing floor excludes it. - A per-request
min_fidelitypassed toMemoryEngine.retrieve()as a plain string is now converted toRepresentationLevel, 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", previouslyauditable), score ordering (packing.multipath_ordering="score", previouslybalanced;balancedis 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 variantprme-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). SeeBENCHMARKS.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()raiseValueErrorfor a reader bundle without citations. SetPRME_PACKING__CONTEXT_CITATIONS=trueor use theauditableformat 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_scoreis compared with the semantic cosine instead of the composite score. Non-neutral requestranking_multipliersare rejected (HTTP 422, MCP error), learned ranking profiles are not applied (rank_fusion_scoring), andfeedback_applyreportsnot_applicable. SetPRME_SCORING__FUSION=weightedto keep the weighted formula, including any tunedPRME_SCORING__W_*weights. - The new defaults live in
PRMEConfig(default_scoring_weights()anddefault_packing_config()), andPRME_SCORING__*andPRME_PACKING__*environment variables,.enventries and secrets that set only some values keep them. SettingPRME_SCORING__FUSION=weightedalso drops the rank fusion recency boost and tie-break, which weighted scoring cannot use.ScoringWeights()andPackingConfig()built in code keep their field defaults (the weighted formula;auditable,balancedand 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. Copyconfig.packingorconfig.scoringwithmodel_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=auditableandPRME_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, passscoring=ScoringWeights()andpacking=PackingConfig()toPRMEConfig.
- 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
- Multi-path ordering default.
PRMEConfig()now orders the ordinary multi-path tier withbalanced(packing.multipath_ordering="balanced") instead of score order. The other retrieval defaults above are unchanged.balancedwas 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 variantprme-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. SeeBENCHMARKS.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 withmultipath_ordering="score":config.packing.model_copy(update={"multipath_ordering": "score"}).
- Upgrade note: to keep score order, set
- Simulation checkpoints can require memory text in the context the reader
gets (
SimCheckpoint.context_keywords, reported asCheckpointResult.context_missing). Four checks that tested the weighted formula’s order rather than what reaches the reader now use it: the infrastructure question inconsolidation, the tools question inremention, and the database and observability questions insurprise_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_fidelitytofull,proseorstructuredand 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.apiandprme.api.server.run_server()now refuse to start on a non-loopback address (such as0.0.0.0, including inside a container) when neitherPRME_API_USER_KEYSnorPRME_API_API_KEYis set. They used to log a warning and serve every request without authentication. The command exits with status 2 andrun_server()raisesprme.api.server.UnauthenticatedBindError(aValueError), 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,::1and IPv4-mapped loopback) andlocalhost(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.ymlpublishes its PostgreSQL test database (fixedprme_testcredentials) on127.0.0.1:5432only, instead of on all interfaces. CI connects to127.0.0.1. An existing container keeps its old mapping until it is recreated: rundocker compose up -donce. 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_envelopeandanchored_score_envelopepolicies to preserve the scored prefix’s original score scale during downstream packing. Add opt-in alternate-querymax_signalsmerging for existing candidates, with exact source-snapshot checks and atomic failure behavior. Both storage engines receive these throughPRMEConfig; 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_v11extraction 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_prefixesfor explicit, token-bounded composition of model-authored predicate detail. A normalized prefix such asraisedmatchesraisedandraised_*, but notfundraised; no synonyms or embeddings are inferred. Responses distinguish this mode withsemantic_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,750total 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
Iorwesubject. 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 3no 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 underspeech_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 underspeech_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_v6and preparedspeech_act_v12plans. 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 undertemporal_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
PENALTYrecord 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=Trueremains available for explicit hypothesis testing. -
TTL expiration now revalidates policy under lock and commits archival with a deterministic, checksummed
TOMBSTONE_SWEEPrecord 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_TOedge and a checksummed completeALIAS_PROPOSEDrecord 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
FastEmbedProviderconstruction 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
ClaimVerifierthat 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 returnsincompletewithout a model call for derived exhaustive counts or lists. The verifier is available throughprme[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 returninsufficient. 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 becameinsufficient, the reported question becamesupported, 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 distinctlocalized_model_entailmentbasis; 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
AnswerabilityEvaluatorfor 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 derivesanswerable,partial,insufficient, orconflictingin 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 preserveallsemantics 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_sourceandmax_per_evidenceresult-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-v1reader contract. It resolves the pinned task prompt’s conflict between an example JSON reasoning object and its option-text exact-match reference by requiring oneA. choice textline 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-v1contract. 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-v1reader 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-v1reader contract shared by PRME and BM25. It resolves the pinned harness’s conflict between alabel: Nprompt 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-bm25versions, 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_idparticipates 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_referencesandresolve_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 declaresauditable, and the compact preset now declarescompactso 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-extractionprovider 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/scanandmemory_scan_nodesexpose scope, type, and lifecycle filters, immutable UUID ordering, explicithas_more/next_cursorfields, 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 historicalstore()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: nonefor 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_storeparity 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.
-
MemoryWorkspaceand lease-scopedNamespaceMemoryfor 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 outsidepublic. -
Optional
duckdb_threads/PRME_DUCKDB_THREADScontrol 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_tokenandcross_scope_token_budgetfields remain parseable for receipt compatibility, but non-default use warns that exact context tokenization andcross_scope_top_nare the active controls. Settingcross_scope_top_n=0now skips the secondary hint search entirely. -
The organizer registry now contains only implemented jobs. The unimplemented, unvalidated
centrality_boostno-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_atnow exposes a machine-readablehistorical_coverageboundary 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.Falseexplicitly 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_applyrequests now raiseValueErrorbefore any work. Trusted operators can retain the legacy behavior with explicitorganize(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
runningstate; 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_efforttonone, 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
Iandwewithout 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=...)andprme 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 viaprme 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_timefield distinguishes when something happened vs when system learned about it;knowledge_atparameter 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) —
prmecommand-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_instructionssection; epistemic inference support - Entity snapshot generation (issue #13) —
generate_entity_snapshot()produces structured entity state views from graph neighborhood;snapshot_generationorganizer job; simulation scenario - Predictive forgetting / consolidation (issue #22) — Semantic clustering of episodic memories; summary abstraction creation; redundant memory archival;
consolidateorganizer job - TTL-based archival (issue #12) —
ttl_daysfield on memory nodes; per-type default TTL configuration;tombstone_sweeporganizer job with operation logging; policy-based retention enforcement - Summarization pipeline (issue #10) — Hierarchical daily -> weekly -> monthly summarization; configurable thresholds; time-budget-aware processing;
summarizeorganizer 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.pywith engine/log fixtures - DuckDB segfault in concurrent tests (issue #19) — Isolated DuckDB connections per test with
conn_lockprotection - 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 — bumpsreinforcement_boost(+0.15, cap 0.5) andconfidence_base(+0.05, cap 0.95), updateslast_reinforced_at, appends evidence refs- Re-mention reinforcement in
store()— opt-in viareinforce_similarity_thresholdconfig; vector-searches for similar existing nodes and reinforces them automatically - Keyword-based supersedence detection in
store()— opt-in viaenable_store_supersedenceconfig;ContentContradictionDetectorwith 10 regex patterns for migration/replacement language - Oscillation detection for flip-flop supersedence patterns —
OscillationDetectorusing Jaccard keyword similarity on supersedence chains; applies confidence penalty (0.1 per cycle, cap 0.3) update_node()method onGraphStoreprotocol 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_gatingconfig;NoveltyScorercomputes 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_countdefault aligned withstore()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.