An AI agent that hallucinates in a consumer app is annoying. The same hallucination inside a financial compliance review or a healthcare diagnostic pipeline becomes a liability event. Someone signs off on the agent's output, and that signature is now attached to a wrong answer nobody can explain.
Nearly every tutorial on agent memory tells you to connect an LLM to a vector database, run a similarity search, and overwrite the user profile when new information arrives. An UPDATE statement against an agent's memory store in a regulated environment means you have already failed the audit.
Proving exactly what the agent knew at the exact millisecond it made a decision is the whole game for regulated agents. Overwrite a memory and you destroy the context that drove every historical decision. You can no longer debug the agent, and you can no longer defend its actions to an auditor who asks why it did what it did six weeks ago.
Building a diagnostic agent for a compliance workflow taught me this the painful way. The agent failed a test case, so I opened the database to see what context it had retrieved. A later turn had already modified that memory. I couldn't reconstruct the prompt the agent actually saw, which meant I couldn't tell whether the bug was in the model, the retrieval, or my own code. A standard vector store was never going to work. We needed an append-only audit trail.
Short answer: Do not use UPDATE or DELETE for agent memory. Implement an append-only architecture where every state change writes a new immutable row. Store the memory embedding alongside a cryptographic hash of the reasoning trace that triggered the change. Treat your agent's memory like an event-sourced ledger, not a CRUD table.
The problem with mutable memory
The standard approach to agent memory keeps a single "working profile." A user says "my risk tolerance is low," and the agent writes risk_tolerance: low. The user comes back a month later, says "actually, I want to put some money into crypto," and the agent overwrites the record with risk_tolerance: high.
The audit trail is gone the moment that overwrite lands. Say a regulator asks why the agent recommended municipal bonds on Tuesday and a Bitcoin ETF on Thursday. Your database shows only the current state, so you have nothing to point at. There is no time-travel, only the present.
Traditional software solved this years ago with Event Sourcing or temporal databases, where you never overwrite a record, you append the next one. Agents add a wrinkle the bank-ledger version never had: the state isn't a tidy boolean flag, it is often a 1,536-dimension embedding or a dense semantic summary that no human will diff by eye.
The Append-Only Architecture
Every memory operation becomes an INSERT under an append-only architecture. Nothing ever gets edited in place, the way a paper ledger only lets you write the next line, never erase a prior one.
Rather than a table of facts, you keep a table of events. Every row needs:
1. entity_id: Who or what this memory is about.
2. memory_content: The actual text or structured data.
3. embedding: The vector representation of the content.
4. timestamp: When this became true.
5. valid_to: When this was superseded (null if current).
6. trace_id: The specific reasoning chain or tool call that produced this memory.
When the agent needs to "forget" or "update" a memory, it inserts a new row and stamps valid_to on the old one. Point-in-time recovery falls out of that for free. You can ask the database "what were the active memories for this user at T=1" by filtering WHERE timestamp <= T1 AND (valid_to IS NULL OR valid_to > T1), and get back exactly the context the agent saw at that instant.
If you are using a framework like LangGraph, you can use its built-in thread-level checkpointing to maintain some of this state. But thread checkpoints do not persist long-term semantic knowledge across separate sessions. For that, you need a dedicated memory ledger.
Linking memory to reasoning
Knowing what the agent remembered is only half the battle. You also need why it remembered it.
Link every memory write to a reasoning trace. When the agent records "user prefers cautious investments," that row must carry a trace_id pointing to the exact prompt, user input, and LLM output that justified the extraction. Otherwise you end up staring at a fact with no provenance, unable to say whether the user actually requested caution or the model inferred it from one offhand sentence about not liking surprises.
Mem0, an open-source memory layer, handles this by treating memory extraction as a distinct, loggable event. Their architecture separates the storage of the memory from the extraction process, making it easier to attach metadata to the operation.
With Anthropic's tool use features, you can enforce this at the schema level by making your update_memory tool require a justification parameter. The agent physically cannot write to the database without stating its reasoning in the tool call payload, the same way a strict expense system refuses a reimbursement that arrives without a receipt attached.
{
"name": "write_memory",
"description": "Store a new fact in long-term memory. You must justify this extraction.",
"input_schema": {
"type": "object",
"properties": {
"fact": {"type": "string"},
"justification": {"type": "string", "description": "The exact quote or reasoning that validates this fact"}
},
"required": ["fact", "justification"]
}
}
The cost of immutability
None of this comes free.
Storage cost and retrieval complexity are the two bills you pay. Your vector database balloons because every historical variant of a fact stays on disk forever. A user who revises their address three times now has four rows where a mutable store would have one.
The retrieval step is where it bites harder. Run a naive vector search against an append-only store and you can pull back a superseded version of a memory purely because its embedding happens to sit closer to the query than the current one. The agent then acts on the user's old address, confidently.
Reaching for a vector database that supports pre-filtering is the fix. Filter the search to vectors where valid_to IS NULL before similarity ranking runs, so deprecated rows never enter the candidate set. We use SQLite-vec for local testing precisely because it combines relational metadata filters with vector similarity in one query.
Shadowing vs. Versioning
When pre-filtering vectors is too slow or your infrastructure does not support it, Relational Shadowing is the fallback.
The pattern keeps a standard, mutable vector database for fast retrieval, and every mutation to that store also writes a synchronous, immutable log row to a relational database like PostgreSQL. The fast path and the audit path run side by side.
Semantic search lives in the vector DB, the audit record lives in Postgres. Need to reconstruct the past? Pull the event log from Postgres and replay it to rebuild the exact state. That replay-from-the-log idea is what I covered when discussing state persistence across sessions. The agent stays fast, and the compliance team still gets its paper trail.
FAQ
How long should we keep historical memory versions? Treat them like application logs. For standard debugging, 30 to 90 days is usually sufficient. For regulated environments (finance, healthcare), you must tie the retention policy to your standard data compliance rules, which can mean 7 years. You must also implement hard-delete workflows for GDPR/CCPA compliance, which breaks the immutability rule by legal necessity.
Doesn't keeping every version increase hallucination risk?
Only if your retrieval pipeline is sloppy. If you fail to filter out deprecated memories (valid_to IS NOT NULL), the agent will pull contradictory facts into its context window and hallucinate trying to reconcile them. Strict metadata filtering is mandatory.
Can we just use git for this? There is an emerging pattern called Git-ContextController where agent state is literally committed to a git repository as text files. It works beautifully for local, single-tenant agents because you get diffs, branches, and time-travel for free. It scales poorly for high-throughput, multi-tenant cloud agents because of locking and concurrency issues.
What about cryptographic signing? For non-repudiation, where you have to prove to a third party that nobody tampered with the logs, you can cryptographically sign each memory row. You hash the payload, the timestamp, and the hash of the previous row, which chains every entry to the one before it so editing any single row breaks every hash downstream. The structure is overkill for nearly every application, and warranted when your agent is executing autonomous financial trades that a counterparty might later dispute.