Build an AI Agent, Stage 7: Decide What It Remembers, and Test What It Forgets
An agent's memory is a set of claims about the world, written at one moment and read at another. Give each one a source version, a shelf life and a confirmed writer, then test that the agent forgets as reliably as it recalls.
The example in this series is a composite scenario, built from patterns common to requirements intake in regulated organisations. It is not a single real organisation, and its figures are illustrative. The code is real: every output block below was produced by running it.
Two months after the Requirements Agent went into pilot, the analysts asked for one improvement more than any other: stop asking us the same thing twice. Brokers raised the same themes again and again, and each time the agent proposed a relation an analyst had already confirmed for an earlier request. So the team gave it a memory. When an analyst confirmed a relation, the agent stored it by theme, and the next request on that theme started with the remembered answer.
It worked for six weeks. Then a broker's request, CR-2207, asked to keep the 30-day window for notifying a claim on one product line. The agent's memory held a confirmed decision from early July: requests on that theme were duplicates of requirement REQ-212. It proposed a duplicate, its note said "previously confirmed by an analyst", and the analyst on duty confirmed it again. The request was closed.
But REQ-212 had been revised in August. Version 5 shortened the window to 14 days. A request to keep 30 days was no longer a duplicate of it; it was a conflict, the kind that goes to the product owner for a decision. The broker escalated two weeks later. The memory had been true when it was written. Nothing had told it, or the agent, that it had stopped being true.
This is Stage 7 of Build an AI Agent From Scratch. In Stage 3, the blueprint said the agent would have no memory across requests in its first version and deferred the decision to this stage, "with its own recall and forgetting tests". Stage 6 packaged the agent's two skills. This stage decides what they may remember. For the mechanics of short-term and long-term agent state, see Agent Memory and State; this stage is about deciding what belongs in it.
Why remembering feels like pure gain
Memory is easy to justify. It saves the analysts repeated work, it makes the agent look as if it is learning, and the first version is a few lines of code: a key, a value, a lookup. Every recall that matches looks like a success, because the entry was correct when it was stored.
What the first version leaves out is the second half of every memory's life. A requirement changes, a decision is reversed, a requester asks for their data to be removed, an entry was never right in the first place. A cache keyed by theme has no way to know about any of these, so it keeps answering. Stale memories don't announce themselves; they look exactly like good ones.
The team's review of the memory store found three more problems besides the stale entry. It kept no expiry, so entries from the pilot's first week were still being recalled. It accepted writes from the agent itself, so a relation the agent had proposed but nobody had confirmed could be stored as if it had been. And it stored a short summary of each request, including, in one case, the name of a policyholder from a sample claim a broker had attached. That summary turned up in the context of a different broker's request.
The third problem has a name. OWASP's guide to agentic threats lists memory poisoning first, "injecting false information across sessions, corrupting knowledge bases, exposing sensitive data" (OWASP, 2025). An agent that can write its own unconfirmed conclusions into memory needs no attacker to poison it: its own mistakes become tomorrow's facts. AI Agent Security places memory in the wider threat model.
The test: list everything your agent stores between runs. For each entry type, write down who or what can write it. If the agent can write anything that it will later read back as true, that is a poisoning path, whether or not anyone malicious ever uses it.
A memory is a claim with a source and a shelf life
The fix starts with a question to ask about each thing you want the agent to remember: what would make this untrue, and how would the agent find out? For a confirmed relation, the answer is a new version of the requirement, which the repository records. For a preference about which questions to skip, it is a change to the question skill, which has a version since Stage 6. The answer gives each memory its pin. Where there is no record of change at all, the answer is time, and that gives it an expiry.
The team wrote the answers into a memory policy, owned by the BA practice lead like the blueprint itself:
May remember | Pinned to | Expires after | Written by | Used as |
relation_decision: an analyst's confirmation or rejection of a proposed relation | The requirement's version | 180 days | An analyst confirmation only | A hint: re-read the requirement at its current version and quote it before proposing |
question_feedback: a question type analysts deleted as unnecessary, per request type | The question skill's version | 90 days | An analyst's edit of a draft only | Omit that question type unless the request itself raises it |
And a list of what is never stored, whatever the convenience: request text and attachments, requirement text, backlog items, any requester or policyholder name or identifier, and anything the agent wrote that no analyst confirmed. The first three are the blueprint's untrusted inputs. Storing them would turn text that was untrusted once, inside a single request, into text the agent trusts in every request after.
The recall rules are three lines. An entry is used only while its pin matches the current version of its source. An entry is never used past its expiry. The current request and the current records always win over memory, and the policy says how: what is recalled is a hint that sends the agent back to the requirement, never an answer it can propose on its own. OWASP's mitigations point the same way, including "Require source attribution for memory updates" and "Limit AI memory retention durations based on sensitivity" (OWASP, 2025).

