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:
-
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.
-
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.
-
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. -
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.
-
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.
-
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/sessionsanswers 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.