How to Evaluate AI Agent Memory: A Step-by-Step Method
Most teams evaluate agent memory by chatting with it. Someone asks the agent to recall something from three turns ago, it does, and the team ships. That method catches nothing that doesn't happen to come up in the conversation someone chose to have, it produces no number to compare against last week's build, and it can't be run in CI. The alternative is a corpus with a known answer for every question, an adapter contract that keeps the system under test blind to those answers, and a scoring pass on two separate axes — what the system retrieved, and what it got right. None of that requires building your own benchmark from scratch; the method below is the same one DecisionSynth Bench's harness implements, and it applies whether you're evaluating a vendor's memory product, a custom RAG pipeline, or your own agent's context-management layer.
Step 1: Get a corpus with a real answer key
Everything downstream depends on this step being solid. A QA pair is only useful for evaluation if the correct answer is knowable independent of the system being tested — otherwise you're not scoring the memory system, you're scoring your own judgment about whether its answer sounds plausible.
There are two legitimate ways to get that answer key. The first is hand-labeling: a human reads the source material and writes down the correct answer, ideally after a separate LLM-seeding pass to generate candidates for the human to filter and refine (this is LongMemEval's approach — 500 questions, LLM-seeded then manually rewritten and decomposed into evidence statements by annotators). The second is generation with the answer built in: a deterministic process creates the scenario and emits the ground truth in the same pass, so there's no separate labeling step to get wrong (this is DecisionSynth Bench's approach — every episode's outcome, override reason, and cited rule are typed fields the generator writes, not a human's after-the-fact interpretation).
Either is defensible. What isn't defensible is skipping this step — using an LLM to generate both the question and a 'gold' answer with no independent verification, then scoring another LLM's response against that ungrounded answer. That measures agreement between two models, not correctness.
Step 2: Ingest through an adapter that never sees the answers
The system under test needs a single, narrow contract: ingest the corpus once, then answer each question using only what it retrieved from its own memory. The ground truth — the answer key, the evidence ids, any distractors — must never reach the system being scored. This sounds obvious until you're debugging a harness at 11pm and it's tempting to pass a little extra context through 'just for this run.'
The adapter pattern keeps that boundary enforced in code, not in discipline. DecisionSynth Bench's harness defines it as two methods: one to ingest the corpus, one to answer a single task, with the runner stripping the answer key and evidence ids before the task ever reaches the adapter.
- ·One file per system, self-contained apart from that vendor's own SDK — a Mem0 adapter, a Zep adapter, and a Letta adapter don't share internals
- ·The runner calls ingest() exactly once, then answer() once per task — no retries that leak information across questions
- ·A matched retrieval budget (the same k across every system under test) so no adapter gets a structural advantage from being allowed to retrieve more evidence than the others
interface MemoryAdapter {
name: string;
// Called once with the full corpus before any questions.
ingest(episodes: EpisodeRecord[]): Promise<void> | void;
// Ground truth is NEVER passed in — the runner strips it first.
answer(task: QaTaskWithoutAnswers): Promise<SystemAnswer> | SystemAnswer;
}
interface SystemAnswer {
task_id: string;
evidence_ids: string[]; // what the system retrieved, best-first
answer_key: Record<string, unknown>; // typed answer, same shape as the real key
}Step 3: Score two axes, not one
A memory system can fail in two independent ways: it can retrieve the wrong evidence, or it can retrieve the right evidence and still answer wrong. Collapsing both into a single pass/fail score hides which failure you're looking at, so the method scores them separately and reports both.
Retrieval is scored as precision and recall at k — of the evidence the system returned, how much was actually relevant (precision), and of the evidence that was relevant, how much did the system find (recall). Answer correctness is scored as exact match against the typed ground-truth key: an unordered set match for a question like 'which episodes overrode for this reason,' an ordered match for 'what happened first, second, third,' and a field-wise match everywhere else. Exact match on a typed field, not a free-text similarity score, is the point — 'approximately right' is not a category a compliance reviewer accepts, so the scoring shouldn't accept it either.
The two axes matter because they diagnose differently. A system with high recall and low exact-match retrieved the right material and still got the answer wrong — a reasoning or extraction problem downstream of retrieval. A system with low recall and (occasionally) correct answers got lucky, or is answering from something other than what it actually retrieved — worth knowing before you trust it in production.
Step 4: Read the score against a baseline, not in isolation
A raw score means little without a reference point. The reference point that matters most is a baseline that stores everything verbatim and retrieves by simple lexical overlap — no extraction, no summarization, no graph, just grep. That baseline isn't a strawman; it's the ceiling. Production memory systems compress by design (they extract facts, summarize, or build a graph, because storing every raw record forever doesn't scale), and compression is lossy. The gap between a system's score and the verbatim baseline's score is a direct measurement of what that system's compression lost.
On DecisionSynth Bench's public dev set (591 episodes, 1,487 QA tasks), the committed verbatim baseline scores 0.950 overall exact-match, 0.292 precision@5, and 0.972 recall@5. That precision number looks low next to the other two until you read the footnote: for a single-evidence task, precision@5 is mathematically capped at 0.2 (one relevant id out of five returned), so 0.292 overall is actually close to the ceiling once the multi-evidence task types are folded in. Reading a benchmark table means checking what's structurally bounded before treating a number as a weakness.
- Run the baseline first, on the same corpus and the same k, before evaluating anything else — it's the number everything else gets compared against
- Report per-task-type scores, not just an overall average — a system can look fine in aggregate while failing one task type completely
- Disclose the retrieval budget (k) and any model/config details for the system under test — a score with no configuration attached isn't reproducible, and isn't really a score
Fairness rules that make a score defensible
A benchmark score only means something if someone else can reproduce it, or at least audit how it was produced. The rules that keep a scoreboard honest are simple and worth stating explicitly rather than assuming: no ground truth reaches the adapter under any circumstance, every system gets the same retrieval budget, the exact configuration (model names, index settings) needed to reproduce a run ships alongside the score, and published rows come from reruns on a held-out set the system couldn't have seen during development — not from whatever number a vendor self-reports.
That last rule is why a public dev set with answers and a private held-out set without them is a standard pattern for this kind of benchmark, not extra caution: a system that looks strong on data it could have trained on or pattern-matched tells you less than a system that holds up against households it has never seen.
Key takeaways
- A usable eval corpus needs an answer key with a real source of truth — either careful hand-labeling with human review, or ground truth emitted by the same deterministic process that created the scenario.
- The adapter contract (ingest once, then answer blind) is what keeps a memory evaluation honest — ground truth must never reach the system under test, enforced in code rather than assumed.
- Score two axes separately: retrieval (precision/recall@k) and answer correctness (typed exact match). Collapsing them into one number hides which kind of failure you're looking at.
- A raw score means little without a baseline. Compare against a verbatim-storage reference to see what your system's compression actually cost.
- Reproducibility rules — no leaked ground truth, matched retrieval budgets, disclosed configuration, held-out reruns — are what separate a defensible benchmark from a vendor's self-reported number.
FAQ
Do I need a private held-out set to evaluate my own memory system?+
Not necessarily — for internal iteration, a public dev set with answers is fine, since you're not trying to defend the score to a skeptical third party. A held-out set matters when the score is going to be published or used to compare vendors, because it closes the gap between 'looks good on data it might have seen' and 'actually generalizes.'
What retrieval budget (k) should I use?+
Whatever you choose, use the same one for every system in the comparison. DecisionSynth Bench's committed baseline runs at k=5; the specific value matters less than holding it constant, since a larger k structurally favors recall at the cost of precision for any system.
Is exact match too strict? What about answers that are 'close enough'?+
For typed fields — an outcome, an override reason, a list of episode ids — exact match is the correct standard, because the underlying question has one correct answer and a compliance reviewer isn't going to accept 'roughly the right decision.' Exact match becomes too strict only when the underlying field itself is free text with legitimate paraphrase room, which is a schema design problem to fix upstream, not a scoring problem to soften downstream.
How do I score a system without ground truth on my own private data?+
Cross-model disagreement is a workable proxy: have two different model families independently audit the same system's answers against the same retrieved context, without either seeing an answer key. High disagreement between the two judges tends to track low underlying quality even when there's no ground truth to check against directly — useful for monitoring a system in production, where answer keys don't exist by definition.
What's the minimum corpus size for a meaningful evaluation?+
It depends more on task-type coverage than raw count. A handful of examples per task type produces a directional signal, not a statistically robust one — report the exact task count per type rather than an aggregate score when a category is small, so anyone reading the results can judge how much weight it deserves.
Can I evaluate a commercial memory product (Mem0, Zep, Letta) with this method?+
Yes — that's exactly what the adapter pattern is for. Each product gets its own adapter implementing the same ingest/answer contract, so its retrieved evidence and typed answers score against the same ground truth and the same rules as every other system in the comparison, including the verbatim baseline.