The test: for each memory type your agent keeps, answer the question above in one line. If the answer is "nothing would make it untrue", look again; if the answer is a change nobody records, the memory needs an expiry short enough that you'd accept it being wrong for that long.
Six tests, before and after
The checker for this stage, memory_check.py, first checks the policy against the blueprint: every class has a pin, an expiry, a writer and a field list; every input the blueprint marks untrusted is on the never-store list; and there is a way to erase by request. Then it runs six tests against a memory store: one recall test that proves memory is still useful, and five forgetting tests, one for each way a memory goes wrong.
Against the first store:
$ python memory_check.py requirements-agent.blueprint.json v1
memory policy v1.0: PASS (0 fail)
memory store v1
PASS recall
FAIL stale source recalled 'duplicate of REQ-212' pinned to v4; REQ-212 is now v5
FAIL expiry recalled an entry 206 days old; TTL is 180
FAIL unconfirmed write stored the agent's own unconfirmed proposal as a decision
FAIL never-store field stored request text, including a policyholder's name
FAIL erasure no way to erase entries derived from CR-2291
checked: 6 recall and forgetting tests
verdict: FAIL (5 fail)
The first store passes the only test a team usually writes for memory: something stored can be recalled. It fails every test of the second half of a memory's life. The stale-source line is CR-2207's incident, reproduced: the July decision pinned to version 4, recalled in September when the repository holds version 5. The never-store line is the policyholder's name.
The policy-governed store enforces the policy at the two points where it can be enforced: on write, by refusing any class, field or writer the policy doesn't allow, and on recall, by checking each entry's pin against the current record and its age against its expiry, and dropping it if either fails.
$ python memory_check.py requirements-agent.blueprint.json v2
memory policy v1.0: PASS (0 fail)
memory store v2
PASS recall
PASS stale source
PASS expiry
PASS unconfirmed write
PASS never-store field
PASS erasure
checked: 6 recall and forgetting tests
verdict: PASS (0 fail)
The policy checker is tested the way earlier stages' checkers were, by breaking a passing policy in one place: a class with no expiry, an untrusted input removed from the never-store list, the erasure rule deleted. A further test states the hub's pass condition for this stage directly: a stale memory never overrides the current record, and is removed when it is found. With seven new tests, the series suite has 51, and all pass.
The test: write one test where the source of a stored memory changes after it is written, then recall it. If it comes back, your agent will act on yesterday's version of a record that has changed.
Forgetting is a feature you have to build
The erasure test fails on the first store for the simplest reason: there was no way to do it. Nobody had built one, because nobody had thought of the store as holding anything that might need to go.
Where the GDPR applies, as it does to most insurers with European customers, that is a legal question as well as a quality one. The regulation requires personal data to be "adequate, relevant and limited to what is necessary" and "kept in a form which permits identification of data subjects for no longer than is necessary" (GDPR Article 5). A memory store that copies request text keeps personal data indefinitely, in a place the data protection team doesn't know exists. The cheapest compliance is not storing it at all, which is what the never-store list does. Erasure by request covers what remains: every entry records the request it came from, so all entries derived from one request can be removed together.
Forgetting also has to happen without anyone asking. An entry whose source has changed is removed the first time recall finds it stale, so the store doesn't fill with entries that will never be valid again. Expiry does the same for entries whose sources can change without a version.
The test: pick one request your agent handled last month and ask how you would remove everything the agent kept from it. If the answer involves searching free text, the store holds things it shouldn't, or doesn't record where they came from.
Designing an agent's memory: a method
List what would save real work if remembered. Ask the people who review the agent's output what they repeat. Done when each candidate has a named benefit, not "it might help".
Ask of each: what would make this untrue, and how would the agent find out? Done when each candidate has a pin to a versioned source or an expiry, or both.
Name the writer. Done when each memory type can be written only by a confirmation from a person, or a system of record, never by the agent's own unconfirmed output.
Write the never-store list. Start from the blueprint's untrusted inputs and add personal data. Done when the store rejects any field not in a memory type's field list.
Write the recall test and the five forgetting tests. Stale source, expiry, unconfirmed write, forbidden field and erasure. Done when all six run in CI against the real store.
Tradeoffs and exceptions
Approach | Gains | Costs | Best suited for | Warning signs |
No memory across requests | Nothing to go stale or leak | Repeated work for reviewers | First versions; agents whose sources change often | Reviewers re-confirming the same decision weekly |
A cache keyed by topic | Fast to build; saves repeated work | Stale entries, no erasure, a poisoning path | Nothing in a regulated setting | Recalls you can't trace to a source version |
Policy-governed memory: pinned, expiring, confirmed writes | Saves work and stays true | A policy, an owner, six tests | Agents with stable, versioned sources | Entries with no pin or no writer |
Re-read the system of record every time | Always current | Slower; more load on the source | Facts that change often or carry legal weight | Memory used for anything the source already answers |
The last row is the alternative to remember most often. If a fact lives in a system of record, the agent can usually read it again more safely than it can remember it. Memory earns its place for what no system records, such as an analyst's judgement that two requests are the same thing, and even then the agent re-reads the record before acting on it.
Not every source has versions. If yours doesn't, use a short expiry and a content hash of what the decision was about: if the hash differs at recall, treat the entry as stale.
How Stage 7 goes wrong
Memory as answer, not hint. The store is correct, but the agent proposes a recalled relation without re-reading the requirement. Make the skill's instructions say that a recall sends the agent back to the record, and test it.
Summaries that seem harmless. A "short summary" field quietly carries names, amounts and claim numbers. A field allowlist, not a filter for personal data, is what keeps them out.
Shared memory across teams. A second team's agent reads the triage team's store "for consistency", and decisions made under one team's rules surface under another's. Scope each store to the agent and team it was designed for.
No owner. The policy says 180 days; nobody revisits it when the repository's change rate doubles. Give the policy the same owner as the blueprint, and review it when the agent's sources change.
What to do next
Take one thing your agent remembers between runs and write a test this week where its source changes after it is stored. Then try to erase everything the agent kept from one request. Stage 8 turns to retrieval: searching the requirements repository well enough that when nothing matches, the agent says so, and never invents a requirement to fill the gap.
Final takeaway
A memory is a claim written at one moment and read at another. Pin each one to the version of its source, give it an expiry, let only a person's confirmation write it, keep request text and personal data out of it, and test forgetting as carefully as recall. Then what the agent remembers can't outvote what is true now.
Sources
OWASP GenAI Security Project, Agentic Security Initiative, Agentic AI: Threats and Mitigations, v1.0, 17 February 2025. Memory poisoning (T1); mitigations including source attribution for memory updates and retention limits based on sensitivity.
General Data Protection Regulation, Article 5. Data minimisation, Article 5(1)(c); storage limitation, Article 5(1)(e).
Series navigation. Previous: Stage 6: Package Its Skills So Each One Is Tested Alone. Next: Stage 8: Retrieval That Can Say "None". The full stage list is on the Build an AI Agent From Scratch hub.
Frequently Asked Questions
What should an AI agent remember between sessions?
Only facts that save real work and that you can keep true: typically decisions a person confirmed, each pinned to the version of the record it was about and given an expiry. Raw request text, personal data and anything the agent concluded on its own are better left out.
How do you stop an AI agent using stale memory?
Store each memory with the version of its source, check that version against the current record at recall, and drop the memory if they differ or if it has passed its expiry. Treat what is recalled as a hint to re-check, never as an answer.
How do you test an AI agent's memory?
Write recall tests that prove a valid memory is used, and forgetting tests that prove a changed source, an expired entry, an unconfirmed write, a forbidden field and an erasure request each leave nothing behind for the agent to recall.
Part of the Agent Engineering curriculum: eight paths from prompts and context to testing, evaluation and governance.


