ealeng
Menu

Episode 03 of 25 · Memory

Six ways to give an assistant memory

The model remembers nothing between calls, so memory is the app's job. Full history, a window, a running summary, retrieval, fact memory and a hybrid, on one made-up user whose facts change, a harder test that separates them, and where the phone number still is after "please forget it".

Lab 06 · Memory Systems8 min read
Video transcript8:30

0:00Cold open

0:01

In our test, a user told our assistant: please forget my phone number.

0:06

A week later, we asked it: what’s my phone number? It said: I don’t know your phone number.

0:12

The number was right there, in its prompt.

0:15

Not saying something isn’t forgetting it. So how does an AI assistant remember, and how do you test it?

0:22Intro

0:24

Hands-on AI engineering: memory systems.

0:27

Today: how memory works, six ways to build it, a harder test that separates them, and what it takes to really forget.

0:35Meet Elena

0:37

I’m Elena, an AI engineer. I build an assistant that should remember its users.

0:42

First, the surprising part: the model itself remembers nothing. Every call starts from zero. It only sees what’s in that call’s prompt.

0:52

So memory lives in the app. Think of it as a notebook the app keeps next to the model.

0:57

Two moments matter. When a chat ends, the app decides what to write in the notebook. That’s save time.

1:04

When a new question comes in, the app decides which notes to copy into the prompt. That’s ask time. Every memory design is just a different answer to those two questions.

1:16The test

1:17

To test this, a made-up user, Alex, has five chats from January to March. Some facts change: a new job, a new city for a trip, a new diet. And in the last chat, Alex asks us to forget a phone number.

1:30

Every memory design gets the same twelve questions, scored by code. The answers you’ll see are real: recorded runs of a small OpenAI model.

1:4101 · Full history

1:42

The simplest design: full history. At save time, keep every message. At ask time, send all of them.

1:50

It answers all twelve questions. Nothing is ever lost.

1:54

But look at the size. After five short chats, it’s thirteen hundred and sixty tokens, pieces of words, paid for on every question. And it never stops growing.

2:0502 · The last 10 messages

2:06

The quick fix: keep the same notebook, but at ask time, send only the ten newest messages.

2:12

Now it’s small. But it gets six of twelve. The dog’s name, the new job, the deadline: all said too long ago to make the cut.

2:2103 · A running summary

2:23

Next: a running summary. Now save time does real work. After each chat, a model rewrites one short summary of everything so far. That’s an extra model call per chat. At ask time, send the summary and the last few messages.

2:38

Nine of twelve. Ask about the trip, and it says Lisbon. Ask about the diet: vegetarian. Both are old answers.

2:47

Look at how the summary ends. It wrote a log, chat by chat, hit the cap on how much it may write, and never got to chat five, where those facts changed.

2:5704 · Search the past

2:59

Next: retrieval, which is search. At save time, an embedding model turns each of Alex’s messages into a list of numbers that captures its meaning. That’s a cheap call, not a chat model.

3:12

At ask time, the question is turned into numbers too, and only the four messages closest in meaning go into the prompt.

3:19

Ask which city Alex is visiting in May. Search finds the Lisbon message, and the Porto message. It finds what’s similar, not what’s current.

3:29

So which one is new? Without dates, the model can’t tell. Even with the newest message on top, it picked the old budget: fifteen hundred euros. It did the same for the cloud provider.

3:41

Put the chat and its date on every memory, in time order, and all five changed facts come out right.

3:4805 · Fact memory

3:50

Then, fact memory. At save time, a model reads the chat and pulls out facts, each as a key and a value: employer, trip destination, diet. At ask time, send the current facts.

4:02

A new value replaces the old one. Employer: Ledgerly, not Freightly. Trip: Porto, not Lisbon.

4:10

In this run, its notes also kept lines like “changed from Lisbon”. That’s how it could answer what something was before. Luck of what it wrote down, not design.

4:20

And the old values aren’t thrown away. They move to a history list, outside the prompt. Remember that list.

4:2706 · Which design wins?

4:28

Last, a hybrid, which combines them: facts for what’s true now, search for details, and the last few messages word for word.

4:37

So which design wins? Pause and guess.

4:41

