Context
Split from #13190 (FM-removal reconciliation) per the contract converged in the #13190 thread + @neo-opus-ada's 1-ticket-1-PR coordination (2026-06-15): #13190 is the FM-pass (removeAgent({removeRepo}), Resolves #13190); this ticket is the Memory-Core primitive the FM-pass consumes via the archiveMemoriesFn seam. One clean Resolves each so neither PR premature-closes the other.
Release classification: post-release (Agent Harness adjacent; boardless).
The Problem
When an FM agent + its repo are removed, its auto-memories become orphaned recall-pollution — they keep surfacing in agent recall with no live owner. The reconciliation needs a Memory-Core primitive that tombstones an identity's memories so they stop surfacing in recall, while retaining the rows (forensics) and staying operator-reversible. No such op exists today.
The Architectural Reality
- Write-identity (V-B-A'd,
MemoryService.mjs:449-453): SSE → AgentIdentity nodeId; FM stdio+PAT agents → @<github-username> (the @${userId} gh api user fallback) — NOT the registry agentId. The op (and the consuming FM-pass) MUST key on this stamped write-identity.
- Tombstone precedent + the gap (V-B-A'd on
origin/dev): archivedAt IS NULL filtering exists only in buildMailboxDelta (MemoryService.mjs:119-185, the message/mailbox queries) — NOT in the memory RECALL paths (queryMemories:1428, queryRecentTurns:983, getContextFrontier:1565). So setting archivedAt alone would NOT hide rows from recall; the op MUST add the exclusion to those paths.
- Stores: the tag lives in Chroma metadata + the graph projection (
memoryProperties.agentIdentity). The tombstone must set archivedAt across both, and every recall path must filter it.
- Precedent to mirror:
archive_message soft-delete + the buildMailboxDelta archivedAt filter.
The Fix
MemoryService.archiveMemoriesByAgentIdentity({ agentIdentity, reason, dryRun = false })
→ { agentIdentity, matchedCount, archivedCount, dryRun }
- Sweep rows where
metadata.agentIdentity === agentIdentity (Chroma + graph projection); set archivedAt (+ archivedReason). Idempotent — already-tombstoned rows skipped (archivedCount = newly tombstoned).
- Add
archivedAt-exclusion to the recall paths: queryMemories, queryRecentTurns, getContextFrontier (+ getSessionMemories / hybrid-graph if they surface memories) — mirroring buildMailboxDelta.
- Reversible:
unarchiveMemoriesByAgentIdentity({ agentIdentity }) clears archivedAt. A hard purge is a separate explicit op, never the tombstone default.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
MemoryService.archiveMemoriesByAgentIdentity (new) |
#13190 thread contract; FM-pass consumer |
tombstone (set archivedAt+archivedReason) rows by stamped agentIdentity (Chroma + graph); return {matchedCount, archivedCount, dryRun}; idempotent |
dryRun:true previews matchedCount without tombstoning |
JSDoc + op |
leak-test: tombstoned rows excluded from all recall paths; counts correct |
recall-path archivedAt-exclusion (queryMemories / queryRecentTurns / getContextFrontier / getSessionMemories) |
the tombstone must actually hide rows |
each path filters archivedAt IS NULL |
— |
— |
leak-test asserts a tombstoned row does NOT surface in each path |
unarchiveMemoriesByAgentIdentity (new) |
operator-reversibility |
clears archivedAt by agentIdentity |
— |
JSDoc |
unarchive restores recall |
Acceptance Criteria
Out of Scope
- The FM-removal-path pass (
removeAgent({removeRepo}) wiring archiveMemoriesFn + the integration test) — that is #13190 (@neo-opus-ada).
- Hard purge (a separate explicit op).
- AC1-residual (tokenless-agent /
gh-fallback-miss / untagged rows) — gated on the deferred spawn-time identity-env (FleetLifecycleService:50-51); annotated on #13190, not closed here.
Related
- Consumed by / blocks: #13190 (the FM-pass wires
archiveMemoriesFn to this op).
- Harness epic: #13012. Precedent:
archive_message soft-delete.
Origin Session ID
0f5d9f1d-0683-452d-aac1-f467297186ac
Handoff Retrieval Hints
query_raw_memories: "archiveMemoriesByAgentIdentity tombstone recall-exclusion FM-removal reconcile"; exact anchors: MemoryService.mjs:449-453 (write-identity), :119-185 (buildMailboxDelta archivedAt precedent), :1428/:983/:1565 (the recall paths needing exclusion).
Live latest-open sweep: checked latest 15 open issues 2026-06-15T~18:48Z; no existing MC-archive/tombstone ticket (only #13190, the parent FM-pass) — net-new MC primitive.
Context
Split from #13190 (FM-removal reconciliation) per the contract converged in the #13190 thread + @neo-opus-ada's 1-ticket-1-PR coordination (2026-06-15): #13190 is the FM-pass (
removeAgent({removeRepo}),Resolves #13190); this ticket is the Memory-Core primitive the FM-pass consumes via thearchiveMemoriesFnseam. One cleanResolveseach so neither PR premature-closes the other.Release classification: post-release (Agent Harness adjacent; boardless).
The Problem
When an FM agent + its repo are removed, its auto-memories become orphaned recall-pollution — they keep surfacing in agent recall with no live owner. The reconciliation needs a Memory-Core primitive that tombstones an identity's memories so they stop surfacing in recall, while retaining the rows (forensics) and staying operator-reversible. No such op exists today.
The Architectural Reality
MemoryService.mjs:449-453): SSE → AgentIdentity nodeId; FM stdio+PAT agents →@<github-username>(the@${userId}gh api userfallback) — NOT the registryagentId. The op (and the consuming FM-pass) MUST key on this stamped write-identity.origin/dev):archivedAt IS NULLfiltering exists only inbuildMailboxDelta(MemoryService.mjs:119-185, the message/mailbox queries) — NOT in the memory RECALL paths (queryMemories:1428,queryRecentTurns:983,getContextFrontier:1565). So settingarchivedAtalone would NOT hide rows from recall; the op MUST add the exclusion to those paths.memoryProperties.agentIdentity). The tombstone must setarchivedAtacross both, and every recall path must filter it.archive_messagesoft-delete + thebuildMailboxDeltaarchivedAtfilter.The Fix
MemoryService.archiveMemoriesByAgentIdentity({ agentIdentity, reason, dryRun = false }) → { agentIdentity, matchedCount, archivedCount, dryRun }metadata.agentIdentity === agentIdentity(Chroma + graph projection); setarchivedAt(+archivedReason). Idempotent — already-tombstoned rows skipped (archivedCount= newly tombstoned).archivedAt-exclusion to the recall paths:queryMemories,queryRecentTurns,getContextFrontier(+getSessionMemories/ hybrid-graph if they surface memories) — mirroringbuildMailboxDelta.unarchiveMemoriesByAgentIdentity({ agentIdentity })clearsarchivedAt. A hard purge is a separate explicit op, never the tombstone default.Contract Ledger Matrix
MemoryService.archiveMemoriesByAgentIdentity(new)archivedAt+archivedReason) rows by stampedagentIdentity(Chroma + graph); return{matchedCount, archivedCount, dryRun}; idempotentdryRun:truepreviewsmatchedCountwithout tombstoningarchivedAt-exclusion (queryMemories/queryRecentTurns/getContextFrontier/getSessionMemories)archivedAt IS NULLunarchiveMemoriesByAgentIdentity(new)archivedAtbyagentIdentityAcceptance Criteria
archiveMemoriesByAgentIdentity({agentIdentity, reason, dryRun})tombstones (setsarchivedAt+archivedReason) all rows with that stampedagentIdentityacross Chroma + the graph; returns{matchedCount, archivedCount, dryRun}; idempotent (re-run tombstones 0 new).dryRun:truereturnsmatchedCountwithout tombstoning.queryMemories,queryRecentTurns,getContextFrontier, +getSessionMemories/hybrid if applicable) EXCLUDESarchivedAt-set rows — a leak-test proves a tombstoned row does not surface in any.unarchiveMemoriesByAgentIdentity({agentIdentity})clearsarchivedAt→ rows recall again (reversible).@<github-username>for FM stdio+PAT agents, perMemoryService.mjs:449-453) — NOT the registryagentId.Out of Scope
removeAgent({removeRepo})wiringarchiveMemoriesFn+ the integration test) — that is #13190 (@neo-opus-ada).gh-fallback-miss / untagged rows) — gated on the deferred spawn-time identity-env (FleetLifecycleService:50-51); annotated on #13190, not closed here.Related
archiveMemoriesFnto this op).archive_messagesoft-delete.Origin Session ID
0f5d9f1d-0683-452d-aac1-f467297186ac
Handoff Retrieval Hints
query_raw_memories: "archiveMemoriesByAgentIdentity tombstone recall-exclusion FM-removal reconcile"; exact anchors:MemoryService.mjs:449-453(write-identity),:119-185(buildMailboxDelta archivedAt precedent),:1428/:983/:1565(the recall paths needing exclusion).Live latest-open sweep: checked latest 15 open issues 2026-06-15T~18:48Z; no existing MC-archive/tombstone ticket (only #13190, the parent FM-pass) — net-new MC primitive.