Skip to content

ADR-0119: Removing something from memory is reversible, and it reaches the agent

Status: Proposed (2026-09-22). The owner decided everything below in a grill-me interview on the same day. Design only; nothing is implemented.

Status pass (2026-10-18): Accepted; the decision is implemented (memory/removal.py, memris/graph.py, harness_api/memory_routes.py). implemented although the text says nothing is; status moves from Proposed to Accepted; memory-map-cleanup-plan.md was never written

Relates to: ADR-0115 (memory is a graph of claims), ADR-0114 (memory is what the owner confirmed), ADR-0110 (plugin manifests). Amends ADR-0115 decision 4 (an entity gains a removed state and a permanent delete) and decision 1 (two new stores: session removal marks and suppressed names).

Context

The owner found the Memory page's Map "good, but full of junk", and there was no way to remove a node. Reading the code and the owner's real data/memory.db showed that the stored memory is small and clean (20 entities, 10 confirmed current facts). The junk is mostly things the Map draws that are not memory:

  • Every item on a conversation summary's Referenced: line becomes an entity node, with no quality check. That covers about 279 items across 61 sessions: IDs, email addresses, file paths, and promotional senders that recur in every "check my email" chat.
  • Test sessions are drawn, because the Map never applies the ephemeral-session check, and many old test names miss its prefixes.

Nothing could remove a node. memris had no entity delete, and the Map was read-only.

Decision

The full decision list and the build order are in docs/architecture/memory-map-cleanup-plan.md. The rules that bind later work:

  1. Fix the sources and add a Remove action. A Referenced item is drawn only when it resolves to a memris entity or recurs in at least 2 sessions. Declared never-draw shapes apply first. Ephemeral sessions are skipped. A plugin can contribute names that are never drawn; the email plugin contributes senders whose mail is at least 80% promotional. All thresholds, shapes and flag rules are YAML. Core carries no email vocabulary.

  2. Removal is reversible and reaches the agent. A removed item leaves the Map, recall and the prompt's context together. The Map never shows a cleaner memory than the one the agent uses. Every removal appears in a Removed list and can be restored exactly.

  3. Removing an entity forgets its facts too. Its live statements are retracted with reason removed, after a confirmation that lists them, and restoring the entity restores those statements. The entity's removed mark is part of memris, which stays free of IRIS imports.

  4. Names that are not entities are suppressed, not edited out. A suppressed folded name hides every matching summary item, past and future. Conversation summaries are never rewritten. A later confirmed fact that names the same thing overrides the suppression.

  5. Permanent delete means gone and staying gone. It works only on removed items and is refused while any statement about the entity still holds. A permanently deleted entity's name stays suppressed. Only Restore lifts a suppression.

  6. Every action has an API and a CLI. Every action is gated like every other memory write.

Alternatives considered

  • A delete button only. The noise is regenerated by every new summary, so the owner would be deleting the same kinds of item forever.
  • Hide from the Map only. The Map would then show a cleaner memory than the agent actually uses.
  • Immediate, permanent delete. A misclick on a graph is easy, and deleting a real entity would lose a confirmed fact with no undo.
  • Edit summary text to remove an item. This rewrites conversation history and fixes only that one summary.
  • List promotional senders in YAML, or trust the summary prompt alone. A hand-kept list goes stale. A prompt is judgement on small local models and does nothing for existing summaries. The email store's own classification is evidence; the prompt is kept only as a second layer.

Consequences

  • Positive: the Map shows what the agent uses. The owner can correct memory without the CLI, and every correction can be undone.
  • Negative: removal and suppression checks sit on the Map's, recall's and context's read paths. A suppressed name can hide a genuine later mention until a fact confirms it.
  • Reversibility: everything except permanent delete can be restored. Permanent delete is a separate, typed-confirmation action on already removed items.
  • When the removed-session ledger cannot be read (amended 2026-09-27, owner decision): recall, the Map and the chat list fail closed. Recall uses no cross-session turn, the Map draws no sessions, and /api/sessions answers 503 ("try again") rather than listing every session, so a removed conversation never reappears because a read failed. The retention review (memory/retention.py) stays fail-open: it is where the owner cleans up, and it lists what it can. Each case logs a WARNING naming the exception type.

Verification

This ADR is verified by the acceptance list in docs/architecture/memory-map-cleanup-plan.md: the Map on the owner's real data is clean after the source filters alone; remove and restore round-trip exactly, including through the memory_graph tool and recall; and a suppressed name behaves as decision 4 says.