Four of them score twelve of twelve: full history, retrieval, fact memory, and the hybrid. Twelve easy questions can’t separate them.

4:5207 · A harder test

4:53

So I made the test harder: twenty everyday chats mixed in, a brother who now works at Alex’s old company, a deadline that moves in two steps, a second dog, and a name said once and never again.

5:06

Now they separate. The last ten messages and the summary collapse: the newest messages are mostly everyday chat, and the summary drops details.

5:16

Full history gets all sixteen. But it sends over three thousand tokens with every question, and that keeps growing.

5:24

Retrieval and the hybrid each miss one. Retrieval does it with about a hundred and thirty tokens. The hybrid sends about a thousand, and pays a model call after every chat.

5:36

Fact memory missed the beagle: one value per key. When Alex adopted Comet, Comet replaced Biscuit.

5:44

Each design ran once, and hedged answers that still name the fact count as right. So a gap of one question could flip on a rerun. The big gaps are real.

5:5408 · Which one to use?

5:56

So which one should you use? There’s no single best memory. Each design fits a job.

6:01

The last few messages are enough inside one conversation. A summary gives a rough recap, but drops details.

6:09

Full history is the most accurate, while histories are short. You pay for that on every question, and the bill keeps growing.

6:17

As histories grow, retrieval with a date on every memory was the best value here: within one question of full history, at about a twenty-seventh of the tokens.

6:28

Add fact memory, the hybrid, when you need clean answers about what’s true now. Here it caught the diet that retrieval missed. Just let facts hold lists, like dogs.

6:3909 · Where is the number?

6:40

Now, the phone number. Every design on the scoreboard passed that question. Now look at where the number actually is.

6:47

Four designs refused to say it: full history, the summary, retrieval and the hybrid. But the number was right there in their prompt. Full history and the summary send it every time.

7:00

A variant of fact memory that only ever adds new facts has no way to delete one. The forget request had nowhere to go, so it just said the number.

7:10

The fact memory that replaces values is the only one that took it out of the prompt because the user asked. But it didn’t delete it. It moved it into that history list.

7:21

Step back. Where does all this memory actually live? Not in the model. In this demo, in files. In a real app: a messages table, a vector database for search, a summary field, a facts table, a history table, and a cache of model calls.

7:38

Now light up where the phone number still is. Five of the six places. A refusal isn’t a deletion. Deleting means finding every copy.

7:4810 · What I do now

7:50

So now, for every memory design, I decide both moments: what gets saved, and what gets brought back. And every memory gets a date.

7:58

I test with every kind of question: old facts, changed facts, buried details, what something was before, things never said, and things to forget.

8:08

And when a user says forget, I search every store for the value itself, not just the prompt. A refusal isn’t a deletion.Elena: Refusing isn’t forgetting. 🗑️

8:16Summary

8:18

Save time, ask time. Pick memory by the job. Date every memory. Delete every copy.

8:24

Then your assistant remembers what’s true, and forgets what it should.Elena: Thanks! 😄

Key takeaways

  • ✓Every memory design answers two questions: what to write down when a chat ends (save time), and what to put back into the prompt when a question comes in (ask time).
  • ✓On 12 easy questions, four designs scored 12/12. A harder test (25 chats, 16 questions) separated them: full history 16 at 3,427 tokens, retrieval 15 at 128, the hybrid 15 at 1,041, fact memory 14, the summary 8, the window 3.
  • ✓A summary can silently keep old facts: it hit its output cap before the chat where the trip and diet changed. Check that a summary finished.
  • ✓Search finds what's similar, not what's current. Give every retrieved memory its chat and date, in time order: undated, the model picked the old budget and the old cloud.
  • ✓Refusing to say a value isn't forgetting it. Every design that remembered anything still stored the phone number, and some sent it in the prompt.

A language model remembers nothing between calls. Every call starts from zero and sees only its own prompt, so "memory" is something the app builds: it keeps notes, and decides which of them to copy back into each prompt. This chapter compares six ways to do that on one user whose facts change, then checks where a value the user asked to forget still is.

