What happens
Measured on main at ad8ff24 through POST /api/v2/memories into an event-backend session, with the real episode store, segment store, and semantic storage on SQLite and on PostgreSQL, and SQLiteVectorStore. The same holds on SQLite and PostgreSQL:
| metadata |
status |
episode store |
semantic memory sets |
segment store |
vector store |
{"userId": "v"} |
500 |
written |
written (3) |
written |
nothing |
{"_x": "v"} |
500 |
written |
written (3) |
nothing |
nothing |
{"k": "v"} |
200 |
written |
written (3) |
written |
written |
Store by store, through each store's own code, the vector stores refuse a key that is not lowercase letters, digits, and underscores of at most 32 bytes: userId, a-b, a.b, a b, the empty key, 33 characters, and é alike (SQLiteVectorStore, SQLiteVecVectorStore, Qdrant, and Milvus). A collection whose declared user properties hold such a key cannot be created. The episode store and the segment store keep every such key; the PostgreSQL stores refuse only a key with U+0000.
Why
- The request takes any string as a metadata key (
packages/common/src/memmachine_common/api/spec.py:474), and every string metadata value becomes a filterable property under its key (packages/server/src/memmachine_server/episodic_memory/episodic_memory.py:225-229).
- The event backend refuses a key that starts with an underscore when it builds the event (
packages/server/src/memmachine_server/episodic_memory/long_term_memory/long_term_memory.py:818-830), and a vector store record refuses a key outside its key set (packages/server/src/memmachine_server/common/vector_store/data_types.py:135-146). The record is built after the segment store has written the segment (packages/server/src/memmachine_server/episodic_memory/event_memory/event_memory.py:274-292).
MemMachine.add_episodes writes the episode store, episodic memory, and semantic memory concurrently (packages/server/src/memmachine_server/main/memmachine.py:795-819), so the episode store and semantic memory have written the episode by the time the event backend refuses it.
Expected
An episode is written to every store or to none: the event backend accepts every key the request accepts, or the request refuses the key with 422 before any store writes.
Fix
Not in #1792, since it needs a decision. #1383 was closed with "Internally changing to use UUID would be better. Externally can allow a wider set.", which points at mapping user keys to internal names in the event backend so that it accepts what the request accepts. Until then, the request could refuse these keys with 422.
🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.
What happens
Measured on
mainat ad8ff24 throughPOST /api/v2/memoriesinto an event-backend session, with the real episode store, segment store, and semantic storage on SQLite and on PostgreSQL, andSQLiteVectorStore. The same holds on SQLite and PostgreSQL:{"userId": "v"}{"_x": "v"}{"k": "v"}Store by store, through each store's own code, the vector stores refuse a key that is not lowercase letters, digits, and underscores of at most 32 bytes:
userId,a-b,a.b,a b, the empty key, 33 characters, andéalike (SQLiteVectorStore,SQLiteVecVectorStore, Qdrant, and Milvus). A collection whose declared user properties hold such a key cannot be created. The episode store and the segment store keep every such key; the PostgreSQL stores refuse only a key with U+0000.Why
packages/common/src/memmachine_common/api/spec.py:474), and every string metadata value becomes a filterable property under its key (packages/server/src/memmachine_server/episodic_memory/episodic_memory.py:225-229).packages/server/src/memmachine_server/episodic_memory/long_term_memory/long_term_memory.py:818-830), and a vector store record refuses a key outside its key set (packages/server/src/memmachine_server/common/vector_store/data_types.py:135-146). The record is built after the segment store has written the segment (packages/server/src/memmachine_server/episodic_memory/event_memory/event_memory.py:274-292).MemMachine.add_episodeswrites the episode store, episodic memory, and semantic memory concurrently (packages/server/src/memmachine_server/main/memmachine.py:795-819), so the episode store and semantic memory have written the episode by the time the event backend refuses it.Expected
An episode is written to every store or to none: the event backend accepts every key the request accepts, or the request refuses the key with 422 before any store writes.
Fix
Not in #1792, since it needs a decision. #1383 was closed with "Internally changing to use UUID would be better. Externally can allow a wider set.", which points at mapping user keys to internal names in the event backend so that it accepts what the request accepts. Until then, the request could refuse these keys with 422.
🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.