Repository navigation
Conversation
An episode's producer, role, recipient, creation time, and scalar metadata values become property values in every memory it reaches, and EpisodeEntry accepted any string, integer, float, or datetime there. So MemMachine.add_episodes wrote an entry to the stores that keep a value and failed in the stores that refuse it. Measured on main through each store's own code: NaN and infinities are refused by PostgreSQL JSONB and Qdrant gRPC, lost by Qdrant REST, and leave Milvus records unreadable; integers outside signed 64 bits are refused by Qdrant gRPC and mostly by Milvus, become floats in Qdrant REST, and make filters on them raise in both SQLite vector stores; U+0000 is refused by PostgreSQL; lone surrogates are refused by PostgreSQL, Milvus, and Qdrant; a datetime whose UTC instant falls outside years 1 through 9999 is refused by every store but Qdrant; and Qdrant shifts the instant of a datetime whose UTC offset carries seconds. Define the property value domain once, in memmachine_common: validate_property_value for any property value, and PropertyStr for a string. Type EpisodeEntry's producer, role, recipient, and metadata keys as PropertyStr, check its creation time and its metadata values that are property values with validate_property_value, and keep metadata values that are lists, objects, or null as they are. The check sits where an entry enters rather than on the shared PropertyValue alias, which the stores' own models carry: there it would preempt each store's own checks on its input, run again for every record read back, and fail semantic memory's vector records after their SQL rows are written. Tests fail under these mutations: - No int64 range check: the int64 refusal tests. - int64 bounds narrowed by one: the int64 boundary acceptance tests. - No finiteness check: the NaN and infinity tests. - Pattern without the surrogate range: the lone surrogate tests. - Pattern without U+0000: the NUL tests. - No whole-minute offset check: the offset seconds and microseconds tests. - No UTC range check: the year 1 and year 9999 tests. - Every offset refused: the whole-minute offset acceptance test. - Metadata without the value check: the metadata value tests. - Metadata keys typed str: the metadata key test. - producer_id, producer_role, or produced_for_id typed str: that field's test. - created_at without the domain check: the created_at tests. - Metadata narrowed to scalar values: the structured metadata test. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Every MemoryMessage field that becomes a property value accepted any string or datetime, so a value some store refuses passed the request and failed in that store after the others had written it. Measured on main through POST /api/v2/memories with the real stores: a metadata value with U+0000 or a lone surrogate answers 500 after semantic memory has recorded the episode in its sets, on PostgreSQL; a producer with U+0000 is refused by the PostgreSQL episode store, segment store, and semantic storage while SQLite and the vector stores keep it; a timestamp of 0001-01-01T00:00:00+05:00 answers 500; and one with an offset of +05:30:45 is stored, where Qdrant shifts its instant. Type the metadata keys and values, producer, produced_for, and role as PropertyStr, and the timestamp as the new PropertyDatetime, so the request answers 422 before any store is called. With the same requests after this change, every one answers 422 and the episode store, segment store, vector store, and semantic history gain no row, on SQLite and PostgreSQL. Tests fail under these mutations: - metadata typed dict[str, str]: the metadata value and key tests. - Metadata keys typed str: the metadata key tests. - producer, produced_for, or role typed str: that field's test. - timestamp typed datetime: the timestamp tests. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This was referenced Oct 7, 2026
edwinyyyu
marked this pull request as draft
October 7, 2026 20:48
edwinyyyu
marked this pull request as draft
October 7, 2026 20:48
This was referenced Oct 7, 2026
Draft
[session storage 2/2] Remove open-or-create from both stores, and close from the segment store
#1625
Draft
This was referenced Oct 7, 2026
[qdrant options] Let a deployment tune a Qdrant collection's HNSW, optimizers and quantization
#1618
Draft
Draft
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose of the change
Summary
An episode's data fans out to the episode store, semantic memory, and episodic memory's segment store and vector store at once. Each value that becomes a property value (a metadata key or value, the producer, the recipient, the role, or the timestamp) was accepted as any string, number, or datetime, so a value that one store refuses was written by the others first, and the request answered 500 with the episode in some stores and not in others. A value that one store alters was kept as given in the others.
This change refuses those values where an episode enters, before any store is called:
POST /api/v2/memoriesanswers 422.MemMachine.add_episodescallers get aValidationErrorwhen they build theEpisodeEntry.The property value domain is defined once, in
memmachine_common.api.spec:validate_property_valuechecks any property value,PropertyStrtypes a string, andPropertyDatetimetypes a datetime.MemoryMessagetypes its metadata keys and values,producer,produced_for, androleasPropertyStrand itstimestampasPropertyDatetime.EpisodeEntrytypesproducer_id,producer_role,produced_for_id, and its metadata keys asPropertyStr, and checkscreated_atand its metadata values that are property values. Metadata values that are lists, objects, or null are kept as they are.What each store does with these values on
main(measured; full matrix in the issues):The domain is checked where an episode enters, not on the shared
PropertyValuealias. The stores' own models (Record,Event,Segment, andEpisode) carry that alias, so a check on it would:Recordvalidation with 12 properties went from 5.6 µs to 9.7 µs in a local measurement;The alias stays as it was.
Fixes #1793. Fixes #1794. Fixes #1795. Part of the fix for #1784, whose store-side refusal is in #1788.
Not covered here, each with its own issue:
Commits
Stack
21 open PRs: three independent PRs, and the vector store tree of short parallel branches. Every 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. #1702 and #1670 sit beneath #1627, whose code depends on them. 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-scalingfeat/horizontal-scalingThis PR is its 2 commits,
ab419b16f,95ded7618, directly onmain, independent of the vector store tree: review and merge it in any order.Verification
Store-level measurement on
mainat ad8ff24, one value at a time through each store's own code, with a throwaway probe that is not committed:SemanticSessionManager.add_messageon pgvector storage and vector-store storage, on SQLite and PostgreSQL;LongTermMemoryon the event backend into the SQLAlchemy segment store on SQLite and PostgreSQL;LongTermMemoryintoSQLiteVectorStore,SQLiteVecVectorStore, Qdrant 1.17.0 over REST and gRPC and in local mode, and Milvus 2.6.24 and Milvus Lite, with and without a declared user property schema.POST /api/v2/memoriesend to end throughMemMachine.add_episodes, with the real episode store, segment store, and semantic storage on SQLite and on PostgreSQL andSQLiteVectorStore, counting rows in each store after every request:0001-01-01T00:00:00+05:002026-01-01T12:00:00+05:30:45{"k": "v"}uv run --frozen --all-extras ruff checkandruff format --check: clean.ty check:packages/serverwith Python 3.12 and 3.14, andpackages/commonandpackages/clientwith Python 3.10 and 3.14: clean.uv lock --check: clean.Unit suite (
pytest packages): 2405 passed, 3 skipped.Integration suite (
-m "integration and not slow"over the four CI paths), with PostgreSQL, Neo4j, and Qdrant 1.17.0 containers: 1454 passed, 188 skipped (tests that need an OpenAI or AWS key).Each test fails under the mutations named in its commit message, which were each applied and run.
git merge-tree --write-tree upstream/feat/horizontal-scaling HEAD: no conflicts.🤖 Generated with Claude Code