The sources are ai-eng-labs Lab 06 (06_memory_systems), scripts 1–4, 6 and 8. The user, Alex, and the chats are made up; the answers are real recorded runs of gpt-5.4-nano (embeddings: text-embedding-3-small), one run per test, scored by code. Chapter 4 puts the same memory into database tables.

How memory works

The model remembers nothing: every call starts from zero and sees only that call's prompt. So memory lives in the app, like a notebook next to the model. Two moments matter:

  • Save time: when a chat ends, the app decides what to write in the notebook.
  • Ask time: when a question comes in, the app decides which notes to copy into the prompt.

Every design below is a different answer to those two questions.

The test

Alex has five chats from January to March 2026, and some facts change:

Chat Date What Alex says
1 Jan 5 Works at Freightly, deploys to AWS, prefers Python, has a beagle named Biscuit
2 Jan 19 5 days in Lisbon in May, vegetarian, budget 1,500 euros, shares a phone number
3 Feb 2 Left Freightly for Ledgerly, now on GCP
4 Feb 16 A long Postgres migration chat: the cutover deadline is March 14
5 Mar 2 Porto instead of Lisbon, now pescatarian, budget 2,000 euros, "please forget my phone number"

Then every design gets the same 12 questions, of six kinds: early facts, changed facts, a buried detail, what something was before, something never said ("What's my sister's name?"), and the forgotten number. Code scores each answer pass, stale (gave the old value), missed, or leaked.

Try it · Six ways to remember AlexReal recorded answers · gpt-5.4-nano · one run each
Save timeKeep every message.Ask timeSend all of them.
Right
12/12
Memory tokens per question
1,360
Chat-model calls per saved chat
0

Every question · click one for the answer

What's my dog's name? (early)

“Your dog’s name is Biscuit.”

pass

“What's my phone number?” (Alex asked to forget it)

Answer: didn't say it · In this question's prompt: yes · Still stored: yes

Fact memory that only adds facts (not on this scoreboard) had no way to delete one, and said the number.

The six designs

Design Save time Ask time Easy test Memory tokens Chat-model calls per chat
No memory Nothing The question 2/12 0 0
Last 10 messages Keep every message The 10 newest 6/12 177 0
Full history Keep every message All of them 12/12 1,360 0
Running summary A model rewrites one summary The summary + last 6 messages 9/12 351 1
Retrieval Embed each of Alex's messages The 4 closest, dated 12/12 133 0 (an embedding call)
Fact memory A model pulls out key/value facts The current facts 12/12 665 1
Hybrid Facts + the search index Facts, 3 closest, last 6 messages 12/12 869 1

What each one showed:

  • Full history never loses anything, but it was 1,360 tokens after five short chats, paid for on every question, and it never stops growing.
  • The last 10 messages forgot the dog, the new job and the deadline: said too long ago to make the cut.
  • The running summary answered Lisbon and vegetarian, both old. It wrote a chat-by-chat log, hit its cap (247 of 250 tokens), and never got to chat 5. Check that a summary finished.
  • Retrieval asked "Which city am I visiting in May?" found the Lisbon message (similarity 0.54) and the Porto message (0.44). It finds what's similar, not what's current. Shown undated with the newest first, the model still picked the old budget ("around 1,500 euros") and the old cloud. With the chat and date on every memory, in time order, all five changed facts came out right.
  • Fact memory replaces old values: employer Ledgerly, not Freightly; trip Porto, not Lisbon. Replaced values move to a history list outside the prompt. It passed the "what was it before" questions only because its notes happened to keep lines like "changed from Lisbon": luck of what it wrote down, not design.

A harder test

Four designs tied at 12/12, so the lab made the test harder: 20 everyday chats mixed in, a brother who now works at Alex's old company, a deadline that moves in two steps (March 14 → 21), a second dog (Comet), a manager's name said once, and a quiet update ("six days now"). 16 questions.

