Repository navigation
[property keys] Refuse an illegal metadata key before any store writes, and reserve one key prefix - #1828
Conversation
8d7408d to
a14919f
Compare
101182c to
cf7ae15
Compare
39075cd to
d432bf8
Compare
8ae3e11 to
53f030e
Compare
…tamp under it MemMachine and its users share one property key space. A key beginning with `memmachine_` now belongs to MemMachine: `reserved_property_key` builds one as `memmachine_<system>_<field>`, and every key is a valid vector store identifier, `[a-z0-9_]` and at most 32 bytes. EventMemory writes its timestamp under `memmachine_em_timestamp`. It refuses an event whose property key is reserved or not a valid identifier, through `validate_user_property_key`; a key is used exactly as given, never rewritten. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An EventMemory filter, and a segment store's, named a property in two ways: `m.<key>` for the key itself, and a bare name for `_<name>`, the layout one consumer gives its own fields. The bare form tied both to that consumer's keys. A filter field is now `timestamp` or `m.<key>` (or `metadata.<key>`). EventMemory refuses any other field before it searches, and the segment stores refuse it as well. The vector search filters on the timestamp, under its reserved key, and on the declared properties. The long-term memory adapter names its system fields as properties when it hands a filter to EventMemory, so its client filters are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MemMachine reserved two key namespaces at once. EventMemory refused a property key beginning with `memmachine_`, while the long-term memory adapter wrote its own fields under a leading underscore and refused a caller key beginning with one. So a caller could not use an underscore, and the adapter's fields sat outside the namespace meant for them. The adapter now writes its fields under `memmachine_ltm_<field>`, and refuses a caller key that is not a legal user property key. A filter still names a system field by its bare name, and refuses a `m.<key>` in the reserved namespace. The reserved namespace is divided by the system that writes each key. EventMemory refuses only its own `memmachine_em_` keys, in an event and in a filter, so the component that embeds it can write its own reserved keys. `is_reserved_property_key` takes the system to ask about one system's keys. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An episode's metadata keys become event properties, and the event backend refuses a key that is not a legal user property key. That refusal came while the episode store and semantic memory were writing the same episodes in parallel, so the request failed after they had kept them, and a request over HTTP got a 500. `MemMachine.add_episodes` now checks every metadata key of every entry with `validate_user_property_key` before it writes anything, and the add-memories route answers a ValueError with 422, as the other routes do. The metadata field's description states the key rule. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The metadata field's description gives the key rule in words instead of a pattern. EventMemory's filter check and the adapter's conversion state what holds for a reserved key, and `MemMachine.add_episodes` says its refusal comes before any store is written. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
53f030e to
4fc4aab
Compare
EventMemory and the segment stores spelled out how a client names a property in a filter, and EventMemory checked a filter's fields against that spelling before it searched. The spelling belongs to the filter language and to the API that accepts a client's filter. EventMemory no longer checks a filter's fields: the long-term memory adapter checks a client's filter, and the segment stores refuse a field that names neither a property nor the timestamp. For the vector search, EventMemory translates the timestamp to its reserved key and a property to its key, and passes any other name unchanged. The docstrings describe a filter over the segments' properties and timestamp. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The docstrings of EventMemory's query and filter translation, the segment stores' filter, and the adapter's filter mapping named the timestamp as a special field. They return to their wording on the base, changed only where it no longer holds: an event memory base property translates to its reserved key, and a segment store refuses an unknown field. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Self-reviewed (human). |
marvinyu-memverge
left a comment
There was a problem hiding this comment.
Approve at 013346b.
I read property_keys.py, both memories' paths (EventMemory and the long-term memory adapter), the segment store's field mapping, MemMachine.add_episodes and the add-memories route, as whole files at head.
Observation, no action needed in this PR: the integrations that hand an LLM a free-form metadata record don't state the new key rule. openclaw's memory_store takes metadata as any string-to-string record and merges run_id/user_id into it, and the Strands tool schema just says "Additional metadata as key-value pairs". So a key the model picks, like sourceUrl or source-url, now comes back as a 422 from add-memories. Every in-repo caller I found (the evaluation ingest scripts, crewai, the Strands example, openclaw's own run_id/user_id) already uses legal keys, so nothing in the repo breaks; the tool descriptions are just where an agent would learn the rule. Could be a small follow-up.
Verified:
- Every key MemMachine writes into an event is under memmachine_: EventMemory's memmachine_em_timestamp and the adapter's eight memmachine_ltm_* fields. The deployment's declared vector store properties (event_backend_indexed_properties) are built from the same constants, so the declaration and the writes can't drift.
- The longest reserved key, memmachine_ltm_produced_for_id, is 30 bytes, inside the 32-byte budget, and reserved_property_key raises at import if a later field doesn't fit.
- add_episodes checks every entry's metadata keys before anything is written, and it's the only path into the episode store, episodic memory and semantic memory; the route turns the ValueError into 422.
- Filters on the event backend: the reserved-key check runs before the adapter maps a bare system name (producer_id) to m.memmachine_ltm_producer_id, so a client's own m.memmachine_* is refused while the mapped name passes. EventMemory demangles it to the vector store key, the segment store reads it from the properties JSON, and timestamp goes to memmachine_em_timestamp and the timestamp column.
- On the declarative backend the _filterable_metadata_none flag is still an underscore key, but user keys there live under metadata., so it can't collide with the user _ keys this now allows.
The one red check, build-docs (24), is npm not finding @mintlify/link-rot@3.0.1580, not this change.
Purpose of the change
Every property key now follows one rule. A key under
memmachine_belongs to MemMachine, and each system writes its own keys asmemmachine_<system>_<field>. Every other key belongs to the user and must be lowercase letters, digits, and underscores, at most 32 bytes. The rule is checked before any store writes. Fixes #1796.Why one reserved prefix. The design reasoning is in The reserved key namespace (design/event_model.md, #1579).
New with this change, compared with the base:
_timestamp. An application that embeds EventMemory and writes its own_keys could pick a name MemMachine later uses. Now every key MemMachine writes into an event starts withmemmachine_, each component writes under its own part of it (memmachine_em_,memmachine_ltm_), and a field added later is reserved without any list to update._keys. On the base, the event backend refused every caller key that started with_. Now onlymemmachine_is reserved._key only after the episode store and semantic memory had written the episode, and the request failed with 500 (Event-backend sessions refuse metadata keys outside the vector stores' key set after the episode store and semantic memory have written the episode #1796). NowMemMachine.add_episodeschecks every key first and answers 422._, so a key such asuserIdwas kept but could never be declared for indexing. Now both refuse the same keys,userIdamong them, and every accepted key is a valid field name on every vector store, so any key can be declared for indexing.m._producer_idas well as throughproducer_id. Now only the bare name works, andm.<key>with a reserved key is refused.Kept from the base:
A caller cannot write a field MemMachine stamps, such as another producer's id. The event backend now refuses
memmachine_keys instead of_keys. The declarative backend still stores user keys undermetadata., so they never meet its own fields.The prefix
memmachine_is reserved.common/property_keys.pybuilds a reserved key withreserved_property_key(system, field)and checks a user key withvalidate_user_property_key. EventMemory writes its timestamp undermemmachine_em_timestampinstead of_timestamp, and it refuses an event whose property key is reserved or illegal.Metadata keys are checked before anything is written.
MemMachine.add_episodeschecks every metadata key of every entry withvalidate_user_property_keybefore it writes. The add-memories route answers the refusal with 422, as the other routes answer a ValueError.userIdanduser-idtoo.uuid.hex), not in its 36-character hyphenated form.The long-term memory adapter writes its fields under
memmachine_ltm_. It wrote_episode_uid,_session_key,_producer_id, and five more fields under a leading underscore, and refused any caller key that began with one.memmachine_ltm_<field>, so_xis an ordinary user key.producer_id. A filter fieldm.<key>whose key is in the reserved namespace is refused.EventMemory refuses only its own keys. It refuses keys under
memmachine_em_in an event. It keeps any other key under the prefix, since that key belongs to the component that embeds it.is_reserved_property_key(key, system=...)answers for one system's keys.No bare filter name stands for
_<name>. EventMemory and the segment stores mapped a bare filter name to_<name>, which is the adapter's layout.context.*included, instead of matching nothing.Data written before this change. Event-backend segments carry the adapter's underscore keys, and vector records carry those and
_timestamp; neither EventMemory nor the adapter reads or writes them any more. A search skips a segment withoutmemmachine_ltm_episode_uid, so it returns none of the old episodes. A vector store's tables and collections keep the properties declared when they were created, and nothing compares those with the current declaration: on SQLite, an old partition opens and answers queries, and an upsert into it raisesOperationalErrorfor the missingmemmachine_em_timestampandmemmachine_ltm_*columns. Event-backend data from before needs migrating or recreating.Commits
57297b1f7Reserve the memmachine_ property key prefix, with EventMemory's timestamp under it. This is [event memory 1/6] Add time bounds, sources and expansion to EventMemory (port of #1597, without session) #1684's key-rule part:common/property_keys.pyand its tests,_timestamprenamed tomemmachine_em_timestamp, where atimestampfilter still reaches the vector store, and EventMemory's key check throughvalidate_user_property_key.fdcbc4d6cName an EventMemory filter's properties as m. alone. EventMemory, the segment stores, and the in-memory fake name propertiesm.<key>and refuse other fields; the vector search filters on the timestamp and the declared properties. The adapter maps its bare system names onto its underscore keys.295e70ea7Keep the event backend's fields under the reserved key prefix. This addsmemmachine_ltm_<field>, the user-key check on the adapter's caller keys, the refusal of reserved keys in a client filter, and EventMemory's narrowed check withis_reserved_property_key(..., system=...).f8ae5a302Refuse an illegal metadata key before any store writes, with 422. This adds the check inMemMachine.add_episodes, the route's 422, and the metadata field's description in the API docs anddocs/openapi.json.4fc4aab97State the metadata key rule in words, and two key docstrings plainly. The metadata field's description and three docstrings are reworded; no code changes.b6b75ab02Leave the filter syntax to the filter language and its API. EventMemory no longer checks a filter's fields, and its docstrings and the segment stores' no longer spell the syntax; the timestamp's routing to its reserved key stays.013346b9fDescribe filters without singling out the timestamp. The filter docstrings of EventMemory, the segment stores, and the adapter's mapping return to the base's wording, changed only where it no longer holds; no code changes.Position: on
feat/horizontal-scalingdirectly, parallel to #1839 and #1840. #1684 builds on both, so its chain sits on this PR's commits rebuilt onto #1840, and whichever of this PR and #1840 merges second rebases ontofeat/horizontal-scaling. #1684 adds the source key, the time and source filters, and expansion under this rule.Stack
24 open PRs: three independent PRs, and the vector store tree of short parallel branches. Every open PR in the tree has
feat/horizontal-scalingas its GitHub base, and the independent PRs havemain. The branches are in a fork, and a pull request can target only this repository's branches, so the on column gives the order the PRs build on each other. A stacked PR's diff on GitHub includes the PRs under it until they merge.Independent of the vector store tree, directly on
main:mainmainmainThe vector store tree. Each PR builds on the one in its on column; PRs on the same parent are parallel branches and do not depend on each other. #1631 is closed, superseded by #1733–#1736, which hold its changes split in four, with review changes since. #1675 is closed, superseded by #1627. #1841 is closed: its request packing is dropped, and its cancellation contract is in #1840. #1828 and #1839 sit side by side on the branch; #1839 and #1840 are the event memory recovery stack, and #1684 heads the event memory stack, which its own PRs index, listed because it builds on #1840, its branch also carrying #1828's commits until one of the two merges; #1871 keeps its own Stack section. Until the PRs under it merge, their changes show in a stacked PR's diff.
mainfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scalingfeat/horizontal-scalingmemmachine-server migrate, which creates every configured store's schema before servers startfeat/horizontal-scalingThis PR is its 7 commits,
57297b1f7,fdcbc4d6c,295e70ea7,f8ae5a302,4fc4aab97,b6b75ab02,013346b9f, directly onfeat/horizontal-scaling.Verification
On 2026-11-03, per Edwin's "Don't change the docstrings to single out the timestamp",
013346b9frestores the base's filter docstrings, changed only where they no longer hold: an event memory base property translates to its reserved key, any other name passes unchanged, and a segment store refuses an unknown field. Docstrings only. At013346b9f:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The copy for #1684's chain, on #1840a82fc6e65, has the same commit as4fe221b12.On 2026-11-02 Edwin set this PR parallel to #1839 and asked that EventMemory and the segment stores not know how the filter language is written; his May 8 ruling that EventMemory takes the client's
m.and bare names is replaced.b6b75ab02drops EventMemory's filter field check and the docstrings' spelling of the syntax. EventMemory's change fromfeat/horizontal-scalingis now the timestamp key and its routing, the bare names no longer mapped to_<name>, and the event key check. Atb6b75ab02:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean; the server unit suite passes, 2371 tests (8 skipped), three fewer for EventMemory's dropped filter check. No integration tests ran. The same commit on the copy for #1684's chain, on #1840cda5face2, is70508eb8d; its server unit suite passes, 2431 tests (8 skipped).Re-ported on 2026-11-02 onto
feat/horizontal-scaling0fa52b38b, below #1839, after Edwin's "#1839 on #1828": the key rule needs nothing #1839 or #1840 adds. Onfeat/horizontal-scaling, EventMemory has no EventMemoryPartition, so the reserved key's set and the event key check stay on EventMemory, and the tests use itsevent_memoryfixture;test_event_memory_recovery.py, which #1840 adds, drops out of the first commit. Otherwise each file's added and removed lines match53f030efe's. The commits are now57297b1f7,fdcbc4d6c,295e70ea7,f8ae5a302, and4fc4aab97, with the same messages, authors, and dates, and signed; the last two keep their patch-ids. At4fc4aab97:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean; the server unit suite passes, 2374 tests (8 skipped). No integration tests ran. The SQLite old-data results above hold at4fc4aab97.Rebuilt on 2026-11-02 on #1840
cda5face2, below #1684, after Edwin's "please reorder" the same day: reserving one key prefix and adding event metadata to event memory are separate ideas. The first commit is #1684's key-rule part. The other four are the commits from8ae3e117f, re-expressed on #1840's code, where everything they change already exists: the bare-name mapping to_<name>, the adapter's underscore fields and its check, the segment store's field mapping, and the API boundary. With #1684 staymemmachine_em_source, the since, until, and source predicates, expansion, and this PR's docstrings on them. For the new base: the vector search keeps filtering on the timestamp under its reserved key, which on #1684 the since and until predicates do; the in-memory segment store fake evaluates atimestampcondition; and the tests name #1840'sget_segment_contexts,ProducerContext, and the timestamp key where they named #1684's methods, its author helper, and the source key. The six files #1684 does not change are identical to8ae3e117f's. At53f030efe:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean; the server unit suite passes, 2434 tests (8 skipped). No integration tests ran. The SQLite old-data results hold, measured with #1840's declaration of_timestampand the adapter's_<field>keys: an old partition opens and answers queries, an upsert into it raisesOperationalError(no columnp_memmachine_em_timestamp), and a new partition takes the upsert.Restacked on 2026-11-02 onto #1684
574566083, on #1840cda5face2, whose base no longer packs vector store upserts and takes the cancellation contract into #1840. None of this PR's files change beneath it. The commits are now5960c8ab7,cbc3475da,86a20bdf4, and8ae3e117f, with the same patch-ids, messages, authors, and dates, and signed. The head's change from2cfd65cbcis #1684's change fromffe4374ba. At8ae3e117f:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-11-02 onto #1684
ffe4374ba, on #184096097c2c0, onfeat/horizontal-scalingwith [sqlite write transactions 1] (#1844) merged. The base changes the segment store's engine check and imports, in the file whose filter-field mapping this PR changes, and the replay was clean. The commits are nowd8cb9872d,37f23f72a,f8c3805ed, and2cfd65cbc, with the same patch-ids, messages, authors, and dates, and signed. At2cfd65cbc:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The unit tests of event memory, long-term memory, the property keys,MemMachine, and the v2 router pass, 527 tests.Restacked on 2026-10-30 onto #1684
c9270336a, on #1840004e6c79f, onfeat/horizontal-scaling690116e37, which has [declared schema 1/2] (#1628), #1831, and #1858 merged. #1831 and #1858 change three files this PR also changes,memmachine.py, the v2 router, andtest_memmachine_mock.py, and the replay was still clean. The commits are now952edd32f,da734bfdf,187ddf713, and99aff2611, with the same patch-ids, messages, authors, and dates, and signed. At99aff2611:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The unit tests of event memory, long-term memory, the property keys,MemMachine, and the v2 router pass, 527 tests; the six more are #1831'sMemMachinedeletion tests.Restacked on 2026-10-28 onto #1684
e3bde9748, on #1840f4b487018, whose base takes #1628'sdf716e47b:is_valid_identifier's docstring, and the SQLite records table built withoutextend_existing. The commits are now246689971,e3ca4e1a3,cf01bedd8, and0ccfdf4a1, with the same patch-ids, messages, authors, and dates, and signed. The head's change from321f9bb41is #1684's change frome9556d9af. At0ccfdf4a1:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-27 onto #1684
e9556d9af, on #1840b631cd84c, whose base takes #1628's second review round:is_valid_identifiermoves from the vector store'sutils.pyinto itsdata_types.py, and the interface's Raises sections are sorted. The commits are now74277a4b8,a265d75f0,80679112e, and321f9bb41, with the same messages, authors, and dates, and signed. The first, third, and fourth keep their patch-ids. The second's one changed line isevent_memory.py's import ofis_valid_identifier, now frommemmachine_server.common.vector_store.data_types. At321f9bb41:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The unit tests of event memory, long-term memory, the property keys,MemMachine, and the v2 router pass, 521 tests.Restacked on 2026-10-27 onto #1684
384dfec58, on #1840cff96f208, whose base takes #1628's review changes: the vector stores evaluate the whole filter language, sosupported_filter_nodes,UnsupportedFilterError, andfilter_nodesare gone, the declared-property checks live inutils.py, and the SQLite stores' property module issqlite_properties.py. This PR names none of them and touches none of the files the base changed. The commits are now86b5b5189,bf5efd35e,015013700, and3104dfe5e, with the same patch-ids, messages, authors, and dates, and signed. At3104dfe5e:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The unit tests of event memory, long-term memory, the property keys,MemMachine, and the v2 router pass, 521 tests.Restacked on 2026-10-27 onto #1684
2122252a3, on #184094b3588a6, after #1844 moved above #1628 beneath it; #1684's tree is127f18617's. The commits are nowa1f0c9fbb,cbe1a9774,166d4a648, andf2a020a3c, with the same patch-ids, messages, authors, and dates, and signed. The head's tree isa6c3fbcf8's, so the checks and tests below stand.Restacked on 2026-10-27 onto #1684
127f18617, on #184075982b908, after [vector store scale-out 6] (#1627) merged intofeat/horizontal-scalingas805ea2177, a squash whose tree is8c2351dbe's. The commits are now8d7db9f46,48ce752ad,a765a8a99, anda6c3fbcf8, with the same patch-ids, messages, authors, and dates, and signed. The head's change from51cf91c30is #1684's change fromc847f0b53to127f18617: a longer request timeout for two Milvus integration tests. Ata6c3fbcf8:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The 521 unit tests that passed at51cf91c30cover this head, since the change touches integration tests only.Restacked on 2026-10-26 onto #1684
c847f0b53, on #18405b16f04f5, whose base gives a vector store its schema as oneVectorStoreSchemaand names EventMemory's declarationexpected_vector_store_indexed_properties. The commits are nowc117e5df2,61867d7d2,b96006335, and51cf91c30, with the same messages, authors, and dates, and signed. The third and fourth keep their patch-ids, and the first two keep their source changes line for line. Only their tests changed: the test fakes takestore_schema, the shared test partition declaresepisode_uid, the declaring fixture declaresd1,d2, andd3, and two test modules importVectorStoreSchemabesidereserved_property_key. At51cf91c30:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The unit tests of event memory, long-term memory, the property keys,MemMachine, and the v2 router pass, 521 tests; the base now marks one segment store test as an integration test. The SQLite old-data results above hold at51cf91c30, measured on a store declared as the deployment declares its event store.Restacked on 2026-10-20 onto #1684
ee58642bc, which now sits on #1840 and #1839: EventMemory serves each partition through anEventMemoryPartitionhandle. The commits are nowcacda52fb,81189acb8,6dad028b3, and4227ccd58, with the same messages, authors, and dates, and signed; the third and fourth keep their patch-ids. The first two conflicted where the base moved or renamed code, and each resolution keeps the commit's change on the base's layout. The filter check,_vector_store_filter, and the key check are nowEventMemoryPartition's, with_TIMESTAMP_FIELDand_EVENT_MEMORY_SYSTEMbeside the base's module-level_RESERVED_PROPERTY_SCHEMA, andencode_events's Raises keeps the base's two new entries. The adapter's changes have the same added and removed lines as before. The tests use theevent_memory_partitionfixture, and the segment store's test adds its segment through_add_segments. At4227ccd58:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. The unit tests of event memory, long-term memory, the property keys,MemMachine, and the v2 router pass, 522 tests. The SQLite old-data results above hold at4227ccd58.Restacked on 2026-10-20 onto #1684
29c22631c, whose base takes #1627's comment, docstring, and documentation accuracy fixes. The commits are now11d733b58,80f332c89,7e0004419, and423ac84e3, with the same patch-ids, messages, authors, and dates, and signed. The head's change fromfbfeae54bhas the same patch-id as #1684's change fromab4714322to29c22631c. At423ac84e3:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-20 onto #1684
ab4714322, whose base takes #1627's second review's test, doc, and docstring fixes. The commits are now3a687e95e,bf9606c17,b7314dab9, andfbfeae54b, with the same patch-ids, messages, authors, and dates, and signed. The head's change from4b25288f8has the same patch-id as #1684's change from387d56131toab4714322. Atfbfeae54b:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-19 onto #1684
387d56131, whose base takes #1627's review fixes to the database manager and the partition registry. The commits are now3f584035a,e87b72361,b68d07cea, and4b25288f8, with the same patch-ids, messages, authors, and dates, and signed. The head's change from0cb33663chas the same patch-id as #1684's change from84a000bc2to387d56131. At4b25288f8:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-19 onto #1684
84a000bc2, which no longer sits on #1663: event memory scores by the partition's similarity metric, andquerytakesscore_threshold. The commits are now235d8ca1c,0d3c08f3f,7d5651306, and0cb33663c, with the same messages, authors, and dates, and signed. Commits 3 and 4 have the same patch-ids. Commits 1 and 2 conflicted with the new base in two places, and each resolution keeps both sides: the event memory tests'make_partitionkeeps itssimilarity_metricparameter and takes this PR's docstring and declaredepisode_uid;EventMemory.query'sRaiseslists the non-finitescore_thresholdbeside this PR's filter field refusals. At0cb33663c:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean; the event memory, long-term memory, and property key unit tests pass (310). Also at0cb33663c, the SQLite check of a partition created with_episode_uiddeclared gives the same results as atd432bf8fa.Restacked on 2026-10-19 onto #1684
4bac1cbfe, whose base rewords one line ofdesign/vector_store_partition_registry.md. The commits are nowf942fa924,ebb6e884a,367ccebb3, and425fd4a39, with the same patch-ids, messages, authors, and dates, and signed. The head's change from322af628ehas the same patch-id as #1684's change from45b75a446to4bac1cbfe. At425fd4a39:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-19 onto #1684
45b75a446, whose base takes #1824 fromfeat/horizontal-scaling. The commits are now6aaceac22,5afe6aa86,d080e6470, and322af628e, with the same patch-ids, messages, authors, and dates, and signed. The head's change from9824b4cb0has the same patch-id as #1684's change from4241c994eto45b75a446. At322af628e:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-16 onto #1684
4241c994e, whose base renames the configuration's tombstone retention check. The commits are now226c77809,4ab38bd1e,7f3156d7a, and9824b4cb0, with the same patch-ids, messages, authors, and dates, and signed. The head's change from401477980has the same patch-id as #1684's change fromb6a4f6285to4241c994e. At9824b4cb0:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-16 onto #1684
b6a4f6285, whose base renames the vector store's raising checks tovalidate_*. The commits are nowf7964d7b2,ce79c7e8a,884dbfdef, and401477980, with the same patch-ids, messages, authors, and dates, and signed. The head's change from9310c2b68has the same patch-id as #1684's change from9a035e168tob6a4f6285. At401477980:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-16 onto #1684
9a035e168, whose base renames the vector store'svalidate_identifiertois_valid_identifier. The commits are nowe1172686a,7870865f3,284f3c8a5, and9310c2b68, with the same messages, authors, and dates, and signed. Commits 1, 3, and 4 have the same patch-ids; commit 2's only change is thatevent_memory.pyimports and callsis_valid_identifier. At9310c2b68:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. Also at9310c2b68, the SQLite check of a partition created with_episode_uiddeclared gives the same results as atd432bf8fa.Restacked on 2026-10-15 onto #1684
539f52a98, whose base drops the vector store's per-partition schema record. The commits are now1fea085fb,437e1c5ed,941ffbfe3, andd432bf8fa, with the same patch-ids, messages, authors, and dates, and signed. The head's change from39075cdf1has the same patch-id as #1684's change fromaae191241to539f52a98. Atd432bf8fa:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean. Also atd432bf8fa, on SQLite: a partition created with_episode_uiddeclared, reopened withmemmachine_ltm_episode_uiddeclared, opens and answers a query; an upsert into it raisesOperationalError(no columnp_memmachine_ltm_episode_uid); a partition created after takes the same upsert.Restacked on 2026-10-15 onto #1684
aae191241, whose base names the vector store's lookupopen_partitionand keeps the segment store'sopen_partition,close_partition, andopen_or_create_partition. The commits becameadb72e43a,c01e726ca,9c9836cce, and39075cdf1, with the same patch-ids, messages, authors, and dates, and signed. The head's change from6a71ec3dahas the same patch-id as #1684's change from392d436cftoaae191241. At39075cdf1:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-15 onto #1684
392d436cf, whose tree isc0235df55's without the segment store'sclose_partition, which #1627 then removed. The commits became76b202fdb,b104ea5fd,962ef6061, and6a71ec3da, with the same patch-ids, messages, authors, and dates, and signed. The head's change fromf66ac07d4has the same patch-id as #1684's change fromc0235df55to392d436cf. At6a71ec3da:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-15 onto #1684
c0235df55, whose tree equalsebb27742d's; only #1627's history beneath it was rewritten. The commits became27c47d49b,099c93a70,2035ec164, andf66ac07d4, with the same patch-ids, messages, authors, and dates, and signed.f66ac07d4's tree equalsa4ef42919's, so the checks below ata4ef42919held for it.Restacked on 2026-10-15 onto #1684
ebb27742d, onfeat/horizontal-scalingcf7ae1579. The commits became0c674aa36,87cd7815f,6f512c697, anda4ef42919. The replay was clean, and all four keep their patch-ids, messages, authors, and dates, and are signed. The head's change froma14919f2ehas the same patch-id as #1684's change from22f48998ftoebb27742d. Ata4ef42919:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Restacked on 2026-10-14 onto #1684
22f48998f. The commits becamed1a97995e,0f1b5196b,786412423, anda14919f2e, with the same messages, authors, and dates, and signed. The first, third, and fourth keep their patch-ids. The second keeps its own line where #1670 renamednormalize_filter_field's unused result to_, since the commit uses that result. The head differs from8d7408d69only by #1820's patch, the same patch-id as its merge21f06ddbc. Ata14919f2e:ruff check,ruff format --check,ty checkas CI runs it, anduv lock --checkclean.Before the restack, at
8d7408d69, on 2026-10-14, the suites ran on its tree, as an unpushed commita275bf393with the same tree:ruff checkandruff format --checkclean;ty checkclean as CI runs it (uv run --frozen --all-extras ty check --project packages/server);uv lock --checkclean. The server and client suites pass: unit, 2,460 passed (7 skipped); integration, 1,691 passed (342 skipped). At2d045afdfand0e222b023, the property key and episodic memory unit tests pass (375 and 381); at26c02a718, the full unit suite passes (2,460, 7 skipped). All four commits are signed.🤖 Generated with Claude Code