Summary
Under transform_mode: "rust" with MODULE authority, ctx_memory resolves ids against the module keyspace (mc_memories.id), while in TS mode the same tool resolves against the host keyspace (memories.id). The same memory therefore has two different ids depending on mode.
That alone would be survivable if the ranges were disjoint. They are not: on this store the module keyspace is 1755..2167 and the host ids visible to an agent span 414..5067. An id in the overlap resolves successfully in both keyspaces — to different memories.
So an id carried across a mode boundary (from conversation context, from a note, from another memory's text that cites an id) does not fail loudly. For get it returns the wrong content; for update / archive / merge it would mutate the wrong row.
Found while flipping a long-lived project to rust mode on v0.42.5 + a ck-mc built from the same tree. Worth saying up front: the mc#444 mirror fix works — activation was clean, the mirror advanced, and a row-level diff against a pre-flip backup showed 0 ids lost, 0 supersede pairs lost, 0 content changed. This is a separate, narrower defect.
Evidence (observed)
Under MODULE authority, asking for two ids that the tool itself had returned in TS mode minutes earlier:
ctx_memory get ids:[5065, 5066]
→ id 5065: not found or not visible from this project
→ id 5066: not found or not visible from this project
Both rows exist and are active in the host DB. The same content is reachable under different ids:
ctx_memory get ids:[2165, 2166]
→ Memory [ID: 2165] in CONSTRAINTS (status: active): <content of host row 5065>
→ Memory [ID: 2166] in CONSTRAINTS (status: active): <content of host row 5066>
mirror_identity confirms the mapping is intentional and present:
SELECT module_row_id, context_row_id FROM mirror_identity WHERE context_row_id IN (5065,5066);
-- 2165|5065
-- 2166|5066
A write performed during the rust session returned a module id, and the host mirror stored it under a different one:
ctx_memory write → "Saved memory [ID: 2167]"
SELECT id FROM memories WHERE content LIKE 'Rust-mode attempt 7%'; -- 5067
SELECT module_row_id, context_row_id FROM mirror_identity WHERE module_row_id=2167; -- 2167|5067
The overlap is what makes it dangerous
module keyspace (mc_memories.id): 1755 .. 2167
host ids visible in <project-memory>: 414 .. 5067 (includes workspace-shared rows)
Concrete collision — id 2146 is a valid row in both stores and they are unrelated memories:
| store |
id 2146 |
host memories |
a CONSTRAINTS memory belonging to another (workspace-shared) project |
module mc_memories |
an entirely different CONSTRAINTS memory |
2146 is rendered in this project's <project-memory> block. An agent that read that id in one mode and acted on it in the other would hit the wrong row with no error.
Mechanism
Observed behaviour is unambiguous. The mechanism below is read from source, not captured in-window — flagging that distinction explicitly:
crates/mc-module/src/lib.rs ("get" arm): looks up memory.id from memory_tool::get_memories(...) and reports id {id}: not found or not visible from this project on a miss — a module-keyspace lookup.
crates/mc-module/src/m0_compose.rs:478: rendered_memory_ids is built as selected_memories.iter().map(|memory| memory.id) from the module's own snapshot.
If that reading is right, then within a single rust session the surface is self-consistent (the block renders module ids and the tool accepts module ids), and the defect is specifically at the mode boundary. I did not capture the rendered block during the flip window, so I can't confirm the in-window rendering empirically — if it instead renders host ids, the same bug is present continuously rather than only across flips, and is correspondingly worse.
Why the failure mode is bad even though get "fails safely"
The miss message reads like a scoping problem ("not visible from this project"), which points an investigator at workspace membership or project identity rather than at id translation. I lost time on exactly that misreading before checking mirror_identity.
More importantly, ids leak across the boundary through durable artifacts, not just live context:
- memory text that cites another memory's id (mine does — it references
5065/2165)
- notes, issues, skill-notes, plans
- anything an agent memorised earlier in a long-lived session
Suggested direction
Not prescribing an implementation, but the options seem to be:
- Translate at the tool boundary — have the module's
ctx_memory facade map incoming ids through mirror_identity (host → module) and report host ids outward, so the agent-visible keyspace is stable across modes.
- Render module ids everywhere in rust mode and accept that ids change on flip — but then a stale id must fail, not silently hit an overlapping row. That requires a namespace tag or a range guard, not raw integers.
Option 1 seems preferable: agent-visible ids stay stable, and durable references written in either mode keep pointing at the same memory.
Whatever the fix, the overlap is the sharp edge — a mis-keyed id should never resolve to a valid-but-different row.
Environment
- magic-context v0.42.5 host (+2 local fork commits, neither touching
crates/ — the Rust workspace is byte-identical to upstream)
ck-mc built from the same tree, subc-core 0.17.50 / protocol 0.20.0 / client-rs 0.13.0 per the committed Cargo.lock
- daemon
ck-subc 0.18.2, wire_version 2
- store: 4274 memories, 2 projects under module authority
Summary
Under
transform_mode: "rust"with MODULE authority,ctx_memoryresolves ids against the module keyspace (mc_memories.id), while in TS mode the same tool resolves against the host keyspace (memories.id). The same memory therefore has two different ids depending on mode.That alone would be survivable if the ranges were disjoint. They are not: on this store the module keyspace is
1755..2167and the host ids visible to an agent span414..5067. An id in the overlap resolves successfully in both keyspaces — to different memories.So an id carried across a mode boundary (from conversation context, from a note, from another memory's text that cites an id) does not fail loudly. For
getit returns the wrong content; forupdate/archive/mergeit would mutate the wrong row.Found while flipping a long-lived project to rust mode on v0.42.5 + a
ck-mcbuilt from the same tree. Worth saying up front: the mc#444 mirror fix works — activation was clean, the mirror advanced, and a row-level diff against a pre-flip backup showed 0 ids lost, 0 supersede pairs lost, 0 content changed. This is a separate, narrower defect.Evidence (observed)
Under MODULE authority, asking for two ids that the tool itself had returned in TS mode minutes earlier:
Both rows exist and are
activein the host DB. The same content is reachable under different ids:mirror_identityconfirms the mapping is intentional and present:A write performed during the rust session returned a module id, and the host mirror stored it under a different one:
The overlap is what makes it dangerous
Concrete collision — id
2146is a valid row in both stores and they are unrelated memories:memoriesmc_memories2146is rendered in this project's<project-memory>block. An agent that read that id in one mode and acted on it in the other would hit the wrong row with no error.Mechanism
Observed behaviour is unambiguous. The mechanism below is read from source, not captured in-window — flagging that distinction explicitly:
crates/mc-module/src/lib.rs("get"arm): looks upmemory.idfrommemory_tool::get_memories(...)and reportsid {id}: not found or not visible from this projecton a miss — a module-keyspace lookup.crates/mc-module/src/m0_compose.rs:478:rendered_memory_idsis built asselected_memories.iter().map(|memory| memory.id)from the module's own snapshot.If that reading is right, then within a single rust session the surface is self-consistent (the block renders module ids and the tool accepts module ids), and the defect is specifically at the mode boundary. I did not capture the rendered block during the flip window, so I can't confirm the in-window rendering empirically — if it instead renders host ids, the same bug is present continuously rather than only across flips, and is correspondingly worse.
Why the failure mode is bad even though
get"fails safely"The miss message reads like a scoping problem ("not visible from this project"), which points an investigator at workspace membership or project identity rather than at id translation. I lost time on exactly that misreading before checking
mirror_identity.More importantly, ids leak across the boundary through durable artifacts, not just live context:
5065/2165)Suggested direction
Not prescribing an implementation, but the options seem to be:
ctx_memoryfacade map incoming ids throughmirror_identity(host → module) and report host ids outward, so the agent-visible keyspace is stable across modes.Option 1 seems preferable: agent-visible ids stay stable, and durable references written in either mode keep pointing at the same memory.
Whatever the fix, the overlap is the sharp edge — a mis-keyed id should never resolve to a valid-but-different row.
Environment
crates/— the Rust workspace is byte-identical to upstream)ck-mcbuilt from the same tree,subc-core 0.17.50/protocol 0.20.0/client-rs 0.13.0per the committedCargo.lockck-subc 0.18.2, wire_version 2