Recall is the easy half. The hard half is currency: knowing whether what came back is still true.
A stale memory is worse than no memory, and not by a small margin. No memory means an instance asks, or reasons from the code in front of it. A stale memory means it proceeds confidently on something that stopped being true in March. It reads identically to a fresh one - same shape, same confidence, same authority in the prompt - so nothing in the output marks it as suspect.
That produces a failure mode teams consistently misdiagnose. When an instance does the wrong thing, there are two very different causes with one symptom:
- retrieval never found the memory, or
- retrieval found it, and it was wrong.
Without a record of when a fact was last confirmed and what replaced it, you cannot tell those apart, and the instinct is to blame retrieval and add more of it.
The first is a retrieval problem, and recall blends full-text with semantic matching so paraphrase and half-remembered titles still find things.
The second is what this page is about. Better retrieval cannot repair a fact that stopped being true, so Kythene records currency explicitly - which is what makes that failure visible rather than indistinguishable from a miss.
You do not have to run this yourself
In normal use, none of what follows is a routine you perform. Keeping memory current is the instance's job, and it happens as a side effect of the work. You write memories and recall them; the tidying happens around you.
That behaviour is carried by the MCP surface itself, not by a Claude-specific add-on, which matters if your team is not all on the same tool:
- The
recalltool description carries the rule, so any MCP client reads it on every single recall: if a result contradicts the live system, verify against the real system and correct it rather than working around it, and never leave a memory you know to be false in place. - Every recall response also returns the workspace guide inline, and the default guide repeats the same instruction in the workspace's own voice.
- The Claude skill states it a third time for instances that have it.
So an instance in Cursor or Codex that has never heard of the skill still gets told, on every recall, to correct what it finds stale. The skill reinforces the behaviour; it is not what creates it.
So read the rest of this page as what that behaviour is built from, and as the controls available when you want to intervene deliberately - not as maintenance you have been handed.
Automatic does not mean unaccountable. Every one of these operations is recorded and reviewable afterwards:
- In the app, a memory shows its current version, whether it is deprecated and what replaced it. The memory list has a toggle for browsing deprecated memory, collections carry per-artifact version history, and the workspace activity feed records deprecations, restores, forgets and promotions with who did them and when.
- Over the API, CLI and MCP, the same record is reachable in the flow of work - so an instance can answer "what did this say before, and who changed it" without anyone leaving the conversation.
The point of automating the correction is that memory stays true without discipline. The point of recording it is that you can still audit what an agent decided on your behalf.
Three ways a memory changes
| What happens | History | Recall afterwards | Reversible | |
|---|---|---|---|---|
| Supersede | New version of the same memory | Retained | Returns the new version only | Old versions readable |
| Deprecate | Flagged stale, optionally pointing at a successor | Retained | Hidden by default | Yes, --restore |
| Forget | Memory, artifacts and bytes deleted | None | Gone | No |
The distinction that matters: supersede when the fact changed, deprecate when the fact stopped applying. "We moved billing to Postgres" supersedes. "We removed the billing service entirely" deprecates. Reaching for forget for either is how you lose the ability to answer why you once believed something.
Supersede: the fact changed
Superseding writes a new version of an existing memory rather than creating a second, competing one. There are two ways to say which memory you mean.
Name it explicitly
Pass supersedes with the id of the memory being replaced. This is the form to prefer whenever you have the id - typically because you just recalled the memory and found it out of date:
{
"body": "Migrations run after the worker boots as of 0.42.",
"title": "Deploy gotcha",
"project": "acme",
"supersedes": "0199c4e2-1d3f-7a80-9f11-2b6d5c8e4a01"
}Naming the target replaces that memory whatever it is called, and it fails loudly rather than quietly doing the wrong thing: an id that is not a memory you can write in this workspace is an error, not a new memory created alongside the old one.
It is also the repair tool. Because the new version can carry a different title, supersedes heals a fork in one call - point it at "Deploy gotcha", write the corrected content under the title "Deployment gotcha", and you are left with one memory rather than two that disagree.
Or reuse the title
Write the memory again with the same title in the same project and Kythene supersedes it. This is the conversational path - it is what happens when someone says:
"Update the Deploy gotcha for acme - we fixed the deadlock in 0.42,
so the ordering constraint is gone."There is no id in that sentence, and requiring one would make the natural way of working impossible. The trade is that matching is on the title string, so near-miss titles fork: "Deploy gotcha" and "Deployment gotcha" are two memories, both live, quietly disagreeing. Prefer supersedes in anything programmatic; keep titles you would actually search for, and reuse them deliberately when a fact moves.
What supersession does, either way
- The response carries
superseded: true, so the caller knows it replaced something rather than adding to the pile. Worth surfacing rather than swallowing - an unexpectedsuperseded: trueon the title path means a collision you did not intend. - The old content becomes a previous version, retained not discarded.
MemoryEntry.versionreports the current one, and an earlier version's artifact is still retrievable by number. - Recall only ever returns the current version. Superseded content never re-surfaces, so there is no window in which two versions of the same fact compete.
- Approvals clear. A memory promoted to a team and approved goes back to awaiting review when a new version lands, because the team approved the old text, not a blank cheque on the title. See Promoting memory to the team.
Deprecate: the fact stopped applying
Deprecating marks a memory stale without destroying it. Recall stops returning it, so instances stop applying it, but it stays retrievable for audit.
"Deprecate the old caching memory - it's replaced by the one I just wrote."replaced_byrecords the successor's id, so a deprecated memory is not a dead end. Anyone who finds it, or any audit that surfaces it, gets a citable pointer to what is true now.- Reversible. Restoring clears both the deprecation and
replaced_by- deciding you were wrong about a decision being obsolete is a normal thing to do, not a data-recovery exercise. - Permission is deliberately lower than deleting. Update or delete rights on the memory suffice, because flagging something stale is a correction, not a destruction, and the friction should match.
- It is logged as an event, so "when did we stop believing this, and who said so" has an answer.
Reading deprecated memory on purpose
Hiding stale memory from recall is the right default and the wrong behaviour for audit. Recall takes include_deprecated (default false). With it set, results carry two extra fields:
deprecated- true when this memory has been flagged stale.replaced_by- the successor's id, when one was named.
This is how you answer "what did we think six months ago, and what changed" without that history leaking into everyday recall. It is also the honest way to review an instance's past reasoning: you can see the memory it applied at the time, and see that it has since been superseded.
Forget: it should not exist
forget removes the memory, its artifacts, their bytes and their embeddings. No history is kept and it cannot be undone. That is the point - it is for something written in error, or written somewhere it should never have been. It is not the tool for something merely out of date.
What an instance should do when memory contradicts reality
This is the part that determines whether any of the above matters, because currency is maintained by the instances doing the work, not by a periodic clean-up nobody schedules.
When a recalled memory disagrees with the live system, correcting it is part of the task, not a separate chore:
- Verify against the real thing first. The memory is not automatically the wrong one - the live system may be the thing that broke.
- Supersede if the fact changed, deprecate with
replaced_byif it no longer applies. - Say so in the reply, so the human sees the correction rather than discovering it later.
None of this depends on your prompt discipline, or on everyone using the same client. It rides on the recall tool description and the workspace guide returned with every recall, so it arrives with the integration - see You do not have to run this yourself.
Where each operation lives
| Operation | MCP / API | CLI |
|---|---|---|
| Supersede a named memory | remember with supersedes: <id> | kythe remember … --supersedes ID |
| Supersede by title | remember with an existing title + project | kythe remember <file|-> --title X --project P |
| Deprecate | deprecate with replaced_by | kythe deprecate <memory-id> --replaced-by ID |
| Restore | deprecate with restore: true | kythe deprecate <memory-id> --restore |
| Read stale memory | recall with include_deprecated: true | kythe recall … --include-deprecated |
| Inspect a version | MemoryEntry.version | kythe artifact <artifact-id> --version N |
| Forget | forget | kythe forget <memory-id> |
Both superseded and replaced_by come back as ids, so a correction is always citable: you can point at what changed, not just assert that something did.
# supersede by title, from a file or stdin
printf '# Deploy gotcha\nMigration runs before the worker boots.\n' \
| kythe remember - --project acme --title "Deploy gotcha" --type gotcha
# supersede a specific memory, whatever it is currently titled
kythe remember - --project acme --title "Deployment gotcha" --supersedes <old-memory-id>
# deprecate, naming the successor
kythe deprecate <old-memory-id> --replaced-by <new-memory-id>
# changed your mind
kythe deprecate <memory-id> --restore