Skip to content

[user properties 1/2] Keep undeclared properties out of the vector store - #1702

Open
edwinyyyu wants to merge 1 commit into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:feat/vector-store-declared-routing-main
Open

edwinyyyu wants to merge 1 commit into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:feat/vector-store-declared-routing-main

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

The event backend wrote every property of an event into its vector record and mapped the caller's whole filter onto the vector store, so a user key a deployment never declared was both stored and filtered there: on Qdrant and Milvus as unindexed payload a filtered query scans for. Now an undeclared key stays off the vector store, so a tenant's undeclared properties cannot shape what it stores or scans; #1670, stacked on this, removes the per-project declaration, so no tenant key reaches it at all. The segment store already holds every property and already receives the whole filter for the context windows, so the vector side only duplicated work the segment store does anyway.

The vector record now carries the keys the collection declares: EventMemory's reserved timestamp, the _-prefixed system properties an adapter stamps on the event, and the keys a project's properties_schema declares, until #1670 removes that option (collection.config.indexed_properties_schema). The vector store is queried with the conjuncts of the filter that name only such fields; a conjunct is dropped whole when any field under it is undeclared, so dropping only ever widens the vector search, and the segment store narrows it back on the windows. An undeclared key never reaches the vector store.

filter_fields joins the filter parser: every field name a tree addresses. Tests: an undeclared property stays on the segment and off the record; a conjunct on an undeclared property reaches only the segment store; an undeclared property under an OR leaves the vector search unfiltered; filter_fields names every field under every node.

This is the routing half of what was #1628 on speedkick (its store-side half, the stores rejecting undeclared keys, is deferred to after #1627); it is re-derived onto #1736's collection shape from the same commit, where the segment store, not the vector record, maps a derivative to its segment.

Part of the fix for #1781.

Stack

21 open PRs: three independent PRs, and the vector store tree of short parallel branches. Every PR in the tree has feat/horizontal-scaling as its GitHub base, and the independent PRs have main. 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:

# PR change on
— #1624 Make no memory request create a project main
— #1786 Refuse a filter value of the wrong type for a datetime column, and answer an invalid list filter with 422 (port of #1620) main
— #1792 Refuse property values that some store refuses or alters where an episode enters main

The 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.

# PR change on
[vector store scale-out 1/6] #1671 (merged) Remove custom sharding from the Qdrant store (port of #1654) main
[vector store scale-out 2/6] #1733 (merged into feat/horizontal-scaling) Answer vector store queries with record UUIDs and scores, and refuse invalid inputs feat/horizontal-scaling
[vector store scale-out 3/6] #1734 (merged into feat/horizontal-scaling) Arbitrate vector store collections in a SQL registry, with an incarnation per collection life and a purge feat/horizontal-scaling
[vector store scale-out 4/6] #1735 (merged into feat/horizontal-scaling) Move the Qdrant store onto the collection registry feat/horizontal-scaling
[vector store scale-out 5/6] #1736 (merged into feat/horizontal-scaling) Move the Milvus store onto the collection registry, against a Milvus server feat/horizontal-scaling
— #1775 (merged into feat/horizontal-scaling) Accept attempts exhausted in the lifecycle churn contract, and say which Qdrant operations filter on the incarnation feat/horizontal-scaling
— #1779 (merged into feat/horizontal-scaling) Classify Qdrant errors by status code alone feat/horizontal-scaling
— #1813 (merged into feat/horizontal-scaling) Create Qdrant collections with strict mode off feat/horizontal-scaling
— #1788 Refuse a repeated record UUID or a non-finite property value at upsert, and state the datetime property contract feat/horizontal-scaling
— #1631 (closed) Superseded by #1733–#1736, which hold its changes split in four, with review changes since —
[user properties 1/2] #1702 (this PR) Keep undeclared properties out of the vector store feat/horizontal-scaling
[user properties 2/2] #1670 Remove per-project filterable properties (port of #1606) #1702
[vector store scale-out 6/6] #1627 Make a vector store one collection, with string-keyed partitions #1670
[session storage 1/2] #1622 Create a session's storage with the session, never on a request #1627
[session storage 2/2] #1625 Remove open-or-create from both stores, and close from the segment store #1622
[declared schema 1/2] #1628 Make a vector store filter only on the properties it declares #1627
[search results] #1663 Score every vector search by cosine similarity, and name scores for it (port of #1598's cosine half) #1628
[declared schema 2/2] #1616 Close the filter union, and make negation the complement on every backend #1663
[sqlite store fixes 1/7] #1460 Publish vector index files atomically (but not durably) #1663
[sqlite store fixes 2/7] #1469 Never reuse a row id in SQLiteVectorStore #1460
[sqlite store fixes 3/7] #1672 Own the search engine's concurrency in the store, not in each engine (port of #1612) #1469
[sqlite store fixes 4/7] #1673 Serialize a partition's writes so the engine sees them in order (port of #1607) #1672
[sqlite store fixes 5/7] #1674 Refuse a pending row replay cannot honor, instead of dropping it (port of #1608) #1673
[sqlite store fixes 6/7] #1675 Take SQLite's write lock at BEGIN, not at the first write (port of #1609) #1674
[sqlite store fixes 7/7] #1676 Give every write a fresh row id, so a key names one version (port of #1610) #1675
[qdrant options] #1618 Let a deployment tune a Qdrant collection's HNSW, optimizers and quantization #1663
[milvus options] #1741 Let a deployment tune a Milvus collection's vector index and its searches #1618

