DEV Community

Cover image for How to Give Your AI Agent Memory You Can Audit
Mir Arshad Ali Talpur
Mir Arshad Ali Talpur

Posted on

How to Give Your AI Agent Memory You Can Audit

Giving an agent memory is easy. Knowing what it remembered, and whether that memory changed its answer, is the hard part. Most memory setups are a vector store behind a retrieval call. The agent receives some text, acts on it, and nobody records which memories were used. When the agent says something wrong, you cannot tell whether the model failed or a stale note from three months ago misled it.

This guide shows how to build agent memory that stays inspectable, using ZizkaDB’s search and context functions. The principle is simple: memory should be part of the record, so every answer can be traced to the memories behind it.

A note on scope: the SDK documents search(query, agent?) for semantic search and context_for(agent, task) for a prompt-ready memory block, and log() embeds events when tenant embeddings are configured. I could not find a public reference for the exact return fields, ranking behavior, or limits, so the code below is illustrative. Check the SDK reference before relying on specifics, and print results to see their structure.

Before you start: why unaudited memory is a risk

Memory fails in ways that are hard to see:

Stale facts. The agent recalls a price, policy, or preference that stopped being true.
Wrong recall. A similar but irrelevant memory gets retrieved and treated as relevant.
Cross-contamination. One customer’s information surfaces in another customer’s session.
Poisoned memory. Bad or malicious content was written into memory once and now influences every later answer.
Personal data you cannot find. A user asks for deletion, and copies live in a vector store nobody mapped.
Silent cost. Every retrieved memory is more input tokens on every call.

An auditable memory addresses each one by recording what was stored, what was retrieved, and what the agent did with it.

Step 1: Decide what memory is for

Write down the job of memory in your agent, in one sentence per use. Typical uses:

User preferences and history, so the agent does not ask the same questions twice
Past resolutions, so similar issues get consistent answers
Working notes inside a long task, so progress survives a long session
Organizational knowledge, such as policies and product facts

Each use has different rules. Preferences are personal data. Policies change and need freshness checks. Working notes can expire. Decide per use what is allowed into memory, how long it should live, and who may read it.

Step 2: Store memory as events, not as a side database

Instead of a separate vector store that only the agent can see, write memory as events in the same record as everything else. Each event carries its content, a type, a session, and a link to what caused it.

python

illustrative, check the SDK reference for exact parameters

note = await db.log(
agent="support-bot",
event="memory_written",
data={
"kind": "preference",
"text": "Customer prefers email over phone contact",
"subject_id": "cust_8842",
"source": "user_message",
"valid_until": "2027-04-27",
},
session_id=session_id,
parent_id=user_event_id,
)

Three design choices make this auditable:

Link the memory to its source. The parent link means you can later answer where a memory came from and which conversation created it.

Tag a subject identifier. If the memory is about a person, include a stable identifier such as a hashed customer ID, so erasure can find it later.

Record freshness. Add a validity date or version where the fact can go stale.

Since the SDK says events are embedded when tenant embeddings are configured, check that embeddings are set up for your deployment. Without them, semantic search will not return what you expect.

Step 3: Retrieve memory with a task in mind

Before the agent answers, ask for relevant memory. The SDK offers two functions:

search(query, agent?) returns events that match a query by meaning. Use it when you want a list to inspect or filter.
context_for(agent, task) returns a prompt-ready block of relevant memory for a task. Use it when you want to inject memory straight into the prompt.
python

illustrative

hits = await db.search("customer contact preferences", agent="support-bot")
block = await db.context_for("support-bot", task="Reply to a delayed order complaint")

Print both at first. Read what comes back with a critical eye: is it relevant, current, and about the right customer? Retrieval quality is rarely what you assume on the first try.

Step 4: Record the retrieval itself

This is the step that makes memory auditable. After retrieving, log an event that says which memories were supplied to the model, and link it between the user message and the model call.

python

illustrative

retrieval = await db.log(
agent="support-bot",
event="memory_retrieved",
data={
"task": "Reply to a delayed order complaint",
"memory_event_ids": [h.event_id for h in hits], # check the field name
"memory_count": len(hits),
"approx_tokens": estimated_tokens,
},
session_id=session_id,
parent_id=user_event_id,
)

make the next LLM call a child of retrieval

Now the causal chain for any answer runs from the user message, through the retrieval, to the memories used, to the model response. When something goes wrong, zizkadb why shows whether a particular memory fed the answer. I could not confirm whether context_for records this automatically, so check one session in the dashboard, and log it yourself if it does not.

Step 4b: Use the MCP route if you work in an editor

If your agent is an MCP-capable tool such as Cursor, Claude Desktop, or Windsurf, the ZizkaDB MCP server exposes memory as tools. Package listings show log_event to record, search_memory to search by meaning, get_context for a formatted memory block, why for the causal chain, query_events to list recent events, and time_travel for past state. The setup is a short config entry with uvx zizkadb-mcp, as covered in the Cursor guide. Tool names may differ between versions, so check the tools your server lists.

The same caution applies: the agent must actually call the memory tools for anything to be stored or retrieved. A rule in your agent instructions telling it when to log and when to search helps.

