Repository navigation
Conversation
Reverts the second commit of MemMachine#1606 (a8322a7), "Remove per-project filterable properties", and keeps its first, the OpenAPI regeneration under the locked FastAPI 0.141.1: docs/openapi.json is regenerated here with the same tool, so it differs from the pre-MemMachine#1606 document only by the ValidationError fields that regeneration added. A project declares `properties_schema`, a set of caller property keys with types, on its long-term memory configuration; the event backend merges it into the vector store collection's indexed schema and rejects filters on any other `m.<key>`. Under this design the schema is fixed for the life of the project, a schema change is a delete and a create, and the vector store maps every distinct schema to one physical collection that all projects declaring it share. What that costs, per backend, is written where a tenant will see it: a warning in the configuration docs and the field descriptions on the server configuration, the project API, the memory-configuration API and the Python SDK. Alternative to the one-collection chain (MemMachine#1622..MemMachine#1616): the two exclude each other. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…quest The event backend created a session's vector store collection and segment store partition on the first request that opened the session, so a search or a write for an unknown session created storage as a side effect, and the service locator was the only place that knew both stores' create paths. The owner is the session. Every path that creates a session row runs through EpisodicMemoryManager._create_session, which inserts the row and, when the row is new, creates the session's partitions in its segment store and its vector store (create_episodic_memory_storage); an equivalent re-create accepts the row and leaves the storage as it is. The request path binds handles with the stores' lookups and raises SessionPartitionMissingError when a partition is absent: a session without its storage is broken, not new. Deleting a session with no open instance deletes its partitions by key, so a session whose storage was never fully created can still be deleted. MemMachine.create_session goes through the manager for the same reason. The session's vector store collection is created with the system keys and the project's own `properties_schema`, resolved and merged as the request path did before. The semantic manager owns its one collection and creates it, once, at the storage's first use. With that, nothing calls the stores' open-or-create. The API is unchanged: the manager's open-or-create still creates a session a memory request names, now through the same path. Ported from MemMachine#1622 (4266b09) onto the chain that keeps per-project filterable properties. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The simple chatbot example, the TypeScript REST demo and the Dify plugin's add-memory tool wrote to a project without creating it, relying on the write to create it. Each now creates its project before its first memory request and accepts 409 as the project already existing. No behavior changes for them; they stop depending on a write creating a project. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
Adding memories to, or searching, a project that did not exist created it, with the server's default configuration, without the caller's knowledge. Now only the create-project request creates a project: a write or a search opens the session and answers 404 for an unknown project, as the search endpoint already promised; the manager's open-or-create goes. Two callers depended on the implicit creation. `org_id` and `project_id` default to `universal`, so the API promises the project `universal/universal`; the server creates it, once, at startup, and leaves one that already exists as it is. The MCP add tool names its own project and has no create-project counterpart, so it creates the project it writes to, once, and says so. The API doc strings and the OpenAPI document say which requests create a project. A breaking API change on `speedkick`. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01ESpWYTmCR7X3bJEpoA8SAn
|
Reopen if stack chosen. |
edwinyyyu
left a comment
There was a problem hiding this comment.
Found while auditing the main version, #1624; both apply here.
- Line comment below.
memmachine-server --stdio(app.py) callsinitialize_resource()directly and neverinit_global_memory, soensure_default_projectdoes not run for that entrypoint; "at startup" covers the HTTP lifespan, mcp_http and mcp_stdio only. Same on #1624.
packages/server/src/memmachine_server/main/memmachine.py:762 -- The 404 for an unknown project is raised only inside if MemoryType.Episodic in target_memories. A write with types: ["semantic"] (any request without episodic) to an unknown project is answered 200: episode_storage.add_episodes a few lines up has already inserted rows under the unknown session key (no FK to the sessions table), and the semantic manager ingests without a session lookup. Even with episodic included, the episode rows are persisted before this check runs, so the 404 arrives after the write. The description's "a write or a search opens the session and answers 404 for an unknown project" holds only for episodic requests. Same on #1624.
Alternative to the
[vector store N/13]chain (#1597, #1622..#1616): the two exclude each other. This chain keeps per-projectproperties_schema(user-defined metadata indexes) and conforms Qdrant to the incarnation lifecycle; SQLite and Milvus follow later. Every PR is a draft.Purpose of the change
Port of #1624: a memory request for an unknown project answers 404, the MCP add tool creates its own project once, and the default project
universal/universalis created at startup.docs/openapi.jsonregenerated.Stack
Slice 4 of 9, every PR targeting
speedkick; merge bottom-up.This PR's own change is its commit
227c0cfef; the rest of its diff is the slices under it, and drops out as they merge. Stacked on slice 3; slice 5 is stacked on it.Verification
ruff check,ruff format --check,ty check,pytest packages/server/server_tests(integration deselected): server 1934 passed, 3 skipped; run at this slice's own commit on 2026-09-15.🤖 Generated with Claude Code