This PR is its one commit, b1c3242fe, directly on feat/horizontal-scaling. #1670 is stacked on it.

Verification

At this PR's head b1c3242fe, on 2026-10-09, on feat/horizontal-scaling with #1813 merged: ruff check and ruff format --check clean; ty check clean as CI runs it (uv run --frozen --all-extras ty check --project packages/server); uv lock --check clean.

Before the rebase onto #1813's merge: At this PR's head 6cdbe0733, on 2026-10-09: ruff check and ruff format --check clean; ty check clean as CI runs it (uv run --frozen --all-extras ty check --project packages/server); uv lock --check clean; the episodic memory and filter tests pass, 448 tests. The full suites have not run on this tree, which is new with #1670 above it; with #1670 beneath it, the server suite without integration tests last passed at 9031d25f4, on 2026-10-08, 2166 tests, and the integration tests of the vector stores, the resource manager, episodic memory, and semantic storage at 9031d25f4, on 2026-10-08, 689 tests, against PostgreSQL 16, Neo4j, Qdrant 1.19.1, and Milvus 2.6.24 in containers.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn

@edwinyyyu edwinyyyu changed the title [vector store 3/9] Keep user properties out of the vector store [vector store scale-out 3/4] Keep user properties out of the vector store Sep 21, 2026
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch from 48951bf to c164c56 Compare September 21, 2026 22:44
@edwinyyyu edwinyyyu changed the title [vector store scale-out 3/4] Keep user properties out of the vector store [vector store scale-out 4/5] Keep user properties out of the vector store Sep 21, 2026
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch from c164c56 to 2dc8c96 Compare September 25, 2026 23:10
@edwinyyyu edwinyyyu changed the title [vector store scale-out 4/5] Keep user properties out of the vector store [user properties 2/2] Keep user properties out of the vector store Sep 25, 2026
@edwinyyyu edwinyyyu added the horizontal scaling Wrong or unsafe when more than one server process serves the same backends (replicas or workers) label Sep 29, 2026
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch from 126f04f to 5745b0e Compare October 7, 2026 19:37
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch from 5745b0e to 8f3792c Compare October 7, 2026 21:16
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch 3 times, most recently from 059b801 to 9031d25 Compare October 9, 2026 00:52
@edwinyyyu
edwinyyyu changed the base branch from main to feat/horizontal-scaling October 9, 2026 00:52
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch 2 times, most recently from 48cb234 to 75bacd5 Compare October 9, 2026 20:12
@edwinyyyu edwinyyyu changed the title [user properties 2/2] Keep user properties out of the vector store [user properties 1/2] Keep user properties out of the vector store Oct 9, 2026
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch from 75bacd5 to 6cdbe07 Compare October 9, 2026 20:22
@edwinyyyu edwinyyyu changed the title [user properties 1/2] Keep user properties out of the vector store [user properties 1/2] Keep undeclared properties out of the vector store Oct 9, 2026
@edwinyyyu
edwinyyyu marked this pull request as ready for review October 9, 2026 21:02
The event backend wrote every property of an event into its vector record
and mapped the caller's whole filter onto the vector store, so a user key
that a deployment never declared was both stored and filtered there: on
Qdrant and Milvus as unindexed payload a filtered query scans for. The
segment store already holds every property and already receives the whole
filter for the context windows, so the vector side only duplicated work the
segment store does anyway.

The vector record now carries the keys the collection declares:
EventMemory's reserved timestamp, the `_`-prefixed system properties an
adapter stamps on the event, and the keys a project's `properties_schema`
declares. The vector store is queried with the conjuncts of the filter
that name only such fields; a conjunct is dropped whole when any field
under it is undeclared, so dropping only ever widens the vector search,
and the segment store narrows it back on the windows. An undeclared key
never reaches the vector store, so a tenant's undeclared properties cannot
shape what it stores or scans.

`filter_fields` joins the filter parser: every field name a tree addresses.

Rebased onto MemMachine#1631, where the vector record no longer carries the segment
uuid (the segment store maps a derivative to its segment).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
@edwinyyyu
edwinyyyu force-pushed the feat/vector-store-declared-routing-main branch from 6cdbe07 to b1c3242 Compare October 10, 2026 00:32

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants