Topic 35

Long-Term Memory

Long-Term Memory

Writing facts back — this buyer prefers replacements, this seller always disputes damage claims — makes an agent feel remarkable in a demo and is the fastest way to build a system that is confidently wrong for months. A remembered fact is an unverified claim that will be re-read later as established truth, by a component that has no way of telling the difference between something it observed and something it once inferred.

So the design is not the storage. Any table will do. The design is what gets written, on what evidence, and when it stops counting. Sundry shipped this last of everything in this chapter, on the narrowest scope the team could define, and still had two incidents in the first month.

What Is Worth Remembering

One distinction carries the entire feature. "Chose a replacement twice, on tickets T-36540 and T-39177" is a fact: it has dates, evidence, and a person could confirm or deny it. "Prefers replacements" is a guess about somebody derived from two data points. The first is an observation. The second is a characterization, and characterizations are what become permanent context that shapes every later interaction with a person who never agreed to any of it.

Worth storing: dated outcomes and stable attributes. This address has failed delivery twice with the same carrier. This seller took nine days to answer each of the last three claims. This account is a business account. Not worth storing: anything phrased as what somebody is like. The test is whether the person described could read the record and call it factually wrong — "difficult customer" cannot be argued with, which is precisely the reason it must not be there.

Write Discipline

Extraction runs after the ticket closes, not during it, and it produces candidates rather than facts. Each candidate is validated against a schema — subject, a predicate from a closed set, a value, the ticket it came from, the date it was observed, the date it expires — and anything that fails validation is dropped and counted. The closed predicate set does more work than any prompt could: it makes "difficult" unrepresentable rather than discouraged, which is Chapter 5's lesson again in a different store.

One candidate accepted, one rejected — the schema is the control, not the instruction
# accepted: an observation, with evidence and a date
{"subject":   "buyer:41827",
 "predicate": "chose_replacement",   # one of 14 allowed predicates
 "value":     {"order_id": "SU-82605"},
 "evidence":  "T-39177",             # the ticket this was read from
 "observed":  "2026-03-10",
 "expires":   "2027-03-10"}

# rejected: an inference dressed as a fact, and nothing to cite
{"subject":   "buyer:41827",
 "predicate": "prefers_replacements",  # not in the set — dropped
 "value":     {"confidence": "high"},
 "evidence":  None}

The two records differ in one word and in everything that matters. The accepted one says what happened, on which order, read out of which ticket, and until when it counts. The rejected one states a conclusion about a person, offers a confidence score in place of evidence, and cites nothing — and note that it is rejected by the schema rather than by anyone's judgement, which is the only kind of rejection that still happens at 4,200 tickets a week.

Records are appended, never mutated. A third replacement in June adds a row; it does not overwrite March. That costs a little storage and buys the ability to remove exactly one wrong fact later without guessing what it replaced or what was built on top of it.

Three months in which the system's own output became the evidence for its next belief
March · a guess is writtentwo replacements become prefers replacements
April · read as factthe agent leads with a replacement
April · the buyer acceptsafter some back and forth
May · a second factdeclines refunds, cited to the April ticket
OFFERED TWICEJune · wants their money backtwo replacement offers, then an escalation

Compounding Error

Here is the chain that cost Sundry a month. In March an early version of the extractor wrote "prefers replacements" for a buyer who had accepted two. In April the agent read that fact, led with a replacement offer, and the buyer took it after some back and forth. In May the extractor read that April thread and wrote a second fact from it: declines refunds. By June a buyer who wanted their money back was offered a replacement twice before escalating, and the record justifying that behaviour cited a ticket in which the agent's own offer was the only evidence.

That is the shape to watch for: the system's output becomes the evidence for its next belief, and each generation looks better supported than the last. Provenance and expiry are what make it unwindable. Provenance lets you trace the May fact back to the April ticket back to the March fact and delete the root. Expiry means a wrong belief stops mattering eventually even if nobody notices it. With neither, the only remedy left is deleting the entire store — which Sundry did once, in June, and it was the right call.

Retrieval, Not Injection

Long-term facts are looked up when they are relevant, exactly like policy passages. The tempting alternative is a customer profile block pasted into every ticket's context, which is cost on 100% of the queue for value on the 14% that are repeat contacts within 90 days — and, worse, it puts stale claims about a person in front of the model on tickets where nobody asked.

Sundry's version is a lookup by subject that the agent calls when the ticket suggests history matters, returning at most three facts, each with its observed date and its evidence ticket, under the same citation rule as policy. The dates are load-bearing: a model that can see an observation is from 2024 treats it differently from one made last week, and a reply that acts on a remembered fact says which one it acted on.

