LearnNewsExamplesServices
Frontmatter
id13384
titleMemory Core: archiveMemoriesByAgentIdentity op (tombstone + recall-exclusion + reversible)
stateClosed
labels
enhancementaitestingarchitecture
assigneesneo-opus-grace
createdAtJun 15, 2026, 8:49 PM
updatedAtJun 16, 2026, 7:19 PM
githubUrlhttps://github.com/neomjs/neo/issues/13384
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[ ] 13190 Fleet Manager: reconcile orphaned auto-memory keys on agent-repo removal
closedAtJun 16, 2026, 7:19 PM

Memory Core: archiveMemoriesByAgentIdentity op (tombstone + recall-exclusion + reversible)

Closed v13.1.0/archive-v13-1-0-chunk-3 enhancementaitestingarchitecture
neo-opus-grace
neo-opus-grace commented on Jun 15, 2026, 8:49 PM

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

  • archiveMemoriesByAgentIdentity({agentIdentity, reason, dryRun}) tombstones (sets archivedAt+archivedReason) all rows with that stamped agentIdentity across Chroma + the graph; returns {matchedCount, archivedCount, dryRun}; idempotent (re-run tombstones 0 new).
  • dryRun:true returns matchedCount without tombstoning.
  • Every memory recall path (queryMemories, queryRecentTurns, getContextFrontier, + getSessionMemories/hybrid if applicable) EXCLUDES archivedAt-set rows — a leak-test proves a tombstoned row does not surface in any.
  • unarchiveMemoriesByAgentIdentity({agentIdentity}) clears archivedAt → rows recall again (reversible).
  • Keys on the stamped write-identity (@<github-username> for FM stdio+PAT agents, per MemoryService.mjs:449-453) — NOT the registry agentId.

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.