Skip to content

[vector store 2/12] Route system filters to the vector store and user-defined ones to the segment store (speedkick) - #1621

Closed
edwinyyyu wants to merge 3 commits into
MemMachine:speedkickfrom
edwinyyyu:feat/event-memory-filter-routing-speedkick
Closed

edwinyyyu wants to merge 3 commits into
MemMachine:speedkickfrom
edwinyyyu:feat/event-memory-filter-routing-speedkick

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

EventMemory took one filter tree and split it by the store's declared keys, so what the vector store evaluated depended on the store's schema: a user-defined key a deployment declared was evaluated in the vector store, against the decision that user-defined properties are never indexed there.

EventMemory.query now takes two filters. system_filter names system fields by their bare names (timestamp, and the fields its caller writes under the _-prefixed layout); it is evaluated during the vector search and again by the segment store. property_filter names user-defined properties (m.<key>) only; the vector store never holds a user-defined property, so it is applied by the segment store to the seeds the one fetch returned, conjoined with the system filter. A bare name in property_filter or a m.<key> in system_filter is refused, and a vector record carries the system properties and never a user-defined one.

The API still carries one filter, so the long-term-memory adapter takes it apart by field namespace with split_conjuncts and refuses a clause that mixes the two under one disjunction or negation. EventMemory itself splits nothing.

Stack

Slice 2 of 12, every PR targeting speedkick; merge bottom-up.

# PR change
1 #1606 (merged) Remove per-project filterable properties
2 #1621 (this PR) Route system filters to the vector store and user-defined ones to the segment store
3 #1622 Create a session's storage with the session, never on a request
4 #1623 Make the examples that write to a project create it first
5 #1624 Make no memory request create a project
6 #1625 Remove open-or-create and close from both stores
7 #1626 Rename logical collection to partition, and open to get, on both stores
8 #1627 Make a vector store one collection, with string-keyed partitions
9 #1628 Make a vector store filter only on the properties it declares
10 #1630 Bound every request to a remote vector store by a configured timeout
11 #1618 Let a deployment tune a Qdrant collection's HNSW, optimizers and quantization
12 #1616 Close the filter union, and make negation the complement on every backend

This PR's own change is its four commits: eaf0133f (the routing, first cut), then on review 34fa10de (folds the one-field FilterOptions into a plain field), 27724f6d (removes the widening loop and the field: one fetch of vector_search_limit) and 07b0b218 (6 files changed, 240 insertions(+), 89 deletions(-); EventMemory takes system_filter and property_filter apart, evaluates only the former in the vector store, and the adapter splits the API's one filter by namespace); it sits directly on speedkick, and #1622 is stacked on it.

Verification

ruff check, ruff format --check, ty check (two pre-existing spacy diagnostics), pytest packages/server/server_tests packages/client/client_tests: 2213 passed, 3 skipped, on the slice-10 tree, 2026-09-14; every slice passed its own run when built.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn

…egment store

EventMemory split a caller's filter nowhere: the whole tree went to the
vector store, which had to store every caller property and filter on any
key, and then to the segment store again. Now the vector store gets the
conjuncts naming only keys its collection declares, evaluated during the
search, and the rest is the segment store's, applied to the seeds
afterward; a disjunction or negation mixing declared and undeclared keys is
undeclared as a whole. A derivative record carries only the caller
properties the collection declares, so an undeclared key never reaches the
vector store, and a declared key's value must have the declared type.

A post-filter can drop seeds and leave the search short, so the fetch is
widened by a factor of four at a time until the limit is met or the fetch
reaches `limit * FilterOptions.max_overfetch_factor` (default 64), where
the search returns what survived. A query with no undeclared part never
widens.

`filter_parser` gains `filter_fields`, `filter_nodes` and `split_declared`;
the in-memory test collection records the filter of each query so routing
can be asserted.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
@edwinyyyu
edwinyyyu force-pushed the feat/event-memory-filter-routing-speedkick branch from b37d6b3 to eaf0133 Compare September 14, 2026 21:54
edwinyyyu and others added 2 commits September 14, 2026 15:07
…options type

`FilterOptions` held a single integer and nothing read it but
EventMemory; a container around one knob is structure without a
second member. `max_overfetch_factor` is a field of EventMemoryParams,
with the same default and bound.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
…ening

A search widened its vector fetch by four at a time, up to
`max_overfetch_factor` times the limit, to make up for seeds the segment
store's post-filter dropped, so a selective predicate on an undeclared key
could cost 64 times the limit in vectors and segment store lookups.

The vector search is one fetch of `vector_search_limit`, as it was: that
limit bounds a query's cost, and the deployment declares the keys it
filters on so they are evaluated during the search. A predicate on an
undeclared key is applied to those seeds afterward, and the result holds
the seeds it admits, fewer than the limit when it drops some; the docstring
says so. The loop, its two constants and `max_overfetch_factor` go.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
@edwinyyyu edwinyyyu changed the title [vector store 2/13] Route filters on undeclared keys to the segment store (speedkick) [vector store 2/12] Route system filters to the vector store and user-defined ones to the segment store (speedkick) Sep 14, 2026
@edwinyyyu
edwinyyyu force-pushed the feat/event-memory-filter-routing-speedkick branch from 07b0b21 to 27724f6 Compare September 14, 2026 23:21
@edwinyyyu
edwinyyyu marked this pull request as draft September 14, 2026 23:39
@edwinyyyu

Copy link
Copy Markdown
Contributor Author

Superseded by modifying #1597

@edwinyyyu

Copy link
Copy Markdown
Contributor Author

Closed: #1597 stands in for this slice. Its typed query parameters (since/until/session_ids/source_ids/block_kinds) are the system filters the vector store evaluates, and property_filter is the segment store's alone, which is the end state this PR was an intermediate step toward. The stack is renumbered to 13 with #1597 as slice 2.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant