Repository navigation
[vector store scale-out 5/6] Move the Milvus store onto the collection registry, against a Milvus server - #1736
Merged
edwinyyyu merged 37 commits intoOct 9, 2026
Conversation
This was referenced Oct 1, 2026
[qdrant options] Let a deployment tune a Qdrant collection's HNSW, optimizers and quantization
#1618
Draft
edwinyyyu
force-pushed
the
feat/vector-store-milvus-registry-speedkick
branch
from
October 1, 2026 22:30
973ea51 to
30c1d79
Compare
edwinyyyu
force-pushed
the
feat/vector-store-milvus-registry-speedkick
branch
from
October 1, 2026 23:51
30c1d79 to
2f72832
Compare
edwinyyyu
force-pushed
the
feat/vector-store-milvus-registry-speedkick
branch
from
October 2, 2026 03:19
2f72832 to
196d45e
Compare
edwinyyyu
marked this pull request as ready for review
October 2, 2026 17:08
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 15, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
xiongzubiao
pushed a commit
that referenced
this pull request
Oct 15, 2026
…n registry, against a Milvus server (#1736) * Move the Milvus store onto the collection registry, against a Milvus server The Milvus store kept its catalog in a `memmachine_<namespace>__registry` collection per namespace, whose `insert` does not enforce primary-key uniqueness, so two creators of one name both succeeded, and it serialized creating and deleting a collection with locks in one process. A collection's name was the partition key on its entities, so a handle kept writing into a collection deleted and created again under its name, and a write in flight during a deletion outlived it. Each call ran the sync client on a thread of the event loop's default executor, so calls waiting on Milvus queued every other `to_thread` call of the process behind them. `MilvusVectorStore` is now a `RegistryBackedVectorStore`, and any number of processes sharing its registry may serve its collections. The store was rewritten rather than adapted; what changes: - The catalog is the collection registry in the relational database `MilvusConf.collection_registry` names, built and started by the database manager before it opens the client. The registry collections and the process-local locks go. - Every entity carries its collection's incarnation as the partition key, with partition-key isolation, and in its primary key, `"{incarnation}:{record_uuid}"`. - Declared properties are typed, indexed fields (`_p_<name>`, a datetime as TIMESTAMPTZ with its UTC offset beside it in `_tz_<name>`), and undeclared ones live in the JSON field, still filterable. Negation is the complement, as on Qdrant: a negated condition holds where the property has no value. - The vector index is HNSW_SQ with 4-bit codes and FP16 refinement, what AUTOINDEX builds on CPU from Milvus 2.6.10, named so every server builds the same; a search rescores `limit x 8` candidates. Scores are the server's. - Reads run at Milvus's default consistency level, Bounded, which the store states: a query reflects every write made at least the server's `common.gracefulTime` before it. `MilvusConf.consistency_level` goes. - The store calls Milvus through pymilvus's `AsyncMilvusClient`, and bounds every request by `MilvusConf.request_timeout_seconds`. - A purge round lists a batch of the incarnation's primary keys, at most `MilvusConf.purge_batch_size`, and deletes them. `MilvusConf.tombstone_retention_seconds` is the retention, refused below 10 x `request_timeout_seconds` + 300 seconds as for Qdrant. A declared string's VARCHAR length is `MilvusConf.max_varchar_length`. Limits the server configures stay the server's. - A delete raises unless Milvus accepted every key sent. Milvus Lite is dropped. It is a separate embedded engine that scores, indexes and enforces collection properties differently, so a store tested against it is not tested against what production runs; MemMachine's local, single-node backend is the SQLite vector store. `MilvusConf.uri` defaults to `http://localhost:19530`, a URI with no scheme, which pymilvus reads as a Lite file, is refused, and the milvus extra no longer installs milvus-lite (the lock drops it with the packages only it required). The store's tests, including the collection lifecycle contract, run against a Milvus 2.6.24 server container as integration tests, and read past the store at Strong to check what Milvus holds. Breaking: existing Milvus data is orphaned, and an existing native Milvus collection has to be dropped; no migration is included. Every Milvus store needs a relational database for its registry, which the samples, the configuration wizard and the configuration docs name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Document how the Milvus store meets the shared contracts `design/milvus_vector_store.md` records the Milvus store's layout: the shared native collection and partition-key tenancy, the composite key, the index and why it was chosen, declared properties as typed fields, the purge in batches, consistency levels, and the async client, with the measurements behind each choice. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Build Milvus handles from live registrations As for Qdrant: the Milvus handle takes its live registration in place of the namespace, name, incarnation, configuration and registry lookup, and _build_collection_handle builds it from one. The test that builds a handle on a mocked client gives it a live registration that stays current. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Take a Registration in the Milvus handle #1734 renames the registry's LiveRegistration to Registration. Names only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Say to drop the Milvus collections before upgrading The Milvus store keeps the native collection's name from main, so on an upgraded server it finds main's collection, whose schema it cannot prepare, and the first request of every new session fails. The user docs' upgrade note now names Milvus beside Qdrant. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test that Milvus queries and deletes stay within their collection Two collections of one namespace and configuration share a native collection. test_a_query_returns_only_its_own_collections_records gives both records that match the same filters and checks that each answers with its own records alone, unfiltered, on a declared property, on an undeclared one, and under a negation. It fails when _query lets the property filter replace the incarnation filter, ORs the two, or drops the incarnation filter: Milvus 2.6.24 refuses each of those searches under partition key isolation, and with isolation off as well the query returns the other collection's records. test_same_uuid_can_exist_in_different_logical_collections now also deletes the shared UUID through one collection and checks that the other still holds and returns its record. It fails when _delete deletes by a record_uuid filter in place of the composite key. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test the Milvus score threshold and ranking for every metric test_a_threshold_keeps_the_matches_within_it_best_first upserts three records at increasing distance from the query, out of order, and queries with a threshold between the middle and the farthest: the two nearest match, best first, with the metric's scores. For Euclidean the distances are 1, 2 and 5 and the threshold 3, between the middle distance and its square; dot product and cosine take their own thresholds. It fails when the threshold is applied to Milvus's squared Euclidean distance before the square root, when every metric keeps scores at or above the threshold, when every metric keeps scores at or below it, when the matches are sorted in reverse, and when every metric's score is the square root of Milvus's distance. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test that the Milvus store reads at Bounded or stronger test_the_store_reads_at_bounded_or_stronger creates a native collection, writes, queries, deletes the collection and drains the purge, then checks that describe_collection reports Bounded and that no search, query, get or hybrid search the store made names a level other than Bounded or Strong. The purge's listing and the tombstone retention rely on reads lagging writes by at most common.gracefulTime. It fails when the native collection is created at Session or Eventually, when the store's search names Eventually, and when the purge round's listing names Session. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Check random Milvus filters against the in-memory evaluator test_random_filters_agree_with_the_model writes 48 records whose properties cover every type, declared and undeclared, with datetimes at offsets from -12:00 to +14:00, instants a microsecond and a second apart, strings with quotes, backslashes and non-ASCII letters, and each property missing a quarter of the time. 200 seeded filter trees of Comparison, In, IsNull, And, Or and Not, with values of their properties' types, must select the UUIDs evaluate_filter selects, after one settle. The trees compare with != only through Not(=): on a missing property the store's != holds, as the complement of =, while evaluate_filter's does not. It fails when a declared datetime is written without its offset, when a negated condition no longer holds on a missing property, when negation is not pushed through And and Or by De Morgan's laws, and when undeclared datetimes are encoded at their own offset in place of UTC. Dropping the store's own UTC normalization of datetime filter literals is an equivalent mutant: Comparison and In normalize datetime values to UTC when they are built, so the store's call never changes a value. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Check a random sequence of Milvus operations against a model test_a_random_sequence_of_operations_agrees_with_a_model runs 60 seeded steps on two collections sharing a native collection: upserts of new and replacing records, deletes of present and absent records, deletion and recreation of a collection, which then reuses its UUIDs, and purge drains. Twelve UUIDs serve both collections and every life of each. After every step each collection holds exactly the model's records, once each, read at Strong; its settled queries, unfiltered and under random filters, select what evaluate_filter selects; a replaced record's old property values no longer select it; and after a drain no deleted life keeps a record. It fails when _upsert inserts in place of upserting, when the primary key leaves out the incarnation, and, by timing out its bounded drain, when the purge round never deletes. test_upsert_removes_stale_filter_fields now also checks that a filter on the replaced value finds nothing, which inserting in place of upserting fails. test_upsert_calls_native_upsert compared the entity the store built with itself; the model test checks the state an upsert leaves instead, so it is removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test Milvus purge rounds on a dropped collection and on two purgers The store fixture now builds its store with _started_store over a per-test registry database, and other_store builds one as another process would beside it: its own Milvus client and registry connections, over the same Milvus and registry database. test_a_purge_round_on_a_dropped_native_collection_retires_the_tombstone deletes a collection, drops its native collection, and drains the purge: the round raises nothing, and the next call finds nothing due. It fails when _purge_round no longer checks that the native collection exists, since Milvus refuses the listing. test_purgers_on_two_stores_reclaim_the_deleted_collections_alone deletes four collections of seven records each beside a live one in the same native collection, and drains both stores' purges at once in batches of 3. Every deleted collection ends empty, the live one keeps its seven records, and neither store finds anything due. It fails when a round reports finding nothing after deleting its batch, and when the listing leaves out the incarnation filter. test_a_purge_round_milvus_does_not_accept_in_full_raises gives a store a mocked client whose delete accepts one of the two keys the round listed: the purge raises. It fails when _purge_round no longer checks the count. test_a_delete_milvus_does_not_accept_in_full_raises now has Milvus accept one of two keys, so a check for zero alone fails it, and matches the exception's type rather than its wording. The drain in these tests is bounded, so a purge that never ends fails the test instead of hanging it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test concurrent preparations of one Milvus native collection Milvus 2.6.24 answers two or more identical create requests for one collection, sent at once from separate clients, with success every time (four clients, five trials), and identical concurrent index requests likewise; a create whose schema differs gets code 1100, "create duplicate collection with different parameters". So a real server cannot make a racing creation take the store's already-exists branch. test_a_creation_that_loses_the_native_collection_to_another_completes_it covers that branch with a mocked client: the native collection is missing when checked and its create is refused as already existing. The creation indexes and loads the native collection, and the collection opens. It fails when the already-exists tolerance is dropped, and when a creation that loses the create returns without indexing and loading. test_a_creation_finding_the_native_collection_mid_creation_completes_it runs two stores on separate clients and registry connections. The first creates the native collection and is held before its indexes until the second's creation of another collection, which finds the native collection present, has returned and been written to and queried. Both collections end usable with every index. It fails when a creation that finds the native collection present skips its index and load steps. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test Milvus settings with values other than their defaults The configuration, database manager and store tests set the request timeout, the declared-string length and the purge batch size to values other than their defaults, so a default passed in place of the configured value fails them. - test_every_request_carries_the_timeout runs at a 17-second timeout. It fails when the store or the handles it builds use 30 in place of the configured timeout. - test_milvus_client_kwargs_forwarded expects the configured 7-second timeout on the client, and test_milvus_creates_vector_store expects 7, 2048 and 500 in the store's parameters. Each fails when async_get_milvus_client passes the default for that setting. - test_parse_valid_storage_dict parses max_varchar_length 2048 and purge_batch_size 500 from the configuration. It fails when either field reads another key, so the configured value is ignored. test_milvus_conf_rejects_a_length_or_batch_size_that_is_not_positive and test_a_setting_that_is_not_positive_is_refused check that MilvusConf and MilvusVectorStoreParams refuse 0 for each of these settings. Each fails when that field's positivity constraint is dropped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test concurrent churn across two Milvus stores with purgers running test_churn_across_two_stores_with_purgers_keeps_every_collection_exact runs six workers, three on each of two stores with their own clients and registry connections over one registry database, for 40 seeded steps each on three collections sharing a native collection: upserts and deletes of records the worker alone owns, owner-filtered queries, and deletion and recreation of the shared collections. Each store's purger drains, in batches of 2, after every collection deletion, and each purge round begins at a settled read, as the tombstone retention would give it. A ledger records, by the incarnation each operation reached, the records upserts and deletes that returned leave, the records sent, and the upserts that raced their collection's deletion. Only the documented outcomes of a lost race may be raised, and the whole churn is bounded, so a deadlock fails it. During the churn, a query returns only records its owner sent to its incarnation. Once the churn is quiet, settled and drained, each live collection holds exactly its ledger's records, once each, with their latest values, and its settled owner and tag filters select them; the deleted collections keep only records of upserts that raced their deletion. In three runs the churn made 16 to 18 collection lives, 7 to 10 upserts raced a deletion, and queries, deletes and recreations met stale handles, existing names and deleted reservations. It fails when _upsert inserts in place of upserting, when the primary key leaves out the incarnation, when the purge listing leaves out the incarnation filter, when a round reports finding nothing after its batch, and, by timing out, when the purge round never deletes. The two-purgers test now builds its batched stores with the same _with_purge_batch_size helper. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Give the Milvus wiring tests' collection registry a file database The Milvus wiring tests built their collection registry on the module's in-memory SQLite configuration, which the Qdrant wiring tests' change below replaced with a file per test, since the registry refuses an in-memory or single-connection engine. Each Milvus wiring test now puts the registry in a SQLite file under its own tmp_path, as the Qdrant ones do. What each test asserts is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Test that the Milvus store keeps a match scoring exactly the threshold The contract says a match scoring exactly the score threshold is returned, and the Milvus store already keeps it. It now has the test the Qdrant and SQLite stores have, on cosine, dot and Euclidean: a threshold equal to a match's reported score keeps the match, and one a step better drops it. It fails with the store's threshold check made strict. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Use serial commas in the Milvus document and the store's comments Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Name the Milvus filter helpers for what they produce The module's expression helpers had names that did not say what they make: `_expr_string`, `_literal`, `_declared_literal`, `_fits`, `_absent`, `_condition`, and `_milvus_filter` are now `_expression_string_literal`, `_property_value_literal`, `_declared_property_value_literal`, `_is_comparable_with_declared_type`, `_property_absent_expression`, `_condition_expression`, and `_filter_expression`; the handle's `_score` is `_score_from_distance`. No behavior changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Send Milvus filter strings as UTF-8, not ASCII escapes json.dumps escapes a character outside the Basic Multilingual Plane as a UTF-16 surrogate pair, which Milvus's expression parser refuses, so a filter on a string such as "café 😀" failed to parse on a declared and an undeclared property alike. The literal now keeps its characters as UTF-8. The new test filters on such a string on both kinds of property; it fails with the ASCII escaping. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Compare an undeclared Milvus property only with values of a comparable type An undeclared property's condition compared only its stored value, so a string equal to an undeclared datetime's stored text matched it, where the declared path, the other stores' JSON properties, and the in-memory evaluator match nothing. The condition now also requires the stored type tag to be one the value compares with, by the rule the declared path already uses: a bool with a bool, an int or float with either, and any other value with its own type. The new test filters an undeclared datetime by its stored text, and an undeclared float by an int; it fails without the type check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Index and load a part-made Milvus collection before a purge round lists it A creation that failed after creating the native collection, before its indexes or its load, leaves it unindexed and unloaded, and its cancelled collection's tombstone is due for purge. The round's listing query then raised "collection not loaded" on every claim, so the tombstone was dead-lettered unless another creation of the same namespace and configuration completed the collection first. A round now creates the missing indexes and loads the collection, the steps preparation already takes, before it lists. The new test fails a creation at its index step, then drains the purge: it fails with "collection not loaded" without the change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop the Milvus store's already-exists guard, which no supported server reaches Preparation caught a create refused as "already exist" by matching the message text. Milvus answers a create of an existing collection with the same schema with success, and refuses one with another schema as "create duplicate collection with different parameters" (code 1100, Milvus 2.6.24), which the text did not match, so the branch ran only under the mocked client of its own test. The guard, its message match, and that test go; the concurrent preparation tests cover racing creators against a real server. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Refuse a metric Milvus does not support before reserving the collection The store checked the metric while preparing storage, after the registry had reserved the name, so a refused configuration cost a reservation and its cancellation, and stayed pending until deleted if the cancellation failed. create_collection and open_or_create_collection now check it first. The test, for both operations, finds nothing to purge after the refusal; it fails with the check back in preparation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Bound the Milvus connectivity check by the request timeout validate_milvus_client listed collections with no timeout, and the client's own timeout bounds only connecting, so a Milvus that accepts the connection and never answers hung validation, the one request request_timeout_seconds did not bound. The check now passes the configured timeout, on a store's first build and in build_all's validation. The new test checks both paths pass it; it fails with the timeout dropped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Require testcontainers 4.15.0, the first with the community modules The shared conftest imports MilvusContainer from testcontainers.community, which 4.14.2, the declared floor, does not have, so an install at the floor could not import the conftest or collect any server test. The lock already pins 4.15.0; only the floor moves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Dispose a mocked-client purge test's registry engine when it fails The test disposed the registry engine it opened only after its last assertion, so a failure left the engine and its aiosqlite thread open for the rest of the session. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop the Milvus store's re-sort of search results The store sorted each query's matches by score, though Milvus returns hits best first and the one transform the store applies, the square root of a Euclidean distance, keeps their order. On Milvus 2.6.24, 3,000 random vectors in each metric gave 450 of 450 result lists in order, filtered and not, at limit 100. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Say that a Milvus native collection keeps its VARCHAR length The store sizes a declared string's VARCHAR field when it creates the native collection, and a later preparation that finds the collection there leaves it as it is, so a changed max_varchar_length applies only to native collections created afterward. The setting's description, the configuration docs, and the design document now say so. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Match no declared Milvus int property with a float filter value The declared path counted an int and a float as comparable either way, so a float filter value on a declared int property reached Milvus as a float literal against an INT64 field, which Milvus refuses to parse ("cannot cast value to Int64", code 1100). A float now compares only with a float property, and matches no int one, as a value of another type matches nothing; an int still compares with a float property by value. The new test filters a declared float property with an int and a declared int property with a float; it fails with the float counted as comparable with an int property. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Create Milvus native collections at Bounded by name The store's reads run at the collection's consistency level, and the purge's listing and the tombstone retention rely on that level being Bounded or stronger. The store named no level when it created a native collection, so the level was whatever pymilvus defaults to, which a pymilvus release could change without the store noticing. The store now creates each native collection at Bounded. The test checks the create request names Bounded; it fails with the keyword removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Report Milvus store operation latencies to a metrics factory The database manager built the Milvus store's parameters without a metrics factory, and MilvusConf had no metrics_factory_id, so a deployment could not configure one and every timed Milvus operation recorded nothing, where the Qdrant and Neo4j stores report theirs. MilvusConf takes metrics_factory_id, and the manager passes the factory it names, as it does for Qdrant. The wiring test checks the factory reaches the store's parameters; it fails with the factory not passed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Name Milvus's cap on a record's undeclared properties A record's undeclared properties are stored in one JSON field, and Milvus refuses an upsert whose JSON exceeds its common.JSONMaxLength setting, 65,536 bytes by default (internal/proxy/validate_util.go and pkg/util/paramtable/component_param.go at v2.6.24). The design document's list of server-configured limits and the database docs now name it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Refuse a Milvus Lite URI by its .db suffix, as pymilvus selects Lite The configuration refused a URI with no "://" as a Milvus Lite file. pymilvus serves a URI ending in .db with Lite and passes a unix: URI to the server as a socket address, so a file:// URI ending in .db passed the check and reached Lite, and a unix: socket URI was refused as a Lite file. The check now refuses a URI ending in .db, and its message names the Lite file. The tests refuse ./milvus.db, milvus.db, and a file:// URI ending in .db, and accept http, https, and unix: URIs; the file:// and unix: cases fail with the "://" check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Write a declared Milvus datetime as its UTC instant The entity builder wrote a declared datetime's text with its own offset, while both filter literals convert to UTC first. Milvus refuses a TIMESTAMPTZ string whose offset has a seconds component ("invalid timezone name", code 1100 on 2.6.24), as local mean time and other historical offsets have, so such a record could not be upserted. The builder now writes the UTC instant; the offset field already keeps the offset written. The new test upserts a datetime at +00:19:32, reads back its instant and offset, and matches it by its instant; it fails with the original offset written, at Milvus's refusal. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Halve a Milvus upsert refused as too large, as the Qdrant store does Milvus's proxy refuses a request over proxy.grpc.serverMaxRecvSize (64 MiB unless configured) with gRPC's RESOURCE_EXHAUSTED status, before writing any of it, and pymilvus raises that status as it is: on 2.6.24 a 72 MB upsert was refused in 0.2 s with nothing stored. So a batch the Qdrant store accepts by halving it failed outright on Milvus. The store now halves an upsert refused with RESOURCE_EXHAUSTED until its halves fit or a single entity is refused. Any other error raises at once, since a timed-out request may still be applied. The store imports grpc for the status, so the milvus extra declares grpcio: 1.59.0 is the first release with Python 3.12 wheels, and grpc.aio.AioRpcError and grpc.StatusCode are older. The integration test upserts 1,200 records with a 60,000-character property, about 72 MB, and fails with RESOURCE_EXHAUSTED without the halving. The mocked-client tests pin the halving, which its test fails without, a single refused entity raising, and a timeout or another refusal sent once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Name Milvus's datetime offset fields for their unit A declared datetime's UTC offset was stored in an INT32 field named _tz_<key>, which left its unit to the code. The field is now _tz_offset_seconds_<key>, under the prefix _TZ_OFFSET_SECONDS_FIELD_PREFIX; its value is unchanged, the offset in seconds. The schema test and the two datetime round-trip tests read the new names. A property key is at most 32 bytes, so a declared property's field names are at most 51 characters, within Milvus's proxy.maxNameLength (255 unless configured). A new test declares a datetime under a 32-byte key, writes it, reads both fields back, and matches it by its instant; the design document states the bound. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Keep a declared Milvus datetime as its UTC instant alone A declared datetime was stored as its UTC instant in a TIMESTAMPTZ field and its UTC offset in an INT32 field beside it. A filter compares only instants, and a search answers with record UUIDs and scores, so nothing reads the offset back; the store the vector records are derived from keeps it. The offset fields go: every declared property, a datetime included, is one field, `_p_<key>`, of those proxy.maxFieldNum allows. Undeclared properties keep their type-tagged JSON, offset included. The schema test asserts the native collection's exact field set, the datetime round-trip tests check that the stored instant is the written one, in UTC, and the longest-key test checks the one field. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 15, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 15, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 15, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 15, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 16, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 16, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 16, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 16, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 19, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 19, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 19, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 19, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 20, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 20, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
This was referenced Oct 20, 2026
Open
Draft
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 26, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
that referenced
this pull request
Oct 27, 2026
…string-keyed partitions, built from the deployment's configuration (#1627) * Make a vector store one collection, its partitions keyed by string, built by the composition root A vector store was a factory of logical collections, each identified by a (namespace, name) pair and created with its own dimensions, metric and declared schema; Qdrant and Milvus shared one native collection among logical collections of equal configuration, under a name derived from a hash of that configuration, and the collection registry mapped (namespace, name) to it. A store is now one collection, and what it held are its partitions. `VectorStore` is built with its vector store name, dimensions, similarity metric and declared properties, and every partition shares them; a partition's handle answers the metric and the declared properties. `create_partition(key)`, `open_or_create_partition(key)`, `open_partition(key)` and `delete_partition(key)` take one string key, and `purge_deleted_partitions()` replaces `purge_deleted_collections()`. A partition is the records carrying its incarnation in the store's native collection (Qdrant, Milvus) or a pair of tables (the SQLite stores). "Collection" names only the native Qdrant or Milvus collection, whose create and delete calls keep their names. `startup()` prepares the storage a store's partitions share: the native collection and its indexes, or the SQLite tables. Vector store names match `[a-z0-9_]+` and are at most 32 bytes, the rule property keys follow, so every name works on every backend: the Qdrant store names its collection by the vector store name, and the Milvus store by `sys_` followed by it, since Milvus requires a leading letter or underscore and names its own internals with a leading underscore. The hash-derived native names go. A partition key is any non-empty string of at most 255 bytes free of NUL characters, since no store names a native resource after one. In the vector store's utilities, a check that returns a bool is named as a predicate, `is_valid_identifier` and `is_valid_filter`, and a check that raises starts with `validate_`: `require_declared_types`, `require_dimensions`, `require_valid_limit`, `require_valid_query_vector` and `require_valid_score_threshold` become `validate_declared_types`, `validate_dimensions`, `validate_limit`, `validate_query_vector` and `validate_score_threshold`, the Milvus store's `_require_every_key_accepted` becomes `_validate_every_key_accepted`, and the configuration's `_require_retention_floor`, which checks a store's tombstone retention, becomes `_validate_tombstone_retention`. The collection registry becomes the partition registry, keyed by partition key within one store. Its tables, `partition_registry_pt` and `partition_registry_gc`, are shared by every store on a database and keyed by vector store name, so the registries of several stores share one database and nothing else. Its reservations, registrations, unregistration and purge rounds are the collection registry's, with the partition key in place of (namespace, name): the primary key arbitrates a reservation, a confirmation or cancellation acts on one incarnation, a registration fences on `require_current()`, and a purge round claims a tombstone under a lease with a claim generation and counts its attempts. The registry records no configuration per partition: every partition shares its store's dimensions, metric and declared properties. On the registry-backed base, the storage a partition keeps of its own is prepared by `_prepare_partition_storage(key, incarnation)`, between its reservation and its confirmation; the Qdrant and Milvus stores keep nothing per partition, and a purge round works on one incarnation in the store's own collection. The errors follow the vocabulary: `VectorStorePartitionAlreadyExistsError`, `VectorStorePartitionDeletedError`, `VectorStorePartitionHandleStaleError` and `VectorStorePartitionPendingError`. `DatabaseManager.get_vector_store(backend, vector_store_name=, vector_dimensions=, similarity_metric=, indexed_properties=)` builds and caches one store per (backend, vector store name), starting its registry and then the store. The resource manager starts a purge sweeper for each store the first time it hands the store out. The Qdrant and Milvus backends name their registry database with `partition_registry`, in place of `collection_registry`. The event backend's store is named by a UUIDv5 of its embedder id in the UUIDv5 of its backend's key in a namespace fixed for the event backend, written as 32 hexadecimal digits, so neither the key nor the id is constrained, and stores on different backends never share a name. The event backend opens a session's partition with `open_or_create_partition`, which retries the races of a partition another worker is creating or deleting. Semantic memory's one store is named `semantic_memory` whatever its embedder and holds every org's features in one partition, `semantic_memory`. The SQLite stores change shape only: their registry tables become `vector_store_sqlite_pt` and `vector_store_sqlite_vec_pt`, keyed by vector store name and partition key, the pending-operation log is keyed the same way, a partition's tables and index file are named by the vector store name and a SHA-256 digest of the partition key, and the stores of one backend share its engine. No migration is provided: existing SQLite vector data, and the Qdrant and Milvus collections and registry tables of earlier releases, are left unread. The lifecycle contract the registry-backed stores satisfy becomes the partition lifecycle contract, and the in-memory test store's collection becomes a partition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restate the vector store design documents for one-collection stores and their partitions The design documents came from #1733-#1736, where a store holds logical collections addressed by namespace and name, each with its own configuration. A store is now one collection, with its dimensions, metric and declared schema fixed at construction, and a partition is one tenant's records in it, addressed by key. The documents say so: - the collection registry document becomes the partition registry document: a registry belongs to one store and is addressed by partition key; its tables are `partition_registry_pt` and `partition_registry_gc`, and a tombstone needs no location, since the incarnation alone finds a dead partition's records in the store's native collection; a decision records that a store is one collection; - the Qdrant and Milvus documents lay out one native collection per store, named by the vector store name (`sys_` and the name on Milvus), created at startup, where a partition's creation makes nothing in the backend; - the overview, isolation, consistency and purge documents speak of partitions and `purge_deleted_partitions`, and the overview describes the store and its partitions. The measurements and their conditions are unchanged; the purge document names the #1734 commits they were taken at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Describe a vector store by what it is built with, not as a collection The general description called a store "one collection: a body of records searched together", which borrows the Qdrant and Milvus term for the abstraction and says something false: every search runs within one partition. A store is constructed with a name, a dimensionality, a similarity metric, and a declared schema, and every record it holds has that dimensionality and schema; the interface, the SQLite stores, the database manager, the user documentation, and the design documents now say so. "Collection" names only a Qdrant or Milvus native collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name each SQLite partition's resources by an incarnation, as the registry-backed stores do The SQLite stores named a partition's tables and index file by a SHA-256 digest of its key, so a partition created again under a key took the deleted one's names: a handle held across the deletion wrote into the new partition (#1536), and a creation that lost a race to create a key evicted the winner's search engine, whose records a restart then lost (#1834). A partition row now carries an incarnation, a UUID minted when the partition is created, as a Qdrant or Milvus registration does. A partition's records table, vec0 table, index file, and cached search engine are named or keyed by it, and so are SQLiteVectorStore's pending operations, which deleting the partition deletes by a foreign key on the incarnation. A partition created again gets resources of its own, and a store's startup replays only its own partitions' operations. Every operation through a handle checks its incarnation first and raises VectorStorePartitionHandleStaleError once the partition is deleted, an operation with nothing to do included, as the registry-backed stores do. A write's check is a self-checking UPDATE of the partition row: the driver defers BEGIN until the first data-modifying statement, so the UPDATE opens the write transaction and holds a deletion off until it commits. For the same reason, creation writes the partition row before its tables and deletion deletes the row before dropping them, so the DDL rolls back with a failed transaction instead of leaving tables no partition names. A new partition's search engine is cached once its row commits, so a failed creation leaves none behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * State that a handle of a deleted partition raises, on every store With the SQLite stores checking each handle's incarnation, no store acts on a partition created again under a deleted one's key, so the partition contract states the stale-handle error outright instead of allowing an implementation that cannot tell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the SQLite stores' creation and deletion to the partition contract A creation that finds its key taken raises VectorStorePartitionAlreadyExistsError, and open-or-create returns a handle on the partition, whoever created it. In the SQLite stores, two callers creating one key at once both found no row, and the one whose INSERT lost at the partition row's primary key raised SQLAlchemy's IntegrityError from either method. Creation now inserts the partition row without reading first and raises VectorStorePartitionAlreadyExistsError when the primary key refuses it. Open-or-create reads, then creates, retried as the registry-backed stores' is, so a caller that loses the creation opens the winner's partition; the attempt bound is now one constant for every store. An index save that the partition's deletion overtook could write its file after the deletion unlinked it, leaving the deleted records' vectors on disk with nothing to reclaim them, since no later partition is named by that incarnation. The save's transaction now opens with a self-checking UPDATE of the partition row and unlinks the file when the row is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Serve every session's event memory from the deployment's configuration, in one store A vector store has one dimensionality and one metric, so the event backend named its store by a UUIDv5 of the backend and the embedder, keeping apart the stores of sessions configured with different embedders. A deployment instead keeps every session's event memory in the vector store, segment store, and embedder its episodic memory configures: a session naming others is refused with a ConfigurationError when it is opened, since its data would live elsewhere. The event store then takes one fixed name, `event_memory`, as semantic memory's takes `semantic_memory`, and the resource manager protocol exposes the configuration the check reads. The configuration documentation states the rule, and that changing a deployment's embedder needs the event memory's vectors rebuilt first; the database documentation and the partition registry document name the two stores instead of describing per-embedder names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Take SQLite's write lock at BEGIN in the SQLite vector stores' write transactions The SQLite vector stores read before they write in one transaction: the stale-handle check, creation's existence check, deletion's lookup. The driver's deferred BEGIN ran those reads outside the transaction, and the stores worked around it statement by statement: a self-checking UPDATE first, the partition row before any DDL, and creation decided by the primary key, with retries. The database manager now sets up a SQLite vector store backend's engine when it creates it, before any connection is pooled, so the setup covers every connection: every transaction begins explicitly, with a deferred BEGIN for reads and BEGIN IMMEDIATE for a transaction opened with write_transaction. A write transaction's reads, writes, and DDL are then one atomic unit, and reads still never take the write lock. The hooks and write_transaction live in memmachine_server.common.sqlite_transactions, and the stores require an engine set up so. This is #1609's mechanism, installed where the engine is created instead of by the store. Every write transaction of both stores opens with write_transaction. The stale-handle check is a read inside it; creation and open-or-create check and add the partition in one transaction, and deletion looks it up and drops it in one. The open-or-create retry goes, and with it the attempt bound the SQLite stores shared with the registry-backed stores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the shared SQL engine setup and transactions in common/sql.py The module holding enable_sqlite_explicit_begin and write_transaction is named for what the server's SQLAlchemy-backed stores share, engine setup and transactions, instead of for one of its functions; write_transaction serves stores on any dialect, and only the SQLite-specific functions say so in their names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the naming and sweeping rules the redesign states but did not test A partition key's limit is in bytes: every store now refuses 128 two-byte characters, 256 bytes in fewer than 256 characters. Each store's params refuse a vector store name outside the rule, as the registry's already did, so a name one backend would refuse never reaches any. The partition registry keeps a key of the longest length with characters no identifier allows on every dialect it runs on, PostgreSQL included. Stores of two names on one backend get a purge sweeper each, since each store purges its own registry's deleted partitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Roll back a write transaction whose commit fails SQLite keeps a transaction whose COMMIT failed open, and with it the write lock: a COMMIT that waits out the busy timeout behind a reader, for one, reports the database locked and leaves the transaction to be retried or rolled back. write_transaction committed after its block and left the rest to the session's close, which after a failed commit returns the connection to the pool without a rollback. The connection then held the write lock for good, and the next transaction to begin on it failed, since SQLite cannot begin a transaction inside another. write_transaction now runs its block in the session's transaction context, which commits when the block exits and rolls back when the block raises or the commit fails, as engine.begin() and session.begin() do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Open the SQLite vector stores' startup DDL as a write transaction too On an engine set up for explicit BEGIN, every transaction that writes opens as a write transaction, and the SQLite vector stores' startup create_all still ran in engine.begin(): its table checks read before its DDL writes, which a deferred BEGIN cannot upgrade while another connection holds the write lock. write_connection, for work on a connection rather than a session, takes the write lock at BEGIN and commits or rolls back as write_transaction does, and both stores' startup uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Annotate the write transaction context managers as async generators asynccontextmanager deprecates annotating the decorated function as returning an AsyncIterator; write_transaction and write_connection are annotated as the AsyncGenerator they are, as the codebase's other context managers are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give every vector store's parameters one base, VectorStoreParams Every store is constructed with a name, a dimensionality, a similarity metric and a declared schema, and the SQLite, sqlite-vec, and registry-backed stores' parameters each declared the four, with the vector store name rule's validator, anew. They are now VectorStoreParams in vector_store.py, beside the store they describe, and each store's parameters inherit them and add what its backend needs; a store keeps what its name names, and the metrics its backend accepts, in its own parameters' description. Each store is constructed as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the session write transaction write_session, as write_connection is named Both context managers open a write transaction, and write_connection is named for what its block receives; the session one is now write_session for the same reason, and its tests are named to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Describe only what a caller chooses in the registry-backed stores' parameters The Milvus and Qdrant parameters described how each store lays its native collection out: the collection's name, a declared property's field or payload index. A caller chooses none of it, and the constants that name the native collection and fields, the design documents, and the user documentation say it already. The parameters keep what bears on the values a caller passes: Milvus's similarity metric is cosine, dot, or euclidean, and stores of different names may share a client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the redesign's docstrings and comments to what each place needs An audit of the docstrings and comments the redesign added or changed against the codebase's writing rules. Removed or cut down: text that named consumers or callers (the composition root, services, sessions, the registry beside the partition key rule), implementation layout in the vector store and registry ABCs and in parameters (native collections, payload indexes, tables, rows), restatements of a contract or of the code at a second place (the module docstring, partition and store class docstrings, the cascade, the hook's mechanism), examples, negatives, a figure that copies a constant, and comments that ordered steps without a reason. A docstring that said every read of the SQLite partition table names the vector store, which incarnation reads no longer do, goes. That stores of different names may share a client moves to the Qdrant and Milvus client fields, and the registry's name follows the vector store name rule instead of restating it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass a vector store's dimensions, metric, and declared properties as one schema A store's dimensions, similarity metric, and declared properties travelled as three values through `get_vector_store` on the resource manager protocol, its implementation, and the database manager, through each backend's builder, into each store's parameters, and from each store into each partition handle. `VectorStoreSchema` holds the three, with the dimension bound and the property rules, and travels as one value; it is frozen, since a store and its handles share it. `VectorStoreParams` holds the vector store name and the schema. The name stays beside the schema rather than in it: with the backend, it is the key the database manager and the purge sweepers find a store by, and it names the store's native collection, registry, and rows, while the schema is what the store is built with. "Schema" now names that whole shape, so text that used it for the declared properties alone says "declared properties", and `EventMemory.expected_vector_store_collection_schema()` becomes `expected_vector_store_indexed_properties()`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name what the redesign's code and tests still call a collection The redesign leaves "collection" to Qdrant's and Milvus's native collections; these places still used it for a store or a partition. - The filter event memory hands its vector store is `vector_store_filter`. - Two `EventMemory` docstrings no longer say a method raises when the collection schema lacks a field: neither method checks that. - A long-term memory comment names the vector store's metric, not the collection's. - The store tests' handles are `partition`, `partition_a`, `partition_b`, and `partition_2`, and their test names, docstrings, partition keys, and section headers say store or partition; the semantic storage and event memory tests' partitions are `vector_store_partition`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the semantic storage sync test for its partition Its fixture is the vector store partition that was a collection before the redesign. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name a configured vector store's identifier `vector_store_id` `get_vector_store`'s first argument, and the database manager's and resource manager's keys, called the configured vector store a backend, a word the semantic manager's `storage_backend` and the event backend already use for other things. The value is the id of a vector store configured under `resources.databases`, the value `episodic_memory.long_term_memory.vector_store` holds, so it is `vector_store_id`, beside the `vector_store_name` of a store on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Set up the SQLite vector stores' engines where they are created The SQLite vector store registered its foreign keys pragma, and the sqlite-vec store its extension loader, on the engine it is handed, which other stores on the same configured vector store share. A connect listener reaches only connections opened after it, so this held only while no connection was pooled before a store was built. The database manager now registers both where it creates a SQLite vector store's engine, beside `BEGIN IMMEDIATE`, and the stores state the requirement on their engine parameter. The SQLite vector store still registers the pragma on the synchronous engine it creates itself. `enable_sqlite_foreign_keys` moves to `common/sql.py`, beside `enable_sqlite_explicit_begin`, and the sqlite-vec loader is `load_sqlite_vec_extension`, beside its store. The database manager's tests check that its engines enforce foreign keys and load sqlite-vec on a new connection; the store tests set up their engines the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Close the SQLite vector store's own engine at shutdown The store creates a synchronous engine for the filter it evaluates during a search, and nothing closed it: a filtered query left a connection in its pool after `shutdown()`. Shutdown now disposes it; a store started again opens new connections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refuse a request for a store built with another schema The database manager builds a store on the first request for its name and returned it to every later request, whatever schema that request asked for, so a caller asking for other dimensions, metric, or declared properties got a store built for the first caller and failed later on a record or a filter. A later request naming the store with another schema now raises `VectorStoreConfigurationError`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Start the SQLite vector stores' DDL as every other store does `916cb57e8` opened both SQLite vector stores' startup `create_all` with `write_connection`, so that concurrent first starts on one file serialize. Every store that creates its tables at startup has that race, PostgreSQL included, and taking schema creation out of startup removes it for all of them (#1570). These two stores start in `engine.begin()` like the others, and `write_connection`, with no other use here, leaves with its tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Skip the sqlite-vec engine test where sqlite3 cannot load extensions The database manager's test that a sqlite-vec store's engine loads sqlite-vec opens a real connection, and sqlite3 on the macOS runners is built without `enable_load_extension`. It takes the condition the sqlite-vec store's tests already skip on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refuse a project whose event memory is not the deployment's when it is created A session whose event memory names a vector store, segment store, or embedder other than the deployment's was refused only when it was opened. Creating it succeeded, and deleting it opens it, so it could never be deleted. Creation now refuses it with `InvalidArgumentError`, a 422 from the project API, through the same check opening uses (`is_deployment_event_memory`). A deployment that configures no event memory refuses event projects the same way. The configuration docs say so, and that changing the deployment's vector store, segment store, or embedder, or its backend, leaves existing sessions' event memory unopenable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Say deployments on different backends need different registry databases The registries are keyed by vector store name, and event memory's and semantic memory's names are fixed, so two deployments on different backends that keep their registries in one database would share registrations and tombstones but not records: one's purge could retire the other's tombstone without reclaiming its records. The docs said such deployments share stores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Answer a partition's schema as one value A partition handle answered its store's similarity metric and declared properties as two properties, though it is built with one `VectorStoreSchema`. `VectorStorePartition.vector_store_schema` answers that schema, and each handle returns the one it holds. Event memory and long-term memory read the metric and properties from it, and the in-memory test partition is built with a schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Share one search engine among a partition's concurrent first opens `_get_or_create_vector_search_engine` checked the engine cache, awaited the read of `index_saved`, and then stored its engine, so two opens of one partition that both missed the cache each built an engine, and the second replaced the first. The first handle's writes then went to an engine no later handle used, and they dropped out of the index once the cached engine saved it. The cache now keeps the engine stored first; a test opens a restarted store's partition twice at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Create the SQLite vector stores' partitions as the other stores do Both SQLite stores created a partition through `_add_partition_if_absent`, a get-or-create that returned the incarnation and whether it added the partition, each caller discarding one; `SQLiteVectorStore`'s also built the new partition's search engine and cached it after commit. They now follow the segment store and the partition registry: `_insert_partition` inserts the registry row under a minted incarnation, so the unique constraints decide the key and the incarnation, and creates the tables in the same transaction. `create_partition` mints again when an insert is rejected for a reason other than the key being taken, up to `_MAX_MINT_ATTEMPTS`, and `open_or_create_partition` reads, then inserts, retried the same way. A handle's search engine is built when the handle is opened, as `open_partition` already did, so `SQLiteVectorStore` now builds one at construction to refuse a metric or dimensionality its engine cannot serve before any partition exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Raise the stale-handle error from a query its partition's deletion overtakes `SQLiteVectorStorePartition.query` checks the partition is live, then searches, reading the records table per candidate under a filter, and then reads the matches' rows. A deletion committed in between drops the table, so the query failed with SQLAlchemy's `OperationalError` ("no such table") rather than `VectorStorePartitionHandleStaleError`. A query that fails with `OperationalError` now reads the registry again: if the partition is gone it raises the stale-handle error, and otherwise the original error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name a store's schema `store_schema` `vector_store_schema` spelled out its type's whole name at every use. `store_schema` names the same value everywhere: `get_vector_store`'s argument, the stores' parameters' field, the handles' constructor argument and attribute, and `VectorStorePartition.store_schema`. A field named `schema` would shadow Pydantic's `BaseModel.schema`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave a store built with another schema to the startup check `62c7a511a` refused a request naming a store already built in this process with another schema. It compared against the process's own cache, so it never saw another process or a restart, where the startup check against storage owns the comparison. Within one process, only a runtime change of the configured embedder could reach it, and refusing such changes is left to a later change. A store built with other dimensions still refuses a vector of the wrong length on every write and query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * State the Milvus and sqlite-vec metric limits as rules on the schema The metric is no longer a field of these parameters but the schema's, and the sentences read as a description of it. They now say which metrics the schema may give. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the SQLite partition rows' incarnation comments The partition registry and the segment store declare the same column without one; the partition contract and the design documents say what an incarnation is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Spell a property's type PropertyType where the redesign wrote it out validate_declared_types and EventMemory.expected_vector_store_indexed_properties, both rewritten by the redesign, take or answer the alias the redesign added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Delete a session's event memory from the deployment's stores A session records the vector store, segment store, and embedder it was created with. Opening refuses one whose recorded stores are not the deployment's, and deletion built the session the same way, so it raised too: the queued deletion failed before the session's row went, and every later scan failed it again. Deletion now moves the session's event memory onto the deployment's vector store, segment store, and embedder before building it, or drops it when the deployment configures no event memory, so it always completes. What the session kept in other stores stays there, as the configuration documentation says. Building the deployment's event store with the session's embedder instead could build it, and on a fresh backend create it, with another schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Take every session's event memory stores from the deployment's configuration A session saved the vector store, segment store, and embedder it was created with, and review commits compared those copies with the deployment's configuration: opening refused a session whose copies differed, creation refused a project that named others, and deletion substituted the deployment's. Each covered one path, and a renamed configuration id still made every session unopenable. The session's event configuration no longer has those fields; event memory takes them from the deployment's configuration whenever a session is created, opened, or deleted, and rows saved earlier load with their copies ignored. The three checks and the substitution go. Creation refuses an event project only when the deployment configures no event memory, which the merge used to refuse before the fields went. Project responses no longer name the deployment's stores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remove the event memory stores from the project API A project could name the vector store and segment store of its event memory, and the response named them back, though every event session now uses the deployment's. ProjectConfig no longer has `vector_store` or `segment_store`, so a request that still sends them has them ignored and a response names no store; the Python client's create_project and get_or_create_project drop the parameters. A project's `embedder` applies to the declarative backend only, as its description now says; an event project's is ignored, as its `vector_graph_store` already was. The OpenAPI document is regenerated for these fields only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Say a deployment without event memory can neither open nor delete event sessions Earlier releases let a project name its own event memory stores on a deployment whose long-term memory is declarative. Every session now takes its stores from the deployment's configuration, so on such a deployment those sessions can be neither opened nor deleted until it configures event memory. The configuration documentation says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 27, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 27, 2026
A declared datetime on the SQLite stores had a `tz_<key>` column beside its microseconds column, holding the UTC offset in seconds that no filter reads. The vector stores keep a datetime property's instant only, as MemMachine#1736 now does for Milvus and MemMachine#1788 for Qdrant, and the segment store keeps the offset, so the column, `offset_column_name`, and the value written to it go: each declared property is one column, and a datetime stays microseconds since the epoch. The tests read a stored datetime back as its instant in UTC, and the roundtrip test checks that the stored instant equals the written one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
edwinyyyu
added a commit
to edwinyyyu/MemMachine
that referenced
this pull request
Oct 27, 2026
The declared-schema contract gains a test that every store matches strings holding accented letters, an emoji, quotes, a backslash, and control characters, by equality and by membership. It fails on Milvus with `ensure_ascii=False` removed from `_expression_string_literal`, the UTF-8 literal [vector store scale-out 6/6] ports from MemMachine#1736's 1a7fffa. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
41 tasks
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
The Milvus store moves onto the collection registry, against a Milvus server, so any number of server processes sharing its registry may serve its collections. The store was rewritten rather than adapted, so the move is one commit; its message lists each change.
MilvusConf.collection_registrynames (required). The registry collections, whoseinsertlet two creators of one name both succeed, and the process-local locks go.partitionkey.isolation, and part of the primary key,"{incarnation}:{record_uuid}"._p_<name>; a datetime is a TIMESTAMPTZ holding its UTC instant, which is all a filter compares; the segment store keeps the offset). Undeclared properties go in one JSON field, where a filter compares one only with values of a comparable type, by its type tag, as it does a declared field: an int compares with an int or a float, a float only with a float.refine_k = 8. On the 100k-row tenant of a 180k-vector test set (Milvus 2.6.24), recall@10 is 0.988-0.990, against 0.73 with no search parameters, at 2.2-2.3 ms per search.AsyncMilvusClient, with every request bounded byMilvusConf.request_timeout_seconds. Under the synchronous client onasyncio.to_thread, with 32 Strong gets in flight beside 8 searchers, slow reads exhausted the shared executor: 12-21 searches per second, against 670-752 on the async client.common.gracefulTimebehind. Session had stalled every search behind the same process's writes (p99 5.3-5.8 s, against 39-58 ms at Bounded).MilvusConf.consistency_levelgoes.proxy.grpc.serverMaxRecvSize, gRPC's RESOURCE_EXHAUSTED status) is halved until its halves fit or a single entity is refused, as the Qdrant store does; any other error raises at once. The milvus extra declares grpcio for the status.MilvusConf.purge_batch_size(10,000) primary keys. A delete raises unless Milvus accepted every key sent. A round first indexes and loads a native collection that a failed creation left part-made, since Milvus answers the listing query only on a loaded collection. That step is a deliberate stopgap: it adds about 2 ms to a round (see Verification), and [vector store scale-out 6] Give each vector store a fixed schema and string-keyed partitions, built from the deployment's configuration #1627 removes it. There the store creates, indexes, and loads its one native collection in startup, so no purge round meets a part-made one.MilvusConf.tombstone_retention_secondshas the same floor as Qdrant's.MilvusConf.max_varchar_lengthis a declared string's VARCHAR length; a native collection keeps the length it was created with.common.JSONMaxLengthon a record's undeclared properties. A declared property's field name, its key of at most 32 bytes under_p_, fitsproxy.maxNameLength(255 unless configured).MilvusConf.metrics_factory_idnames the metrics factory the store reports its operation latencies to, as Qdrant's does.MilvusConf.uridefaults tohttp://localhost:19530, and a URI ending in.db, which pymilvus serves with Milvus Lite, is refused.Breaking, with no migration:
collection_registry.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. #1670 and #1702 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-scalingfeat/horizontal-scaling)feat/horizontal-scalingfeat/horizontal-scaling)feat/horizontal-scalingThis PR is its 37 commits,
aa809bd91,955f84a89,5582cf4d6,8e6539180,caefa7b73,509c2359d,7f1eb3b42,6e312e078,6d1ed1e92,25e79487d,2575af03c,6f368777f,d43e9e611,6dfbf595a,1b94f7f9f,cc29d2765,3681d092e,bc6d4f9cd,1a7fffa42,d6fc2199b,28e8c507f,9fd7f76d5,b3ef4f1ef,76062f81d,6dd2f9c87,6fcd8c5c2,c264b3d94,9696e6e2c,31fc7c465,baabf4899,07e6ce899,dac193a56,25fab8f2d,ca890c8b1,f56686f28,b01522967,49b9ec2dd, directly onfeat/horizontal-scaling. #1788 and #1670 are stacked on it, in parallel. Split from #1631 (closed).Verification
Commit 37: at the head
49b9ec2dd:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2169 passed, 8 skipped); in test containers, the integration tests pass (1676 passed, 266 skipped). The schema test asserts the native collection's exact field set, with no offset field, and the datetime tests read back the written instant in UTC.Commit 36: at the head
b01522967:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2169 passed, 8 skipped); in test containers, the integration tests pass (1676 passed, 266 skipped). The new test declares a datetime under a 32-byte key, the longest a property may have, so its fields are named_p_and_tz_offset_seconds_plus 32 characters, and Milvus 2.6.24 creates, writes, reads back, and filters on both.Commit 35: at the head
f56686f28:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2169 passed, 8 skipped); in test containers, the integration tests pass (1675 passed, 266 skipped). Against Milvus 2.6.24, a 72 MB upsert is refused in 0.2 s with gRPC's RESOURCE_EXHAUSTED ("received message larger than max (72055171 vs. 67108864)"), raised by pymilvus as it is, with nothing stored; a 32 MB one is accepted. The new integration test fails with RESOURCE_EXHAUSTED with the halving removed, and so does the mocked halving test.Commits 33-34, after another review of this PR: at the head
ca890c8b1:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2165 passed, 8 skipped); in test containers, the integration tests pass (1674 passed, 266 skipped). Commit 33's tests of afile://URI ending in.dband of aunix:URI fail under the earlier check, which refused a URI without://. Commit 34's test fails with the datetime written at its own offset: Milvus 2.6.24 refuses+00:19:32with "invalid timezone name" (code 1100).Cost of the purge round's index-and-load, measured on this PR's purge and collection-preparation code: against Milvus 2.6.24 in a test container on one machine, with 768-dimension vectors, a collection of 20,000 records purged in rounds of 5,000 beside another collection of 20,000 in the same native collection, three passes with the step and three with it replaced by a no-op. The step alone takes 2.4 ms median (p90 4.5 ms) on a loaded collection. A round that deletes a full batch takes 32 ms median both with and without it, and whole purges took 228-383 ms in either mode.
Commits 30-32, after two more reviews of this PR: at the head
dac193a56:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2160 passed, 8 skipped); in test containers, the integration tests pass (1673 passed, 266 skipped), both measured at a head whose tree differs only in how one configuration test buildsMilvusConf, which passes at this head. Commit 30's test fails with the level left out of the create request; commit 31's fails with the factory not passed. Commit 32 cites Milvus v2.6.24'scommon.JSONMaxLength(pkg/util/paramtable/component_param.go, default 64 KiB) and the upsert check that enforces it (internal/proxy/validate_util.go).Commit 29: at the head
31fc7c465:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2160 passed, 8 skipped); in test containers, the integration tests pass (1673 passed, 266 skipped). Its test fails with a float counted as comparable with an int property: Milvus 2.6.24 refuses the query ("cannot cast value to Int64", code 1100).Rebased onto
feat/horizontal-scalingafter it was rebased ontomain(2d471c50a): every commit is patch-identical to the one before the rebase; commit 28 changes documentation only. At the head9696e6e2c:ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2160 passed, 8 skipped); in test containers, the integration tests pass (1672 passed, 266 skipped).Commits 19-27, after a review of this PR: at the head,
ruff check,ruff format --check, andty checkare clean, anduv lock --checkpasses; the full server suite without integration tests passes (2055); in test containers, the integration tests pass (1533 passed, 266 skipped). Each new test failed under a named mutation of the code, listed in its commit message. Against Milvus 2.6.24: a filter string escaped to a surrogate pair fails to parse; a native collection left unloaded refuses the purge's listing with "collection not loaded"; a create of an existing collection with another schema is refused as "create duplicate collection with different parameters", which the removed guard's text did not match; and 450 of 450 result lists came back best first, across the three metrics, filtered and not.Rebased onto
feat/horizontal-scalingafter #1734 and #1735 merged there as5a846570aandf4cb7bd84: every commit's tree is unchanged, so the results below hold.Commits 6-18, rebased with the stack onto
feat/horizontal-scaling: at the head,ruff check,ruff format --checkandty checkare clean; the full server suite without integration tests passes (2055); in test containers, the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (662 passed, 6 skipped). Each new or changed test failed under a named mutation of the code, listed in its commit message. Milvus 2.6.24 answered identical concurrent creates of one collection with success every time, so the store's already-exists branch is tested with a mocked client.After the fourth review round of #1733 and #1734: the commits are rebased onto #1735's head
a0891bec2, patch-identical to the ones verified at0022fed7d, with commit 1's conflict inmilvus_vector_store.pyresolving to the commit's own file. At the head:ruff check,ruff format --checkandty checkare clean; the full server suite without integration tests passes (2107); in test containers, the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (597 passed, 6 skipped).After the third review round of #1734: commits 1-4 are patch-identical to the ones verified below, rebased onto #1735's head
0c4f87037, except commit 1, whose conflict inmilvus_vector_store.pyresolves to the commit's own rewritten file. Commit 5 adds documentation only. At commit 5, as verified before #1734's last test-only change:ruff check,ruff format --checkandty checkare clean; the full server suite without integration tests passes (2100); in test containers, the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (597 passed, 6 skipped).After the second review round of #1733-#1735: commits 1-4 are patch-identical to the ones verified below, rebased onto #1735's head
d823255de. At commit 4:ruff check,ruff format --checkandty checkare clean; the full server suite without integration tests passes (2097); in test containers, the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (597 passed, 6 skipped).Rebased onto #1735. Commits 1 and 2 are patch-identical to the ones verified below, and do not type-check on this base until commit 3, which adapts the Milvus handle and its test to the registration API. At commit 3:
ruff check,ruff format --checkandty checkare clean; the full server suite without integration tests passes (2085); in test containers, the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (590 passed, 6 skipped).After #1734's rename: commits 1-3 are patch-identical to the ones above, rebased onto #1735's head, and do not type-check there until commit 4, which renames the Milvus handle's uses. At commit 4:
ruff check,ruff format --checkandty checkare clean; the full server suite without integration tests passes (2085); in test containers, the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (590 passed, 6 skipped).Before the rebase:
At each commit:
ruff check,ruff format --checkandty checkare clean, run as CI runs them.At the head, in test containers: the integration tests pass against PostgreSQL, Qdrant 1.19.1 and Milvus 2.6.24 (589 passed, 6 skipped). They cover the vector store (the Milvus store with the collection lifecycle contract), the vector-store semantic storage, event memory, long-term memory and the resource manager. The 6 skipped are three Neo4j-specific semantic storage cases, which skip on the other backends, and three long-term memory tests, for want of a NebulaGraph server.
Before the review changes of #1733-#1736, the head's tree was byte-identical to #1631's head
89ba59636(tree8f3ad14bc); it now differs from it by those changes.🤖 Generated with Claude Code