Step 5: Control how much memory goes into the prompt

Memory costs money and attention. Every memory you inject is input tokens, and too many dilute the useful ones. Set limits and test them:

A maximum number of memories per call
A relevance cutoff, so weak matches are dropped
A token budget for the memory block
A preference for recent or validated memories where freshness matters

Log how many memories and roughly how many tokens each retrieval added. Over a week, that tells you what memory costs and which tasks gain from it.

Step 6: Handle stale and conflicting memories

Memory that is never updated becomes wrong. Build a simple policy:

Expiry. Use the validity date from Step 2, and skip expired memories at retrieval time.
Supersession. When a fact changes, write a new memory that references the one it replaces, so the old one is not retrieved first.
Conflict handling. If retrieval returns two memories that disagree, tell the model so, or prefer the newer one, and log which was chosen.
Review. Periodically sample stored memories of the kinds most likely to go stale, such as prices and policies.
Step 7: Keep customers’ memories apart

Make sure a retrieval for one customer can never return another’s memory. The SDK supports agent-scoped keys and tenant isolation, and you should also filter by the subject identifier where memory is personal. Then test it on purpose: write a distinctive memory for one test customer, query as another, and confirm it never appears. Keep the test result, since security reviews often ask for it.

Step 8: Plan for deletion

Memory about a person is personal data, and embeddings made from their messages are too. The SDK includes forget(filter_key, filter_value) for erasure by metadata filter, and the project describes it as covering both event logs and vector data.

python

illustrative

await db.forget("subject_id", "cust_8842")

Test it on synthetic data first. Write memories for a test subject, run the erasure, then confirm that search and retrieval no longer return anything about them, and that other customers’ memories and their audit trail are intact. This is the reason Step 2 asked for a subject identifier on every personal memory.

Step 9: See what the agent knew at a given time

When a dispute concerns an old answer, you need the memory as it was then, not as it is now. The SDK’s at(agent, timestamp) reconstructs state at a timestamp, and the MCP tool list includes time_travel. Use it to check which memories existed, and combine it with the retrieval event from Step 4 to see which of them the agent actually received. That separates the agent knew it from the agent was shown it.

Step 10: Test memory like a feature

Treat memory quality as something you measure:

Recall check. Write a few known facts, then ask related questions and confirm they are retrieved.
Precision check. Ask unrelated questions and confirm irrelevant memories are not injected.
Freshness check. Update a fact and confirm the old one stops appearing.
Isolation check. Repeat the cross-customer test after every significant change.
Poisoning check. Try writing a deliberately misleading memory, and confirm you can find it, trace what it influenced, and remove it.
Cost check. Compare tokens per session with and without memory, and compare answer quality.

Run these after changes to embeddings, retrieval settings, or prompts. Also watch the behavioral drift view, since a change in memory settings often shows up as a shift in how many retrieval and model events each session contains.

What the business case looks like

These figures are modeled assumptions, not measured results. Replace them with your own.

Assume a support agent handles 20,000 sessions a month, and 10 percent are repeat customers where memory avoids re-asking questions. If memory saves about 2 minutes of conversation on each, at 40 euro per hour of support cost equivalent, that is 2,000 sessions times 2 minutes, about 67 hours, worth roughly 2,700 euro a month.

Add the investigation side. If 8 incidents a month involve a questionable memory, and tracing it takes 5 hours without a retrieval record against 1 hour with one, at 100 euro per hour, that is about 3,200 euro a month saved.

Then the cost side. If unrestricted memory adds 1,500 input tokens to every call and you trim that to 500 through limits and cutoffs, the saving depends on your price per token and volume, so calculate it from your own usage logs.

The harder figure is risk. A customer’s private information surfacing in another customer’s session, or a deletion request you cannot fully honor, is a serious incident with legal and reputational cost. The tests in Steps 7, 8, and 10 are cheap insurance against it.

Common mistakes to avoid

Keeping memory in a separate store nobody audits. If it is not in the record, you cannot trace it.

Not logging the retrieval. Without it, you cannot tell which memories influenced an answer.

Injecting too much. More memory means more tokens and more distraction, so cap it.

Never expiring anything. Stale facts become confident wrong answers.

Forgetting subject identifiers. You cannot erase what you cannot find.

Skipping the isolation test. Cross-customer leakage is the failure that ends deals.

Assuming retrieval is relevant. Read real results, and measure recall and precision.

Where to go from here

Pick one memory use, such as customer preferences. Store it as events with a subject identifier, retrieve it before the model call, and log the retrieval. Then run the isolation and deletion tests. That gives you a memory you can explain, limit, and remove, which is the version enterprises are willing to approve.

If you tell me what your agent remembers today and where it is stored, I can help map it onto this approach and design the tests.

Want to test ZizkaDB on your agent? Try our open source version here: https://github.com/ZIZKA-AI-SL/ZizkaDB

Interested in a design partnership? Fill in the form on our site or reach me directly at founder@zizka.ai

Sources: ZizkaDB Python SDK wiki · ZizkaDB MCP and Cursor wiki · zizkadb-mcp on PyPI · ZizkaDB repository README

Top comments (0)