Support canonical project references for memory mutations #213
Description
Reported 2026-09-13: reads return projectId plus a project-local number and revision ids, while the memory mutations wanted an internal id that no read publishes. That blocked read-modify-write without guessing identifiers.
State found on 2026-09-15
Checked against the live server before touching anything. The report was partly overtaken:
| Tool | Identifier | Canonical? |
|---|---|---|
memory_update |
projectId + number + baseRevisionId | already fixed, built for #238 |
memory_delete |
projectId + memoryNumber, or id | already fixed |
memory_archive |
id only |
the gap |
memory_restore |
id only |
the gap |
memory_get |
id only |
covered by project_memory_get |
A live project_memory_get of #180 confirmed it: the response carries Reference, Number and RevisionId, and no internal id anywhere.
One consequence was worse than a missing feature. Archiving a project memory was reachable, but only through memory_delete without privateDataDelete — an agent wanting to archive would never look behind a tool named delete. Restoring had no canonical path at all, so a memory archived that way could not be brought back.
Done
memory_archive and memory_restore take projectId and memoryNumber, matching memory_update and memory_delete. Personal, organization and system memories keep id, because their own public reference carries it and they have no project number. Mixing both forms is refused rather than silently preferring one, and a project number is never turned into an internal id for anything else.
memory_get keeps id deliberately and now says so: its response contains the internal id, which has no place in a project-memory answer. project_memory_get is the project read.
System guide raised to v21.
Acceptance
- Resolve, read, update, reread, archive and restore a project memory using only returned public identifiers.
- The same local number in another project does not reach this memory, and the refusal discloses none of its content.
- Stale revisions still rejected;
idcalls keep working for the scopes that have no number. - Tool schemas, descriptions and the system guide updated together.
Verification
AProjectMemoryIsReadUpdatedArchivedAndRestoredThroughPublicIdentifiersOnly walks the whole path over MCP — project_list, item_resolve, project_memory_get, memory_update, reread, archive, restore — taking every identifier from a previous response, with no database access. MemoryAddressingRefusesMixedAndIncompleteIdentifiers covers empty, half and mixed requests and the wrong-project case. Suite: 109 green.
Confirmed on the deployed server without mutating anything: memory_archive with a project but no number returns the new refusal, so the new contract is live.
Observation, not fixed here
memory_archive and memory_restore answer with a plain sentence while every other mutation returns a JSON receipt naming what changed. Worth aligning, but that is a response-shape decision rather than the addressing bug this task reported.