Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Agent memory that forgets on purpose

After this lesson: I can give an agent a memory that forgets on purpose.

60 min lesson7 commandsAI coding agent lab: use opencode to explain and fix code, and check its work

A memory that forgets

An agent memory is a small store of notes the agent can look up later. A good one forgets on purpose: old notes expire, and some things are never stored.

That matters because a memory that keeps everything keeps passwords too, and keeps facts that stopped being true. The Building agents module made a memory. This lesson adds forgetting.

By the end of this lesson

Note

I can give an agent a memory that forgets on purpose.

Key words

WordWhat it means
Memory storea list of notes the agent can look up
Keyworda word used to find a note
Time to livehow many days a note is kept
Expireto be forgotten because a note is too old
Never-store rulea test that refuses a note on the way in

The cells students fill in are left blank in this preview.

Notes expire with age

Each note carries the day it was written. Its age is today minus that day. When the age is more than the time to live, the note has expired and is not used.

To look something up, count how many of the query words are in each note. Rank by that count, then by the newest day.

Worked example: day 40, time to live 30

NoteDayAgeUsed?
deploy to staging on friday535No: expired
staging server is called box72020Yes, 1 hit
deploy needs two approvals337Yes, 1 hit
deploy staging fix is merged391Yes, 2 hits, first

The cells students fill in are left blank in this preview.

A common mistake

Warning

Many people think a memory should keep everything, because more is more helpful. It is not. Old notes can be wrong, and a note with a password in it is a leak waiting to happen. Decide what to forget before you decide what to keep.

Some things are never stored

Check each note on the way in, before it is stored. Refuse words such as password, an email address, or a long run of digits like a card number.

These rules are simple and they miss things. They are a safety net, so they are paired with a rule for the agent: never write a secret down.

Write the model helper, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// The token counts of a reply, or null when the reply carries none.' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the model helper, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"Recorded sample: no model answered (\" + error.message + \").\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the memory store, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const blocked = [\"password\", \"secret\", \"token\"];' 'function check(text) {' '  const low = text.toLowerCase();' '  for (const b of blocked) { if (low.indexOf(b) >= 0) return \"refused: holds the word \" + b; }' '  if (text.indexOf(\"@\") >= 0) return \"refused: holds an email address\";' '  if (/[0-9]{12}/.test(text)) return \"refused: holds a long number\";' '  return \"ok\";' '}' 'const store = [];' 'function add(text, day) {' '  const why = check(text);' '  if (why === \"ok\") store.push({ text: text, day: day });' '  return why;' '}' > memory.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the memory store, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function find(query, today, ttl) {' '  const words = query.toLowerCase().split(\" \");' '  const live = store.filter(function (n) { return today - n.day <= ttl; });' '  const scored = live.map(function (n) {' '    const have = n.text.toLowerCase().split(\" \");' '    return { text: n.text, day: n.day, hits: words.filter(function (w) { return have.indexOf(w) >= 0; }).length };' '  });' '  return scored.filter(function (n) { return n.hits > 0; }).sort(function (a, b) { return b.hits - a.hits || b.day - a.day; });' '}' >> memory.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the memory store, part 3

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const notes = [' '  [\"deploy to staging on friday\", 5],' '  [\"staging server is called box7\", 20],' '  [\"deploy needs two approvals\", 33],' '  [\"my password is hunter2\", 38],' '  [\"email me at [email protected]\", 38],' '  [\"deploy staging fix is merged\", 39],' '];' >> memory.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the memory store, part 4

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  notes.forEach(function (n) { console.log(\"add day \" + n[1] + \": \" + add(n[0], n[1])); });' '  console.log(\"stored \" + store.length + \", still live \" + store.filter(function (n) { return 40 - n.day <= 30; }).length);' '  find(\"deploy staging\", 40, 30).forEach(function (n) { console.log(n.hits + \" hits, \" + (40 - n.day) + \" days old: \" + n.text); });' '  const q = \"In one short sentence, why should an agent forget old notes?\";' '  const sample = \"Old notes can be wrong now, and a memory that keeps everything also keeps what it should not.\";' '  const reply = await ask([{ role: \"user\", content: q }], sample, 60);' '  console.log(\"model said: \" + reply.text);' '}' 'main();' >> memory.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run the memory store

Command
docker exec -w /home/node/project agent node memory.js

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

What the Run prints

LineWhat it shows
add day 5: okThe note is stored
add day 38: refused: holds the word passwordThe rule stopped a secret
add day 38: refused: holds an email addressThe rule stopped an address
stored 4, still live 3One note has expired
2 hits, 1 days old: deploy staging fix is mergedThe best match comes first
model said: ...A sentence. The words differ each run

The cells students fill in are left blank in this preview.

About this lab and its source: AI coding agent lab: use opencode to explain and fix code, and check its work

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.