Skip to content

Create Qdrant collections with strict mode off - #1813

Merged
edwinyyyu merged 3 commits into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:fix/qdrant-strict-mode-off
Oct 9, 2026
Merged

edwinyyyu merged 3 commits into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:fix/qdrant-strict-mode-off

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

The fix for #1776 on feat/horizontal-scaling: a search with a user metadata filter fails on Qdrant Cloud.

The event backend sends a caller's metadata filter to the Qdrant store, and a filter may name a property that has no payload index. Qdrant Cloud turns strict mode on for every new collection, with unindexed_filtering_retrieve false, so the server refuses such a filter with 400 Index required but not found instead of scanning for it. Self-hosted Qdrant leaves strict mode off, which is why local runs pass.

The store now creates each collection with strict_mode_config=StrictModeConfig(enabled=False), the form Qdrant documents for disabling strict mode at creation. It takes precedence over the server's default, as the verification below shows.

Stack

Directly on feat/horizontal-scaling, independent of the vector store tree.

Verification

At this PR's head c2f78ea15, 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); test_qdrant_vector_store.py, unit and integration, 219 passed over REST and gRPC against Qdrant 1.19.1 in a container started with Qdrant Cloud's strict-mode defaults for new collections. TestStrictMode fails with the store at cd96676bc, whose collection reads back with strict mode on.

A manual check at beb0f577b, whose setting bb84b1b49 only moves in-line, against a Qdrant 1.19.1 server started with strict mode on by default for new collections (QDRANT__STORAGE__COLLECTION__STRICT_MODE__ENABLED=true, with UNINDEXED_FILTERING_RETRIEVE and UNINDEXED_FILTERING_UPDATE false), scrolling with a filter on a key that has no payload index:

collection strict mode filter on an unindexed key
created without strict_mode_config on refused, 400 Index required but not found
created by the store at feat/horizontal-scaling (cd96676bc) on refused, the same error
created by the store at this PR off served

The same check against Qdrant Cloud itself, through the event backend's search as #1776 describes, is in this comment.

🤖 Generated with Claude Code

edwinyyyu and others added 2 commits October 9, 2026 14:21
A filter may name a property that has no payload index, and the Qdrant
store expects the server to scan for it. Qdrant Cloud turns strict mode
on for new collections, with unindexed_filtering_retrieve false, so it
refuses such a filter with 400 "Index required but not found" and the
search fails (MemMachine#1776). The store now creates its collections with
strict_mode_config enabled=False, which overrides the server's default.

A collection created before this change keeps its strict mode; the
collection update API can turn it off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The setting has one use, at collection creation, so it is written there
with its reason beside it instead of as a class constant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@xiongzubiao xiongzubiao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced against qdrant/qdrant:v1.19.1 started with Qdrant Cloud's defaults for new collections (QDRANT__STORAGE__COLLECTION__STRICT_MODE__ENABLED=true, UNINDEXED_FILTERING_RETRIEVE and UNINDEXED_FILTERING_UPDATE false), not against Qdrant Cloud itself: a collection created with strict_mode_config={"enabled": false} serves a filter on an unindexed key, as the PR's table says. One test-coverage note inline.

assert requested[0].enabled is False

@pytest.mark.asyncio
async def test_the_server_records_strict_mode_off(self, any_qdrant_client, store):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session's QdrantContainer runs with strict mode off by default. On such a server a collection created without strict_mode_config reads back strict_mode_config: null, so this test does fail without the change and pins that the setting reaches the server. What it does not cover is the #1776 case, the setting overriding a server default that turns strict mode on: that rests on the manual check. The container accepts the same QDRANT__STORAGE__COLLECTION__STRICT_MODE__* environment the manual check used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manual check of that case has now also run against Qdrant Cloud itself, over REST and gRPC. With Cloud's strict-mode default, the store's collection at cd96676bc refused the #1776 search, and the one created at this PR served it: #1813 (comment)


🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.

@edwinyyyu

Copy link
Copy Markdown
Contributor Author

@xiongzubiao On the review's note that the check ran against a container rather than Qdrant Cloud itself: I ran the #1776 repro against a Qdrant Cloud cluster (Qdrant 1.19.2), over REST and gRPC.

The repro builds LongTermMemory on the event backend as #1776 describes. It uses QdrantVectorStore with a SQLAlchemyVectorStoreCollectionRegistry, and a collection whose indexed_properties_schema is EventMemory.expected_vector_store_collection_schema() plus EVENT_BACKEND_SYSTEM_FIELDS. It adds three episodes whose metadata and filterable_metadata are {"topic": "alpha"}, {"topic": "beta"}, and {"topic": "gamma"}, then calls search_scored("launch", ...) without a filter and with parse_filter("metadata.topic=alpha").

A collection created without strict_mode_config reads back from Qdrant Cloud with strict mode enabled: true, unindexed_filtering_retrieve: false, unindexed_filtering_update: false, and max_payload_index_count: 100. A scroll filtering on an unindexed key is refused with 400 Index required but not found.

collection created by the store at strict mode, as read back search without a filter search with metadata.topic=alpha
feat/horizontal-scaling (cd96676bc) on alpha, beta, and gamma refused: 400 over REST, INVALID_ARGUMENT over gRPC, both Index required but not found for "topic" of one of the following types: [keyword]
this PR (bb84b1b49) off alpha, beta, and gamma alpha

REST and gRPC gave the same results. The collection this PR creates still reads back max_payload_index_count: 100 and the other strict-mode limits, with enabled: false.


🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.

The Qdrant test container now starts with Qdrant Cloud's strict-mode
defaults for new collections, so the store's tests run where a
collection that leaves strict mode on refuses a filter on an unindexed
key. The strict-mode test checks that the server default is on, that
the store's collection reads back with it off, and that the collection
serves a filter on an unindexed property. It fails without the previous
commits.

The test against qdrant-client's in-process local mode is removed: local
mode neither records nor enforces strict mode, so it could only check
the argument passed to create_collection.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
edwinyyyu added a commit to edwinyyyu/MemMachine that referenced this pull request Oct 9, 2026
…l state

Only the Qdrant test container's Cloud-like defaults carry through: the
store still creates collections in the strict mode of MemMachine#1628, and MemMachine#1813's
TestStrictMode, which expects it off, stays out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
edwinyyyu added a commit to edwinyyyu/MemMachine that referenced this pull request Oct 9, 2026
Only the Qdrant test container's Cloud-like defaults carry through: the
store still creates collections in the strict mode of MemMachine#1628, and MemMachine#1813's
TestStrictMode, which expects it off, stays out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@edwinyyyu
edwinyyyu requested a review from malatewang October 9, 2026 22:29
@edwinyyyu
edwinyyyu merged commit 15f2c06 into MemMachine:feat/horizontal-scaling Oct 9, 2026
39 checks passed
edwinyyyu added a commit to edwinyyyu/MemMachine that referenced this pull request Oct 10, 2026
The test of MemMachine#1813, merged into this PR's base, in partition terms: the
server defaults new collections to strict mode, the store's collection
reads back with it off, and a filter on a partition's unindexed property
is served.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
edwinyyyu added a commit to edwinyyyu/MemMachine that referenced this pull request Oct 10, 2026
Every store refuses a batch naming a record UUID twice. A non-finite float
is already refused by the declared schema's type check, so its separate
check and tests go, and the datetime tests run on the declared key alone,
since an undeclared key is refused. The store keeps the strict mode of
MemMachine#1628 over the one MemMachine#1813 merged into the base, and MemMachine#1813's test of it off
stays out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This was referenced Oct 10, 2026
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.

3 participants