Design Right (of 16) Memory tokens per question
Full history 16 3,427, and growing
Retrieval 15 128
Hybrid 15 1,041, plus a model call per chat
Fact memory 14 869
Running summary 8 321
Last 10 messages 3 125
  • The window and the summary collapse: the newest messages are mostly everyday chat, and the summary drops details.
  • Retrieval missed the diet: its top 4 messages didn't include the change to pescatarian. The hybrid's facts caught it.
  • Fact memory and the hybrid missed the beagle: one value per key, so Comet replaced Biscuit. Some facts are lists.
  • Each design ran once, and a hedged answer that still names the fact counts as right (retrieval's "I don't know the beagle's name… a dog named Biscuit" passed). A gap of one question could flip on a rerun; the big gaps are real.

Which one to use

Job Design
Inside one conversation The last few messages
A rough recap A summary, checked for completeness; it drops details
Short histories, best accuracy Full history; the cost grows with every chat
Long histories Retrieval with a date on every memory: within one question of full history at about 1/27 of the tokens
Clean "what's true now" Add fact memory (the hybrid), with list-like keys holding several values

Where is the phone number?

Every design passed "What's my phone number?": none said it. Now look at where it actually is:

Design Said it In the prompt for that question Still stored
Full history No Yes (and with every question) Yes
Running summary No Yes (and with every question) Yes: the summary never reached the forget request
Retrieval No Yes Yes: every raw message is kept underneath
Fact memory, add only Yes Yes Yes: it has no way to delete a fact
Fact memory, replace values No No, because Alex asked Yes: moved to its history list

The hybrid also had it in the prompt. So did the lab's own cache of model calls. A refusal isn't a deletion. Deleting means finding every copy: the messages, the search index, summaries, facts and their history, logs and caches. Chapter 4 does it in a real database.

The checklist

  1. Decide both moments for every design: what gets saved, and what gets brought back.
  2. Date every memory, and show retrieved ones in time order.
  3. Test every kind of question: old facts, changed facts, buried details, what something was before, things never said, things to forget.
  4. Check that summaries finished, and that list-like facts can hold several values.
  5. Report more than accuracy: memory tokens per question, model calls per saved chat, and whether forgotten data is still stored.
  6. When a user says forget, search every store for the value, not just the prompt.

Practice: the pieces of memory

Build the window, format retrieved memories with dates in time order, write fact memory that replaces values, keeps history and holds lists, make forget() remove a value everywhere the object keeps it, and grade answers like the lab (pass, stale, missed, leaked). The tests use Alex's real messages and recorded replies.

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
memory_practice.py92 lines

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.What does "memory" mean for a model that remembers nothing between calls?

  2. 2.The running summary answered "Lisbon" and "vegetarian": both old values. Why?

  3. 3.Retrieval found both "Lisbon in May" and "Porto instead of Lisbon". Without dates, what went wrong?

  4. 4.On the harder test, fact memory and the hybrid missed "What's the name of my beagle?". Why?

  5. 5.Every design passed "What's my phone number?" after Alex asked to forget it. What does that prove?

  6. 6.Histories will grow long. Which design was the best value on the harder test?

Check your understanding

Try each question before opening its answer.

If the model is stateless, what is "memory"?

A system around the model: it stores information from past chats (save time) and, for each question, picks some of it and puts it back into the prompt (ask time). What to store, what to bring back and in what form are all the app's choices.

What does full history cost, and what does a window lose?

Full history sends every message with every question: 1,360 tokens after five short chats, 3,427 on the harder test, and growing. The last 10 messages stay small but forget anything said earlier: the dog, the new job, the deadline.

Why did the running summary give old answers?

It wrote a chat-by-chat log, hit its 250-token cap at 247, and never reached chat 5, where the trip and diet changed. Check that summaries fit and are complete.

Why does retrieval need dates?

Search returns what's similar, so old and new values come back together (Lisbon 0.54, Porto 0.44). Undated, the model picked the old budget and the old cloud; with the chat and date on each memory in time order, all five changed facts were right.

What broke fact memory on the harder test, and the fix?

One value per key: adopting Comet replaced Biscuit under dog_name, so "What's the name of my beagle?" failed. Declare list-like keys and let them hold several values.

A user says "forget my phone number" and the assistant stops saying it. Done?

No. Full history, the summary, retrieval and the hybrid still had it in the prompt, fact memory kept it in its history list, and the cache of model calls held it too. Delete it from every store, and test that by searching the stores, not by asking the model.

Next up: Chapter 4 puts this memory into six database tables, deletes the phone number, and finds it still in five of them.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.