In March you told the agent the staging database lives on the shared cluster. In June the team moved it to its own instance and you said so. In September you ask where staging runs, and the answer names the shared cluster, confidently, with the June note sitting one row away in the same store. Nothing deleted the old fact, because nothing was asked to, and the retrieval that picked it had no way to know which of the two was still true.
That is the buyer's question about consolidation and forgetting, stripped of the marketing: when a newer memory contradicts an older one, what happens to the older one? Not what gets retrieved. What happens to the record that used to be true. I put that one question to six memory systems, including the one I work on, and read each vendor's own pages for the answer. All six answer it, in six different ways, and our row is the sixth answer, so read it with that in mind.
One line to carry: a memory layer that keeps the old fact and a memory layer that hides it will both say they handle forgetting, and only the default read tells you which one you bought.
What a store can do with an old record
A store has four options when a new fact collides with an old one. Take the old record out. Leave it in and stop returning it. Refuse more than a fixed amount in the first place. Fold several records into one. Each loses something. Taking it out loses the history, and knowing a decision was reversed is different from never having made it. Leaving it in costs storage and search. A fixed cap is predictable and blunt. Folding records is the most work and the hardest to explain.
Three checks run on a vendor's own pages, without a demo call. Is the old record removed, hidden, or kept? Who decides: a rule, a model, a size, or you? Is the decision visible from outside? The six sections below are those three checks.
Zep: marked invalid and kept, and the filtering is yours
Zep's answer has two layers. At the fact level it is invalidation. Its page on graph creation says that each extracted fact is compared to existing edges: "duplicates merge, and contradictions invalidate older facts rather than leaving conflicting truths side by side". The fact stays, stamped with the time it stopped being true, and the decision is a model's, at write time. What the stamp does at read time is only half stated: the context block guide still tells you, when you build your own block, to "clearly mark any fact with a non-null invalid_at as no longer valid". The mark is the store's. Relying on it alone is not what the guide recommends.
The second layer is observations, and that one retires. The observations page says "the older observation is retired so the graph reflects the current state of what is known", and it opens with the gate: "Available to Flex Plus and Enterprise customers". Whether retired means hidden or deleted, the page does not say.
Supermemory: replaced for search, kept for audit, and some of it expires
Supermemory's graph-memory page puts the claim under the subtitle temporal truth and automatic forgetting, and says: "New fact replaces what was true before for search purposes; history can remain for audit". The old fact is not taken out. It stops being what you find. The decision is the product's, at write time and in a background pass.
The second half of that subtitle matters as much as the first. The same page says "temporary facts drop after they expire", and the search page states the read-time rule: "By default, search excludes memories that have been forgotten or have passed their forgetAfter expiration", with a flag to ask them back. So the reachable set shrinks on contradiction and on a timer. Whether the store shrinks is not on the page.
Cognee: both kept, three marks on offer, none on by default
Cognee keeps both records, and the question that separates it is who marks the older one and who filters it. On Cognee's pages, both are you. The fact validity guide stamps a node closed by a call you make: "Supersede instead of delete: the old node stays in the graph with valid_to set". A changelog entry describes tagging the older assertion superseded for relationships you name, off unless you turn it on, and the cognify page lists a third mark, contradiction detection, opt-in and off by default.
Then the sentence that matters, on the same guide: "Retrieval is not filtered yet." In full: "Search and graph completion neither filter nor down-weight closed nodes, so a superseded fact can still surface in results". The current-state view is a filter you write. Of the six, this is the answer that hands you the most and decides nothing at read time. The same vendor is also the one that names the cost of getting a merge wrong, in an instruction rather than a feature: "Automate merges only under a defined confidence policy".
Mem0: both kept, badged, and the reader chooses per call
Mem0's path for the old record is Dream, a platform feature, and its table describes the action aimed at this exact question: "Marks an older fact as outdated when a newer one contradicts it". Then, on the same page: "Supersede runs automatically as part of adding memories, on every plan", "Superseded memories are not deleted and not hidden by default", and "Nothing Dream does is destructive".
So a normal read returns both, the old one badged, and only latest_only=true asks for the current truth. The version you get is decided by whoever is reading, every time, not once by the store. The caller can also put a date on a memory, and the expiration page is exact about what that does: "Expiration hides a memory, it does not delete it."
Letta: overwritten under a ceiling, or rewritten in the background with git behind it
Letta's answer depends on which Letta you read. On memory blocks, the block carries a character ceiling and the old text does not survive a rewrite: "If multiple processes (agents or external scripts) modify the same block concurrently, the last write wins and overwrites all earlier changes". The ceiling is the one number in this comparison you know before anything runs. And the same documentation now carries a sentence that changes what the ceiling is worth: "Memory blocks may be deprecated in the future. We do not recommend building on memory blocks anymore."
The Letta its pages now point to keeps memory as files in a git repository, and its consolidation step is called Dreaming: "Dreaming uses background subagents to review recent conversations, consolidate lessons, and update memory without interrupting active work". There, "Every memory edit is committed to the MemFS git repository", so the older version of a memory file survives as a commit. Two Lettas, and the page you land on decides which you get.
Mnemoverse, ours: kept, marked if the writer says so, not hidden yet
I work on this one, so weigh this section hardest. Our merge step is not our answer to this question. The API reference entry for the merge endpoint opens with "Consolidation is off on the hosted service and we do not currently recommend turning it on." and continues "The endpoint responds 503 with a typed reason rather than consolidating." The overview states the consequence: "Memories are kept rather than compressed away".
Both records stay, and the older one can be marked. A write can name the records it replaces, and the reference says what happens to each of them: it "gains a superseded_by pointer to the new atom and stays fetchable by ID". That is a mark, made by the writer, at write time. What it does not do yet is hide anything, and the reference is direct about it: "It records the correction; it does not yet hide the original." The filter that would do the hiding exists under a callout titled "Read-side filtering is deployed, and switched off (as of 2026-08-29)". So on the hosted service both records stay, the older one is marked if the writer says so, a default read still returns it, and nothing is merged. That is a real answer to losing the record of a reversed decision. It is no answer to a store that keeps growing.
Six answers, one table
Every quotation above is a contiguous substring of a page the vendor published, re-checked on 2026-09-13, and each page is named on the full write-up. The table is a reading of published pages, not a benchmark, and four of the six engines are closed, ours included.
| system | old record | who decides | when | visible from outside |
|---|---|---|---|---|
| Zep | kept, stamped invalid_at; on two plans, observations retired | a model at ingestion; you at read | write time; the read is yours | hosted documentation; Graphiti source for the engine |
| Supermemory | replaced for search, kept for audit; temporary facts expire | the system's model, in a background pass | write time and background | hosted documentation |
| Cognee | both kept; three marks, each opt-in; retrieval not filtered yet | you: the mark and the filter | write time for the mark, read time for the filter | published source |
| Mem0 (Dream) | both kept, old fact badged; reader chooses per call | the store marks, the reader chooses | write time, then every read | hosted documentation |
| Letta | block overwritten under a ceiling; MemFS files rewritten by Dreaming, prior versions in git | the agent that wrote last; background subagents | write time; background | published source |
| Mnemoverse | both kept; marked when the writer names it; not hidden; merge refused | the writer; the hiding is nobody's yet | write time; read-side filter off | hosted documentation |
The axis a feature list does not show is when the decision happens and whose it is. Zep and Supermemory decide at write time, by a model. Mem0 marks at write time and lets the reader decide per call. Cognee and we mark at write time only when the caller asks and leave the reader to filter, Cognee because retrieval is not filtered yet, we because the filter is switched off. Letta's block decides by whichever agent wrote last. A write-time decision is a rule you inspect in the store. A read-time decision is a parameter you pass on every call, and a default you can get wrong. A decision that is nobody's yet is the third case, and two of the six are in it.
The one-minute test on whatever you already use
Tell it something. Tell it the opposite a week later. Ask a question that touches both, then ask a second question about what you used to think. First answer current and the second finds nothing: the old record was taken out, or written over. Both come back with nothing to separate them: nothing was marked, or the mark does nothing at read time. First current and the second still finds the old one, marked as old: you are in the middle of this article, where most of the six are, and telling those four apart takes the three checks, not the first reply.
Which of the six fates did your store turn out to have, and did you know before you ran the test?
Disclosure: I work on Mnemoverse, one of the six systems above, so weigh the argument accordingly. The full comparison with every source page is on our library.
Top comments (0)