When an AI agent hallucinates in a consumer app, it is annoying. When an AI agent hallucinates in a financial compliance review or a healthcare diagnostic pipeline, it is a liability event.
Most tutorials on agent memory tell you to connect an LLM to a vector database, run a similarity search, and overwrite the user profile when new information arrives. If you use an UPDATE statement on an agent's memory store in a regulated environment, you have already failed the audit.
Regulated agents require you to prove exactly what the agent knew at the exact millisecond it made a decision. If you overwrite a memory, the context that drove historical decisions is destroyed. You cannot debug the agent, and you cannot defend its actions to an auditor.
I recently had to build a diagnostic agent for a compliance workflow. When it failed a test case, I checked the database to see what context it had retrieved. The memory had already been modified by a subsequent turn. I couldn't reconstruct the prompt. That was when I realized we couldn't just use a standard vector store. 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 is to maintain a "working profile." When the user says, "My risk tolerance is low," the agent writes risk_tolerance: low. When the user later says, "Actually, I want to invest in crypto," the agent updates the record to risk_tolerance: high.
This destroys the audit trail. If you later need to explain why the agent recommended bonds on Tuesday and Bitcoin on Thursday, your database only shows the current state. You cannot time-travel.
This is a solved problem in traditional software. We use Event Sourcing or temporal databases. But with agents, the state isn't just a boolean flag. The state is often a high-dimensional vector or a dense semantic summary.
The Append-Only Architecture
An append-only memory architecture ensures that every memory operation is an INSERT.
Instead of a table of facts, you have 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 sets the valid_to timestamp on the old row. This allows point-in-time recovery. You can query the database for "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).
If you are using a framework like LangGraph, you can leverage 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 to know why it remembered it.
Every memory write must be linked to a reasoning trace. If the agent decides to record "User prefers cautious investments," that memory must carry a trace_id pointing to the exact prompt, user input, and LLM output that justified the extraction.
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.
If you are using Anthropic's tool use features, you can enforce this strictly by defining your update_memory tool schema to require a justification parameter. The agent cannot write to the database without explicitly stating its reasoning in the tool call payload.
{
"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
This architecture is not free.
The biggest tradeoff is storage cost and retrieval complexity. Your vector database will balloon in size because you are keeping every historical variant of a fact.
More importantly, it complicates the retrieval step. If you run a naive vector search against an append-only store, you might retrieve an outdated version of a memory simply because its vector happens to be a closer match to the query.
To solve this, you have to use a vector database that supports pre-filtering. You must filter the search to only include vectors where valid_to IS NULL. We use SQLite-vec for local testing exactly because it allows us to combine relational metadata filters with vector similarity in a single query.
Shadowing vs. Versioning
If pre-filtering vectors is too slow or unsupported by your infrastructure, the alternative is Relational Shadowing.
In this pattern, you keep a standard, mutable vector database for fast retrieval. However, every time you mutate the vector DB, you synchronously write an immutable log to a relational database like PostgreSQL.
The vector DB handles the semantic search. The relational DB handles the audit. If you ever need to reconstruct the past, you pull the event log from Postgres and rebuild the state. This is what we covered when discussing state persistence across sessions. It keeps the agent fast while keeping the compliance team happy.
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? If you need non-repudiation (proving to a third party that the logs were not tampered with), you can cryptographically sign each memory row. You hash the payload, the timestamp, and the hash of the previous row. This creates a blockchain-like structure. It is overkill for 99% of applications, but necessary if your agent is making autonomous financial trades.