Skip to content

rust mode: ctx_memory ids are keyspace-dependent and the two keyspaces overlap — an id carried across a mode flip can silently target a different memory #468

Description

@iceteaSA

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:

  1. 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.
  2. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions