# Agent Memory Versioning: How to Roll Back a Bad Memory Write, Restore the Index With It, and Find the Answers That Relied on a Changed Fact

> Agent memory versioning keeps every change to what an agent remembers as an immutable revision, so a bad write can be inspected and reverted instead of silently overwriting the truth. What to record per revision, why a rollback has to restore the retrieval index and derived facts along with the stored memory, how Anthropic, Google Vertex AI, Mem0, Letta and Hindsight handle history and rollback as of 13 November 2026, how versioning interacts with GDPR erasure, and how to run provenance backwards to find the answers already sent that relied on a fact that has since changed. With a tested Python build.

_Gaurav Dadhich · 2026-11-13_

\# Agent Memory Versioning: How to Roll Back a Bad Memory Write, Restore the Index With It, and Find the Answers That Relied on a Changed Fact \*Published 13 November 2026. Every product behaviour below was read from the vendor's own documentation or post on 13 November 2026.\* Agent memory versioning means every change to what an agent remembers is kept as an immutable revision, with who or what caused it and why, so that a bad write can be found, inspected and reverted rather than silently replacing the truth. It has three jobs, and most write-ups stop after the first: keep a history you can roll back, make sure the rollback reaches the retrieval index and every derived fact and not only the stored record, and run provenance backwards so that when a fact changes you can list the answers your agent has already sent that relied on the old version. The managed platforms now ship the first job. \[Anthropic's Managed Agents memory stores\](https://platform.claude.com/docs/en/managed-agents/memory) create an immutable version on every mutation and keep versions for 30 days, and \[Google's Vertex AI Memory Bank\](https://docs.cloud.google.com/agent-builder/agent-engine/memory-bank/revisions) keeps revisions for 365 days by default and exposes a \`RollbackMemory\` call. The other two jobs are still mostly yours to build. Agents now rewrite their own memory constantly, through extraction on every turn, overnight consolidation jobs and bulk imports, and each of those is a write that can be wrong. An overwrite with no history turns a recoverable mistake into a permanent one. ## What memory versioning is, and what it is not A memory version is a snapshot of one remembered item (a fact, a preference, a note, a file) at one point in time, plus the metadata that explains how it got there. Versioning the memory is a different thing from versioning the system around it, and \[EverMind's 11 November post on memory versioning\](https://evermind.ai/blogs/ai-memory-versioning-tracking-model-evolution) draws the line well: memory-state revisions record changes to remembered content, schema versions describe the structure of the stored data, and runtime versions identify the model, prompts, retrieval settings and tools that read it. A schema migration can add a field without changing a fact, and a model upgrade can change how the same fact is used. Folding all three into one version number makes every incident harder to diagnose, because you cannot tell which layer moved. Memory versioning is also not the same as session rewind. \[Google's Agent Development Kit\](https://google.github.io/adk-docs/sessions/session/rewind/) can rewind a session to an earlier invocation, and the \[OpenAI Agents SDK\](https://openai.github.io/openai-agents-python/) can pop items off a session's history, but the \[ChronoMem paper\](https://arxiv.org/abs/2607.27773) (Su, Xu, Zuo and Bertino, arXiv 2607.27773v2, 5 August 2026) points out that these are session-scoped and do not restore user-level or app-level long-term memory. A rewind undoes the last few turns of one conversation; memory versioning undoes a change to what the agent believes about a user across every future conversation. It is not a backup either. A nightly backup gives you a restore point per day; a revision log gives you one per write, with the reason attached, which is the difference between "restore yesterday" and "undo the update that came from ticket 2207". ## What to record on every memory revision A revision is only useful if it tells you enough to decide whether the change was right. These fields are the minimum I would ship: | Field | Why you need it | |---|---| | Stable memory identity | Lets you follow one fact through every change; without it, an update looks like a delete plus an unrelated add | | Scope (tenant, user, agent) | A correct fact restored into the wrong scope is a new incident, not a fix | | Content before and after | The diff is what a reviewer reads | | Operation | Add, update, delete, invalidate, revert; a revert should be visible as a revert | | Source | The message, document, tool result or background job that caused the write | | Reason | Why the writer decided this replaced the old value | | Recorded time and valid time | When you learned it, and when it became true in the world; these differ more often than people expect | | Writer identity | The model, job or person that made the change, including the model version | The managed APIs already carry most of these. \[Mem0's history endpoint\](https://docs.mem0.ai/api-reference/memory/history-memory) (\`GET /v1/memories/{memory\_id}/history/\`) returns, per entry, the \`old\_memory\`, the \`new\_memory\`, the \`event\` (\`ADD\`, \`UPDATE\` or \`DELETE\`), the conversation \`input\` that led to the change, and the user, agent, app and session identifiers. Vertex AI Memory Bank lets you attach \`revision\_labels\` (arbitrary key-value pairs) when generating memories and filter revisions by them later, which is how you tag every write from one import job so you can find and undo them as a batch. Anthropic attributes writes made by an agent through a \`read\_write\` mount to the session that made them. The field teams most often skip is valid time. "The customer moved their data to Mumbai on 3 November" was recorded on 5 November; if you only keep the recorded time, every question about what was true on 4 November gets the wrong answer. Temporal graph systems such as \[Graphiti\](https://help.getzep.com/graphiti/getting-started/overview) build this in with a bi-temporal model and invalidate an edge rather than deleting it, so point-in-time queries keep working after the fact changes. ## Per-memory revisions or whole-store snapshots There are two granularities, and you usually want both. \*\*Per-memory revisions\*\* are what the managed APIs give you. Each item has its own history and you revert one item at a time. That is the right tool when a single extraction went wrong: the agent misread "I am in New York this week" as "I moved to New York", and you want that one fact back without touching anything else the agent learned since. EverMind makes the same point in its post: a single correction preserves valid updates that a broad restore would throw away. \*\*Whole-store snapshots\*\* let you put the entire memory back to a consistent point in time. That is the right tool when a batch went wrong: a bulk import with a bad field mapping, a consolidation job that merged records it should not have, or a poisoning attempt that wrote dozens of plausible facts. Reverting thousands of items one by one is not a plan. ChronoMem implements this by committing a whole-memory snapshot on every write, appending each write to an immutable event log, and keeping a version pointer (HEAD) that a rollback moves; it trades storage for fast restoration rather than replaying the whole log. If your store only offers per-memory history, you can still get a store-wide restore by recording a checkpoint time before any risky operation and, on rollback, reverting every item to its latest revision at or before that time. The Python build further down does exactly that, and the revision labels in Vertex AI are the managed equivalent of tagging the risky batch so you can find it again. ## How to roll back a bad memory write \*\*Revert forward, do not truncate.\*\* A rollback should create a new revision whose content equals the old one, with the operation marked as a revert and the reason recorded. History only grows. If you delete the later revisions instead (ChronoMem's rollback algorithm does truncate the versions after the restore point), you lose the record of what the agent believed between the bad write and the fix, which is exactly the period you will be asked about when a user complains. What each platform gives you for this, as of 13 November 2026: - \*\*Anthropic, Managed Agents memory stores\*\* (beta header \`agent-memory-2026-07-22\`). Every mutation creates an immutable \`memver\_\` version, and versions belong to the store, so they survive the deletion of the memory itself. There is no dedicated restore endpoint: you retrieve the version you want with \`memory\_versions.retrieve()\` and write its \`content\` back with \`memories.update()\`, or \`memories.create()\` if the memory was deleted and the version is still retained. Individual memories are capped at 100 kB and a store holds at most 10,000 memories. - \*\*Google, Vertex AI Agent Engine Memory Bank.\*\* Revisions are on by default, and an immutable revision is saved each time a memory is created or modified. \`RollbackMemory\` takes a \`target\_revision\_id\`, and a \`GenerateMemories\` response carries a \`previous\_revision\` value so you can undo what one generation call did. Revisions can be switched off per instance (\`disable\_memory\_revisions\`) or per request, which is worth checking in any project you inherit. - \*\*Mem0.\*\* The history endpoint gives you the before-and-after of each change. The pages I read describe history and an update endpoint, not a rollback call, so a revert is an update that writes the old text back. - \*\*Letta.\*\* \[MemFS\](https://docs.letta.com/letta-code/memfs) holds each agent's memory in a git repository, and every memory edit is committed, so \`git log\` and \`git revert\` are your history and rollback. Letta's \[context repositories post\](https://www.letta.com/blog/context-repositories) (12 February 2026) adds that its memory defragmentation skill backs up the memory filesystem before it reorganises it. Local-only agents commit to a repository on the current machine, and Letta's docs say you are responsible for backing that up. - \*\*Hindsight.\*\* The \[memories API\](https://hindsight.vectorize.io/developer/api/memories) lets you invalidate a memory with a reason, which removes it from recall, consolidation and the knowledge graph while keeping it for audit, and restore it later by setting its state back to valid. \*\*Pause writes while you restore.\*\* A restore that runs while the agent keeps writing ends with a store that matches neither the old state nor the new one. Hold the write path (or queue writes) for the scope being restored, and restore inside the same tenant and user boundaries the application uses in normal operation. \*\*Find out why the bad write happened before you resume.\*\* If the cause was the extraction prompt or a bad source document, the next run will write the same mistake back. Hindsight's docs make a sharp version of this point: editing or invalidating a memory does not change the document it came from, and reprocessing that document resets the curation of the facts it produced, because extraction runs fresh from the original text. Fix the source or the rule, then reprocess. Prevention is cheaper than any rollback: attribute every fact to the person or agent it belongs to, keep its source, stage bulk imports and review them before they become the default context for every task, keep user preferences apart from the agent's own procedures, and put a \[type-aware write policy\](https://www.maximem.ai/blog/agent-memory-write-policy) in front of the store so the class of write that went wrong is blocked next time. ## Restoring the index together with the memory Most rollbacks quietly fail at this step. The stored record is not the only copy of a memory. There is an embedding in a vector index, a keyword index entry, graph edges that link it to entities, summaries and observations derived from it, and often a cache. If you put the old record back and leave the rest, the agent keeps retrieving the bad value, because retrieval reads the index, not the record. EverMind describes the layout of its own open-source EverOS this way: Markdown files are the source of truth, SQLite holds state and LanceDB holds the retrieval indexes, and restoring older Markdown while keeping incompatible retrieval state can leave the agent searching a different version of its knowledge than the one you restored. ChronoMem's rollback algorithm removes the rolled-back versions from its retrieval backend as part of the same operation, for the same reason. Some vendors do the cascade for you. When you edit a fact in Hindsight, it re-embeds the fact, drops the observations and links derived from the old version, and re-consolidates, asynchronously after the edit returns, so there is a short window where the record is fixed and the derived layer is not. Letta's MemFS has no vector index by default (agents read Markdown with file tools), so the problem only appears once you add the optional semantic search. Whatever you run, treat this as an invariant you can check in code: \*\*the index holds exactly the current version of every memory, nothing older and nothing missing.\*\* After any restore, compute the set of version identifiers the index holds and compare it with the set of current versions. Anything in the first set and not the second is stale retrieval. Anything in the second and not the first is a memory the agent cannot find. Both should be empty before you let writes resume. ## How to test a restore before you trust it A restore is not done when the command returns. Run a small set of questions with known answers against the restored store, and check three things for each: the fact that was wrong now comes back right, a neighbouring fact that should not have changed did not change, and a different user or tenant still sees only their own memory. Inspect what was retrieved as well as the final answer, because a right answer built on the wrong retrieved memory is a coincidence. If you need to reproduce how the agent behaved at the restored point, the memory alone is not enough. Keep the model, prompts, tools and retrieval configuration that were live at the checkpoint alongside it, which is where separating memory revisions from runtime versions pays off. Running restores on a copy, verifying it against invariants and known answers, and then swapping it in is the same pattern I described for \[nightly consolidation jobs\](https://www.maximem.ai/blog/agent-memory-consolidation), and for the same reason: an unattended job that rewrites memory needs a way back. ## Finding the answers that relied on a fact that has since changed Rolling memory back fixes what the agent says next. It does nothing for what the agent already said. If your support agent told forty customers last month that you cannot host data in India, and you launched a Mumbai region last week, those forty answers are now wrong and sitting in someone's inbox or security review. Memory versioning, run backwards, is how you find them. Kirtan Joshi built exactly this as a demo called Errata and wrote it up on \[DEV on 10 November 2026\](https://dev.to/kirtan120007/the-answers-you-already-sent-are-the-ones-hindsight-remembers-3b8e), on \[Hindsight\](https://github.com/vectorize-io/hindsight), Vectorize's open-source agent memory, with a fictional company and synthetic data (20 facts, 30 sent answers, 4 fact changes). The author calls it "a mechanism test, not an accuracy claim", so treat the figures below as developer-reported on that set. Three design choices are worth copying: 1. \*\*Capture provenance at send time.\*\* When the agent drafts an answer, record which memory versions it relied on. Errata reads Hindsight's \`based\_on\` citations from each answer and stores them as the answer's dependency list. Against hand-fixed true dependencies on 30 answers, those citations had recall of 1.00 (32 of 32 true dependencies cited) and precision of 0.78, because they over-cite. For a "who might be affected" sweep, over-citing is the safe direction to fail in. 2. \*\*Give every fact version its own identity and never upsert evidence.\*\* The author's first attempt re-wrote a changed fact under the same document ID, which Hindsight treats as an upsert that deletes the old memories, so the IDs that earlier answers cited stopped resolving and the audit trail broke. The fix was a new document ID per fact version, writing the new version first and then invalidating the old one with a reason. 3. \*\*Separate "depended on a changed fact" from "is now wrong".\*\* An answer that told a US credit union its data stays in the US is still true after an India region launches. Errata asks a model for a verdict per affected answer and only accepts "needs correction" if the model quotes a sentence that really appears in the sent answer; otherwise the answer goes to "needs review". Across two local runs, no truly affected answer was cleared by mistake (0 of 12 false clears), and the precision of "needs correction" was 0.55, partly because chatty answers restated neighbouring facts. Each verification took about 38 seconds on a laptop. The write-up also checks the forward direction: on Hindsight Cloud, after the four changes, Hindsight's reflect answered 8 of 8 fresh questions correctly with the old, now-wrong sent answers still in the bank, against 4 of 8 for plain BM25 over the same raw documents with the same answer model and 2 of 8 for a model with no memory. Eight questions is a very small test; read it as evidence that invalidation keeps old versions out of new answers on this data, not as a ranking of retrieval methods. The general recipe does not depend on Hindsight. Log every outbound answer with the memory version identifiers it used. When a memory gets a new current version, query for answers whose dependencies include an older version of that memory. Send that list through a verdict step that has to point at the exact sentence that is now false, and route everything uncertain to a person. Nothing should be sent to a customer automatically. ## Why prompting is not a rollback The tempting shortcut is to leave the store alone and tell the model to ignore the bad information. ChronoMem tested this directly. After the agent had seen the full conversation stream, it was asked to answer as if only an earlier memory state existed. On LoCoMo, scored by question-answering F1 (no judge model), prompt-only rollback scored 2.3%, 2.8% and 0.9% with Llama-3.1-8B, Qwen2.5-7B and Mistral-7B as the answer models; retrieval over the full history scored 28.9%, 27.1% and 19.4%; and restoring the snapshot scored 36.1%, 38.5% and 31.3%. The paper's main text does not state how many rollback queries the test used, and these are small open models, so the absolute numbers will not transfer. The direction is the useful part: once the information is in context or in the index, instructions do not take it out. The state has to change. ## Consolidation and dreaming are writes too Background memory jobs are the largest single source of memory rewrites, and they deserve the most history. Anthropic's 6 November research post, \[The components of agent memory\](https://www.anthropic.com/research/components-of-agent-memory), shows dreaming at its best: Claude checked a wrong note about a Pokémon map against the session transcript, caught the error and rewrote the note, and the corrected run got through the cave the uncorrected run never left. The same post shows the risk. When several agents wrote to one shared table of contract terms while building a wiki, they overwrote each other's work and only 41 of 57 graded facts survived; with one agent per contract writing its own page, 57 of 57 survived. Overwrites lose facts, and nobody notices until the brief is graded. A rewrite by a background job is a write like any other and should land as a revision with the job as its source. On Anthropic's memory stores that happens by construction, because every mutation creates a version. On Letta it happens because every memory edit is a git commit, and memory subagents such as dreaming work in git worktrees. If you run your own job over your own store, label every revision it makes with the run identifier, so that undoing a bad night is one store-wide restore to the checkpoint taken before the run. ## Versioning and the right to erasure Version history and deletion pull in opposite directions. A history that keeps everything also keeps the personal data a user asked you to erase, and an erasure that deletes the current record but leaves forty old revisions has not erased anything. The \[GDPR guide on agent memory\](https://www.maximem.ai/blog/gdpr-compliant-agent-memory) covers erasure that reaches derived facts, embeddings and backups; revision history belongs on that list. The managed platforms show the two workable patterns. Anthropic offers \`memory\_versions.redact()\`, which scrubs content out of a historical version while keeping the audit record of who did what and when, and its docs name user deletion requests and leaked secrets as the use cases; the current head of a live memory cannot be redacted, so you write a new version or delete the memory first. Retention is the other lever: Anthropic keeps versions for 30 days after they are written (the recent versions of a live memory are always kept), Vertex AI's default revision TTL is 365 days and configurable, and Vertex keeps a deleted memory's revisions for 48 hours after the parent is deleted. Whatever you build, write down your retention period, make erasure walk the revision table and the derived layers, and test that a redacted value cannot be retrieved or restored. ## A tested Python build: revisions, index sync and stale-answer tracing The build below is about 120 lines of standard-library Python on SQLite. It keeps an append-only \`memory\_versions\` table, a \`head\` pointer per memory, an FTS5 full-text index that only ever holds head versions, and an answer log with dependencies. Six tests pass on Python 3.13: history is kept on every write, a revert is a new version and re-syncs the index, moving the head without the index leaves the agent retrieving the bad value (the failure this article is about), a store-wide restore to a checkpoint undoes a bad batch without deleting history, scope holds after restore, and the stale-answer query finds the one sent answer that relied on a changed fact and not the one that did not. \`\`\`python import sqlite3 SCHEMA = """ CREATE TABLE memory\_versions ( id INTEGER PRIMARY KEY, memory\_key TEXT NOT NULL, -- stable identity, e.g. 'acme:region' scope TEXT NOT NULL, -- tenant or user the fact belongs to content TEXT NOT NULL, op TEXT NOT NULL, -- write | revert source TEXT NOT NULL, -- message, document or job that caused the write reason TEXT NOT NULL, recorded\_at REAL NOT NULL ); CREATE TABLE head (memory\_key TEXT PRIMARY KEY, version\_id INTEGER NOT NULL); CREATE VIRTUAL TABLE search\_index USING fts5(content, version\_id UNINDEXED, memory\_key UNINDEXED); CREATE TABLE answers (id INTEGER PRIMARY KEY, recipient TEXT, question TEXT, text TEXT, sent\_at REAL); CREATE TABLE answer\_deps (answer\_id INTEGER, version\_id INTEGER); """ class VersionedMemory: def \_\_init\_\_(self): self.db = sqlite3.connect(":memory:") self.db.executescript(SCHEMA) self.clock = 0.0 def \_now(self): self.clock += 1.0 # deterministic clock for tests return self.clock def \_reindex(self, key, version\_id): # the index only ever holds the head version of each key self.db.execute("DELETE FROM search\_index WHERE memory\_key = ?", (key,)) content = self.db.execute("SELECT content FROM memory\_versions WHERE id = ?", (version\_id,)).fetchone()\[0\] self.db.execute("INSERT INTO search\_index (content, version\_id, memory\_key) VALUES (?, ?, ?)", (content, version\_id, key)) def write(self, key, scope, content, source, reason, op="write"): cur = self.db.execute( "INSERT INTO memory\_versions (memory\_key, scope, content, op, source, reason, recorded\_at) " "VALUES (?, ?, ?, ?, ?, ?, ?)", (key, scope, content, op, source, reason, self.\_now())) vid = cur.lastrowid self.db.execute("INSERT OR REPLACE INTO head VALUES (?, ?)", (key, vid)) self.\_reindex(key, vid) return vid def history(self, key): return self.db.execute( "SELECT id, content, op, source, reason FROM memory\_versions WHERE memory\_key = ? ORDER BY id", (key,)).fetchall() def revert(self, key, to\_version\_id, reason): # a rollback is a new version with old content: history only ever grows scope, content = self.db.execute( "SELECT scope, content FROM memory\_versions WHERE id = ? AND memory\_key = ?", (to\_version\_id, key)).fetchone() return self.write(key, scope, content, f"revert-of:{to\_version\_id}", reason, op="revert") def restore\_as\_of(self, t, reason): # store-wide restore: every key goes back to its latest version recorded at or before t keys = \[r\[0\] for r in self.db.execute("SELECT DISTINCT memory\_key FROM memory\_versions")\] for key in keys: row = self.db.execute( "SELECT id FROM memory\_versions WHERE memory\_key = ? AND recorded\_at <= ? ORDER BY id DESC LIMIT 1", (key, t)).fetchone() current = self.db.execute("SELECT version\_id FROM head WHERE memory\_key = ?", (key,)).fetchone()\[0\] if row and row\[0\] != current: self.revert(key, row\[0\], reason) def check\_index(self): # invariant: the index holds exactly the head versions, nothing older, nothing missing heads = {r\[0\] for r in self.db.execute("SELECT version\_id FROM head")} indexed = {r\[0\] for r in self.db.execute("SELECT version\_id FROM search\_index")} return {"stale\_in\_index": sorted(indexed - heads), "missing\_from\_index": sorted(heads - indexed)} def search(self, query, scope): return self.db.execute( "SELECT s.version\_id, s.content FROM search\_index s JOIN memory\_versions v ON v.id = s.version\_id " "WHERE search\_index MATCH ? AND v.scope = ? ORDER BY rank", (query, scope)).fetchall() def record\_answer(self, recipient, question, text, used\_version\_ids): # provenance at send time: which memory versions this answer relied on aid = self.db.execute("INSERT INTO answers (recipient, question, text, sent\_at) VALUES (?, ?, ?, ?)", (recipient, question, text, self.\_now())).lastrowid self.db.executemany("INSERT INTO answer\_deps VALUES (?, ?)", \[(aid, v) for v in used\_version\_ids\]) return aid def answers\_relying\_on\_stale(self, key): # provenance used backwards: sent answers that cited a version of \`key\` that is no longer head return self.db.execute( "SELECT DISTINCT a.id, a.recipient, a.question, d.version\_id FROM answers a " "JOIN answer\_deps d ON d.answer\_id = a.id JOIN memory\_versions v ON v.id = d.version\_id " "WHERE v.memory\_key = ? AND d.version\_id != (SELECT version\_id FROM head WHERE memory\_key = ?) " "ORDER BY a.id", (key, key)).fetchall() \`\`\` The test that matters most is the one that fails on purpose: \`\`\`python def test\_restoring\_store\_without\_index\_leaves\_stale\_retrieval(): m = build() good = m.history("acme:region")\[0\]\[0\] m.write("acme:region", "acme", "Customer data for Acme is stored in eu-west-1", "chat-2207", "ambiguous message") # the "restore the file only" mistake: move the head pointer, skip the index m.db.execute("UPDATE head SET version\_id = ? WHERE memory\_key = 'acme:region'", (good,)) report = m.check\_index() assert report\["stale\_in\_index"\] and report\["missing\_from\_index"\] == \[good\] assert "eu-west-1" in m.search("stored", "acme")\[0\]\[1\] # the agent still retrieves the bad value \`\`\` A production system needs what this build leaves out: embeddings and derived summaries (each one another index \`check\_index\` must cover), a write lock per scope during restore, the verdict step for stale answers, and redaction plus a retention period, because keeping every revision forever is the opposite of what an erasure request needs. ## Build it or buy it: what to ask a memory vendor Versioning is easy to claim and easy to test. Ask a vendor whether every write, background jobs included, produces an immutable revision with its source and reason; how long revisions are kept and whether they survive deletion of the memory; whether a rollback re-syncs embeddings, graph links and derived summaries; and whether a historical revision can be redacted for an erasure request. Then write a bad fact, roll it back, and check that search no longer returns it. The \[nine failure modes of home-built memory\](https://www.maximem.ai/blog/what-breaks-when-you-build-agent-memory) are a good list to run against any vendor's rollback, because several of them, deletes that miss derived copies and duplicates that outvote corrections among them, are rollback bugs in disguise. ## Where Maximem Synap fits \[Maximem Synap\](https://www.maximem.ai/synap) stores memory as discrete statements rather than passages of text, which is what makes per-fact history possible. When a new statement replaces an old one, nothing is destroyed: the old statement is marked historical and linked to what replaced it, along with the reason, and every memory can be traced to where it came from, what it replaced, why it changed and what it used to say. When a customer asks why their agent believes something, the answer can be read in the dashboard rather than guessed at. A replacement that would lose detail is refused, so "takes a cholesterol medication" never overwrites "takes 20mg of Atorvastatin daily". Synap's per-agent memory architecture (what gets extracted, into which categories, how it is scoped and ranked) is reviewed by a person before it applies, is versioned, and can be rolled back, which covers the schema-version layer of the three described at the top. Answer-level provenance, the backwards trace from a changed fact to sent answers, sits in your application: log the memories each answer used, and the history above tells you when one of them changed. ## Frequently asked questions ### What is agent memory versioning? Agent memory versioning keeps every change to an agent's stored memory as an immutable revision, with the source and reason for the change, so that a bad update can be inspected and reverted instead of overwriting the previous value for good. It covers individual facts and notes, and a good implementation also keeps the retrieval index and derived facts consistent with whichever version is current. ### How do I roll back a bad memory write? Find the revision before the bad write, write its content back as a new revision marked as a revert, and record why. On Vertex AI Memory Bank that is \`RollbackMemory\` with a \`target\_revision\_id\`; on Anthropic's memory stores you retrieve the old version and write its content back with an update; on Letta it is a git revert on the memory repository. Then re-sync the index, check the fact with a known question, and fix whatever caused the bad write so it does not come back. ### Why is restoring the memory file not enough? The agent retrieves through an index, not by reading the stored record. If the embeddings, keyword index, graph links or derived summaries still hold the bad version, the agent keeps finding it after you restore the record. Restore or rebuild every derived layer in the same operation, and check that the index holds exactly the current version of each memory. ### How do I find answers my agent already sent that used an outdated fact? Record the memory version identifiers each answer relied on when it is sent. When a fact gets a new version, query for answers whose dependencies include an older version, then have a verdict step decide whether each answer is actually wrong, requiring it to quote the sentence that is now false, and send uncertain cases to a person. Depending on a changed fact does not always make an answer wrong. ### Can I just tell the model to ignore the bad memory? Not reliably. In the ChronoMem paper's LoCoMo test, asking the model to answer as if later information did not exist scored between 0.9% and 2.8% F1 across three small open models, against 31.3% to 38.5% for actually restoring the earlier memory state. Change the stored state and the index; do not rely on instructions. ## Sources All retrieved 13 November 2026. - Anthropic, \[Managed Agents memory documentation\](https://platform.claude.com/docs/en/managed-agents/memory) (memory versions, redaction, retention, limits) - Anthropic, \[The components of agent memory\](https://www.anthropic.com/research/components-of-agent-memory), 6 November 2026 - Google Cloud, \[Memory revisions, Vertex AI Agent Engine Memory Bank\](https://docs.cloud.google.com/agent-builder/agent-engine/memory-bank/revisions), last updated 12 November 2026 - Mem0, \[Memory history API reference\](https://docs.mem0.ai/api-reference/memory/history-memory) and \[update memory API reference\](https://docs.mem0.ai/api-reference/memory/update-memory) - Letta, \[MemFS documentation\](https://docs.letta.com/letta-code/memfs) and \[Introducing Context Repositories\](https://www.letta.com/blog/context-repositories), 12 February 2026 - Vectorize, \[Hindsight memories API\](https://hindsight.vectorize.io/developer/api/memories) - Zep, \[Graphiti overview\](https://help.getzep.com/graphiti/getting-started/overview) - EverMind, \[AI Memory Versioning: Tracking Changes in Agent Memory\](https://evermind.ai/blogs/ai-memory-versioning-tracking-model-evolution), 11 November 2026 - Su, Xu, Zuo and Bertino, \[ChronoMem: Version Control and Semantic Rollback for Large Language Model Agent Memory\](https://arxiv.org/abs/2607.27773), arXiv 2607.27773v2, 5 August 2026 - Kirtan Joshi, \[The answers you already sent are the ones Hindsight remembers\](https://dev.to/kirtan120007/the-answers-you-already-sent-are-the-ones-hindsight-remembers-3b8e), DEV, 10 November 2026 (synthetic data; developer-reported)

---

Source: [https://www.maximem.ai/blog/agent-memory-versioning](https://www.maximem.ai/blog/agent-memory-versioning)
