Seeding Facts the Model Must Not Forget
The complaint that starts every memory investigation is unanswerable on its own: "it forgot what I told it." There is no ground truth in production logs — you cannot score an answer when nobody recorded what the answer should have been. So the suite manufactures ground truth: write a fact into a session, bury it under real turns, then ask.
CASES = [
{"seed": ["My standup moved to 9:15am UTC starting Monday."],
"bury": 40,
"probe": "What time is my standup?",
"expect": "9:15"},
{"seed": ["Decision: ship without the export button."],
"bury": 60,
"probe": "Are we shipping the export button?",
"expect": "without the export button"},
]
def evaluate(cases, session, ask, bury=None):
hits = 0
for c in cases:
depth = bury if bury is not None else c["bury"]
session.reset()
session.load(seeded_history(c["seed"])) # plant the fact
session.bury(filler_turns(depth)) # then hide it under real dialogue
answer = ask(c["probe"], session.history())
hits += c["expect"].lower() in answer.lower()
return hits, len(cases)
Input: 12 cases, each planting one fact and burying it under 40–120 filler turns before probing.
Output: (7, 12) — 58% recall, and every one of the 5 misses came from a case with 60 or more turns between the seed and the probe.
Three properties make this a measurement rather than a demo. The expected value is known before the model answers, so scoring is mechanical. The filler is real turns — trimmed, summarized, re-ordered by whatever the session under test does — because synthetic filler never exercises the retention policy that usually causes the loss. And distance is a parameter: varying bury turns a single suite into a curve, which is where the interesting failure lives.
The seeds must survive whatever compression the session applies, so a suite that plants a fact and then loses it inside summarization is measuring Summarizing Long Conversations as much as the store itself. Designing the datasets properly — paraphrases, distractors, unseeded negatives — is Building Eval Datasets.
Forgetting Wrong and Remembering Never
Recall alone scores only half the failure space. A system that always repeats the first thing it heard is perfect on recall and unusable in practice, while one that invents confident details never fails a "did it forget?" test either. Four metrics cover the space:
| Metric | What it counts | The bug it exposes |
|---|---|---|
| Recall accuracy | probes where the planted value appears | facts evicted by trimming or swallowed by compression |
| Staleness rate | answers repeating a superseded value | "10:00" after the user moved the meeting to 14:00 |
| False-memory rate | asserted details absent from any turn | preferences and names the user never gave |
| Correct abstention | "you never said" answers on unseeded probes | confident invention when the log has nothing to support it |
Staleness is scored against a corrected conversation — the fact exists twice, and only the newer value is true:
STALE_CASES = [
{"seed": ["Standup is at 10:00."],
"correct": ["Actually, 14:00 works better for me."],
"probe": "What time is my standup?",
"accept": "14:00",
"reject": "10:00"},
]
def staleness(cases, session, ask):
repeats = 0
for c in cases:
session.reset()
session.load(seeded_history(c["seed"] + c["correct"]))
answer = ask(c["probe"], session.history()).lower()
if c["reject"] in answer and c["accept"] not in answer:
repeats += 1 # asserted the dead value
return repeats, len(cases)
Input: 10 correction cases, each with the old value and the new value both present in the log.
Output: (2, 10) — 20% staleness: two answers insisted on the superseded time.
The false-memory half works the same way, pointed at negatives. Ask about something the log never contains — "what editor do I prefer?" when no editor was ever mentioned — and count any concrete answer as a failure while counting an abstention as a pass. Together the two directions separate the three real bugs: the memory dropped a fact (recall), the memory kept the wrong version (staleness), or the memory never had it and the model supplied one anyway (false recall). Each maps to a different fix in Managing Conversation History, Semantic Memory with Vectors, and the prompting layer respectively.
Where Recall Actually Breaks
A single accuracy number hides the only fact worth knowing: memory systems do not degrade evenly. They hold flat and then fall off a cliff at the point where the retention policy starts discarding — so the suite runs at several session lengths and reports the curve.
LENGTHS = [10, 40, 100, 250]
def sweep(cases, session, ask):
report = {}
for n in LENGTHS:
hits, total = evaluate(cases, session, ask, bury=n) # same cases, re-buried deeper
report[n] = round(hits / total, 3)
return report
Input: the 12-case suite against a session using a 1,500-token window with a rolling summary, each case re-buried at every length.
Output: {10: 1.0, 40: 0.92, 100: 0.58, 250: 0.25} — flat, then a knee between 40 and 100 turns.
The knee is the deliverable. It tells you which mechanism is failing: recall collapsing at exactly the length where trimming begins indicts the retention policy; staleness rising while recall holds indicts the summary; both flat with false-memory climbing indicts the extraction gate in the store. Fixing any of them without the curve means you can only claim a new single number, not a repaired behavior.
Failure modes in the harness itself:
- Substring scoring — expecting
9:15accepts19:15, and expecting10:00inside"not 10:00"counts as a hit. Score on normalized answers with boundaries, and treat the rejected value as a failure even when the accepted one also appears. - Identical filler every run — the suite then measures one lucky ordering. Shuffle filler and vary the seed phrasing.
- One seed wording per fact — models that pattern-match the planted sentence score well and still miss the fact when the user phrases it differently.
- Flaky gates — sampling at nonzero temperature makes the same commit pass or fail. Fix temperature or run each case three times and take the majority.
- Two changes per run — altering the trim policy and the model at once makes the curve unattributable.
- Aggregate-only reporting — one blended score lets a recall regression hide behind an abstention improvement. Publish all four metrics per length.
Wiring the sweep into CI so a merge that moves the knee blocks the build is Regression Suites; the harness structure it plugs into is Offline Eval Harness.
Quiz
1. Why plant facts instead of scoring memory against production conversations?
2. What does a high recall score with a high staleness rate describe?
3. A sweep returns {10: 1.0, 40: 0.92, 100: 0.58, 250: 0.25}. Where does the system start failing, and what does that locate?
4. Your false-memory check asks about an unmentioned preference and counts any concrete answer as a failure. What is this really testing?
Flashcards
Question
How is a labeled memory recall case constructed?
Click to reveal answer
Answer
Seed a known fact into the session, bury it under 40–120 real filler turns, then probe with a question whose expected value you wrote down before the model answered. Varying the bury distance turns the suite into a curve.
Question
What four metrics does a memory eval report, and what does each catch?
Click to reveal answer
Answer
Recall accuracy (dropped facts), staleness rate (repeating superseded values), false-memory rate (asserting details never provided), correct abstention (confident answers where the log has nothing).
Question
What is staleness in a memory system?
Click to reveal answer
Answer
Repeating a value the user has since corrected. Score it with cases where the old and new values both exist in the log: accepting the new value passes, asserting the dead one fails.
Question
Why sweep a memory eval across session lengths instead of reporting one score?
Click to reveal answer
Answer
Memory fails at a boundary, not gradually. The knee in the curve sits where the retention policy starts discarding, which identifies the failing mechanism — trim, summary, or extraction gate — instead of just the magnitude.
Question
What makes a memory eval gate reliable enough for CI?
Click to reveal answer
Answer
Fixed temperature or majority-of-three scoring, shuffled filler and varied seed phrasing per run, boundary-aware matching instead of naive substring tests, and all four metrics reported per length rather than one blended number.
Revision Notes
Key Takeaways
- 1. Ground truth is manufactured: plant the fact, bury it under real turns, record the expected answer first
- 2. Recall alone is insufficient — staleness and false-memory scores cover the two ways memory is wrong without forgetting
- 3. Sweep session lengths; the knee in the curve identifies the failing mechanism, the aggregate score does not
- 4. Score with boundaries and normalize answers, or substring tests inflate every metric
- 5. Stabilize the harness — fixed temperature, shuffled filler, per-length metrics — before letting it gate a merge
Interview Tips
- • Describe a memory bug as a measurement problem first: production logs lack labels, so you seed your own
- • Name all four metrics and the distinct fix each one points to; interviewers check that you do not collapse them into accuracy
- • Show the sweep numbers and locate the knee out loud — the reasoning matters more than the arithmetic
- • Be ready for "how do you keep the eval from being flaky?" — temperature, majority voting, and boundary-aware scoring
Cheat Sheet
Memory Evaluation Cheat Sheet
Case shape
seed -> known fact written into the session
bury -> 40/100/250 real filler turns (exercises trim + summary)
probe -> question with an expected value recorded in advance
score -> normalized match, boundaries enforced
Metrics
| Metric | Failure it exposes |
|---|---|
| Recall accuracy | fact evicted by trimming or compression |
| Staleness rate | repeated superseded value after a correction |
| False-memory rate | asserted detail absent from every turn |
| Correct abstention | confident answer where the log has nothing |
Staleness case: seed "Standup is at 10:00." + correction "Actually 14:00." -> accept 14:00, reject 10:00. Repeating the dead value counts even when the new one also appears.
Sweep: lengths [10, 40, 100, 250] -> report the curve. Flat-then-cliff means a bounded mechanism; the knee locates the eviction boundary.
Read the knee: recall falls -> retention policy. Staleness rises while recall holds -> summary compression. False memory climbs -> extraction gate in the store.
Harness failure modes: naive substring matching, identical filler each run, one seed phrasing, nonzero temperature (flaky gates), two variables changed per run, single blended score hiding a component regression.
Pipeline: datasets → Building Eval Datasets → harness → Offline Eval Harness → gate → Regression Suites.
Continue Learning
Before You Start
- No prerequisites listed.