What Never to Store

The never-store list is written before the feature ships, not after the first incident, and it is short: payment details, government identifiers, health information and anything from which a protected characteristic could be inferred, and speculative characterizations of a person. The first three sound too obvious to need writing down, right up until an extraction step is pointed at raw ticket text — buyers explaining why they need a refund type card numbers and medical reasons into support tickets every week.

The list is enforced in the schema and in a filter on extraction, never in a prompt asking the model to be careful. Deletion has to reach the store as well: a customer deletion request that clears the transcript and leaves the derived facts standing has deleted the evidence and kept the conclusion, which is the worst available outcome. Chapter 13 covers how that runs in production; the decision belongs here, while there is still nothing to delete.

Does It Help

Barely. On repeat contacts, resolution moved two points. Repeat contacts are 14% of Sundry's queue, so the queue-wide effect is under half a point — well inside the noise of a 120-ticket eval set, so it was measured on the repeat-contact slice specifically, and it could not have been measured at all if the eval set had not already existed.

Against those two points, two incidents in the first month. The compounding chain above, which took three months to appear and a day to diagnose once somebody looked at provenance. And a stored observation about a seller — disputed damage claims on six of the last eight — which the agent quoted almost verbatim into a message_seller call, where a person at that company read it. The fix for the second was excluding seller facts from anything outbound, and it is the kind of boundary Chapter 12 makes explicit.

Ship this last, after the eval set exists, scoped to the narrowest set of observations a support agent would actually write on a card. Expect the review conversation to take longer than the code, and expect the number to be small. It is the feature most likely to hurt without anybody noticing, and the only defence against that is having built everything else in this chapter first, so you can see two points move against a baseline you trust.

Common Mistakes
  • Storing inferences as facts — "difficult customer" becomes permanent context that shapes every future interaction, and the person it describes can never see it or correct it.
  • Writing a remembered fact with no provenance — it cannot be verified, cannot be traced when it turns out to be wrong, and cannot be explained to the customer it is about.
  • Setting no expiry — a preference recorded two years ago outranks what the same buyer did last week, and nothing in the system ever notices the record aged.
  • Injecting the whole profile into every ticket — cost on the entire queue for value on the 14% that are repeat contacts, plus stale claims in context on tickets that never needed them.
  • Building it before the eval set exists — a two-point move cannot be distinguished from noise without one, and this is the feature most likely to come out negative.
Best Practices
  • Store dated observations with the evidence ticket attached, and use a closed predicate set so a characterization has no field to live in.
  • Retrieve long-term facts by subject when the ticket calls for them, and never inject a profile block into every context.
  • Set an expiry on every record and build a deletion path that a real customer request can reach, including facts derived from a deleted transcript.
  • Ship it after the eval set exists and measure it on repeat contacts specifically, where the effect is large enough to be visible at all.
Comparable toolsMem0 a memory layer as a serviceZep facts extracted and expired for youLetta memory as the framework's core ideaLangChain entity memory the same feature, retention left to you

Knowledge Check

Which of these belongs in long-term memory as written?

  • "Prefers replacements over refunds" — a stable preference the buyer has shown twice now
  • "Chose a replacement on order SU-82605, from ticket T-39177, on 10 March"
  • "Likely to accept a replacement, confidence high" — an inference with its certainty recorded
  • "Tends to argue about delivery charges" — a pattern seen across three separate tickets

A remembered fact turns out to be wrong. What makes it possible to unwind everything built on it?

  • The confidence score written alongside each fact, which flags the least reliable records first of all
  • Provenance on every record, which traces a later fact back through the ticket that produced it
  • Overwriting each fact in place, so only the newest version of a claim ever survives
  • A periodic summary of the store, which condenses old records into current conclusions

How does compounding error actually happen in a memory store?

  • The store grows until retrieval returns too many facts and the model cannot weigh them
  • Records degrade over time as each rewrite loses detail from the original observation
  • Two facts contradict each other, and the model picks whichever appears first in context
  • A wrong fact shapes behaviour, and the extractor later reads that behaviour as fresh evidence

Why is a customer profile block injected into every ticket the wrong design?

  • It spends context on the whole queue for value on the 14% of tickets that are repeat contacts
  • A profile block is large enough on its own to push a long ticket past the model's context window
  • Injected profiles break prompt caching entirely, since the block changes on every single request
  • The model treats injected facts as considerably less authoritative than facts a tool returned to it

You got correct