🚀Publish FREEJoin WriterDock's early community of writers.Start Writing — It's Free

Maintaining Agent Memory: Updating, Summarizing, Retrieving and Forgetting

Suraj Kumar

Suraj Kumar

November 3, 2026

Memory that is never maintained goes bad. It grows until it crowds the context window, it fills with duplicates and contradictions, and it keeps facts that stopped being true months ago. An agent that trusts a stale memory is worse than one with none, because it is confident and wrong. In the last lesson you learned what memory is and where it lives. This lesson is about keeping it healthy: what to write, how to update it, how to compress it, how to find the right piece when you need it, when to let go, and how to protect the people whose data it holds. You will build five small tested helpers along the way.

The memory update cycle

Think of memory as a cycle that never stops, not a place you write into once.

The memory update cycle: capture something worth keeping, judge whether it is durable, safe and trusted, write or merge it into existing memory, retrieve only what a task needs, then review, summarize and forget, and return to capture

  1. Capture. Something worth keeping happens: a task ends, the user corrects the agent, a decision is made.
  2. Judge. Is it durable, safe to store and from a source you trust?
  3. Write or merge. Add it, or better, update the existing entry.
  4. Retrieve. On later tasks, load only what that task needs.
  5. Review and forget. Summarize old logs, expire what is never used, and fix what is wrong.

Each step needs a decision, and the rest of this lesson takes them in turn.

Deciding what to save

The most common mistake is saving too much. A transcript of everything is not memory, it is a second context window that you will have to search. The test for each candidate is simple: will a future run need this?

Good candidates are durable facts ("the user's timezone is IST"), preferences ("prefers short answers"), decisions and the reason behind them, lessons learned from a mistake, and the state of unfinished work. Poor candidates are one-time details, raw tool output, and anything the agent can cheaply look up again.

When to write also matters. Anthropic's guidance for long-running agents points to natural moments: at the end of a session, update the progress log with what was finished and what remains, and when the user corrects the agent, record the correction. Waiting until the context is nearly full is too late, as the memory tool's built-in instruction says: assume your context might be reset at any moment, so anything not recorded is lost.

Anthropic's article on harnesses for long-running agents shows a concrete setup. A first session creates the support files: a progress log (claude-progress.txt), a structured feature list, an init.sh script and an initial git commit. Every later session starts by reading the log, the feature list and the git history, works on one item, and ends by committing and updating the log. Two details are worth copying. The feature list was JSON, because the authors found the model was less likely to inappropriately change or overwrite JSON than Markdown, and agents were allowed to change only a single passes field. And the agents were told, in the article's words, that it is unacceptable to remove or edit tests. Structure protects memory from careless edits.

Structure your memory

Do not keep everything in one file. Split it by kind, so each part can follow its own upkeep rules:

  • A profile of stable facts about the user: small, structured, always loaded.
  • A log of events and decisions: grows over time, summarized and aged.
  • Rules the agent follows: few, reviewed by a person, rarely changed.

Anthropic's prompting guidance matches this: use structured formats such as JSON for state with a defined shape, plain text for free-form progress notes, and git for checkpoints you can restore. Give every entry a little metadata: when it was created, when it was last used, and where it came from. That metadata is what makes the later steps possible.

Update, do not just append

Imagine the agent learns the user's preferred contact method in January, and the user changes it in March. If you only append, memory now holds two conflicting facts, and the agent may pick the wrong one. For facts with a single answer, update in place.

Two rules make this safe:

  • Newer wins, but not always. Something the user said directly should not be overwritten by something the agent merely guessed.
  • Record the source. Keep whether a fact came from the user, from a tool, or from the agent's own inference, so you can decide how much to trust it.

Here is the full set of helpers for this lesson. The first function, upsert_fact, does the update. The others appear in later sections, and all of them were tested:

python
1import re
2from datetime import datetime
3
4SENSITIVE = re.compile(
5    r"\b(?:\d[ -]?){13,19}\b|password|passcode|api[ _-]?key|secret|\bsk-[A-Za-z0-9]{10,}",
6    re.IGNORECASE,
7)
8
9
10STOP = {"a", "an", "and", "do", "does", "for", "i", "in", "is", "my", "of", "on", "the", "to", "what", "when"}
11
12
13def words(text):
14    return set(re.findall(r"\w+", text.lower())) - STOP
15
16
17def looks_sensitive(text):
18    return bool(SENSITIVE.search(text))
19
20
21def upsert_fact(profile, key, value, source, now):
22    old = profile.get(key)
23    if old and old["value"] == value:
24        old["last_used"] = now
25        return "unchanged"
26    if old and old["source"] == "user" and source != "user":
27        return "kept"
28    profile[key] = {"value": value, "source": source, "updated": now, "last_used": now}
29    return "updated" if old else "added"
30
31
32def retrieve(entries, query, now, k=3, half_life_days=30):
33    wanted = words(query)
34    scored = []
35    for e in entries:
36        overlap = len(wanted & words(e["text"])) / max(len(wanted), 1)
37        if overlap == 0:
38            continue
39        age_days = (now - e["created"]).days
40        scored.append((overlap + 0.5 * 0.5 ** (age_days / half_life_days), e))
41    scored.sort(key=lambda pair: pair[0], reverse=True)
42    for _, e in scored[:k]:
43        e["last_used"] = now
44    return [e for _, e in scored[:k]]
45
46
47def compact_log(entries, keep_recent, summarize):
48    if len(entries) <= keep_recent:
49        return entries
50    old, recent = entries[: len(entries) - keep_recent], entries[len(entries) - keep_recent :]
51    summary = {"text": summarize([e["text"] for e in old]), "created": old[-1]["created"], "kind": "summary"}
52    return [summary, *recent]
53
54
55def forget(entries, now, max_age_days, pinned_kinds=("rule",)):
56    kept, removed = [], []
57    for e in entries:
58        idle = (now - e.get("last_used", e["created"])).days
59        (kept if e.get("kind") in pinned_kinds or idle <= max_age_days else removed).append(e)
60    return kept, removed

upsert_fact returns what it did: added, updated, unchanged or kept. A guess (source="inferred") never replaces something the user said, so a wrong inference cannot silently overwrite a fact the user told you. When the user states the new value directly, it replaces the old one.

The user profile

A profile is the simplest and most valuable memory: a small record of durable facts, such as name, timezone, preferred tone and ongoing projects. It stays small enough to load on every run, so the agent always starts knowing who it is talking to. Store it as JSON or a database row, with the metadata above, and keep it under the user's control: they should be able to see what is stored, correct it and delete it. Anything in there is something you will have to protect.

Compress old entries

A log that only grows will eventually be too big to load. The fix is summarization, the same idea as compaction in Context engineering applied to long-term memory: keep recent entries word for word, and replace older ones with a summary.

compact_log does this. It takes the entries, the number of recent ones to keep, and a summarize function you supply, usually a call to a model:

python
1summary_prompt = (
2    "Summarize these log entries. Keep decisions made and why, open problems, "
3    "the current goal and important facts. Drop repeated detail."
4)

The summarizer is passed in, not built in, so you can test the logic without a model, which the tests do. Notice two design choices. The summary replaces the old entries, so the log stops growing. And when the log grows again, compacting it again includes the earlier summary in the text to be summarized, so nothing is lost twice. As Anthropic's context engineering article advises for compaction, tune for recall first, so you do not drop something important, and tighten for precision after.

Summaries lose detail, so be careful with entries that must stay exact: ids, amounts, dates and rules should live in structured fields, not only inside prose that gets squeezed.

Retrieval: load only what this task needs

As memory grows, loading all of it brings back the context problem. Retrieval means choosing a handful of relevant entries for the task in hand. A common approach combines two signals: relevance (how well the entry matches the question) and recency (newer entries are often more useful).

retrieve is a deliberately simple version. It counts how many meaningful words of the query appear in each entry, adds a bonus that halves every 30 days of age, and returns the top few. The 30 days is a setting to tune for your data. Look at one detail in the code: words() removes common words like "the" and "is". Without that filter, the query "when is the Acme renewal" matched an unrelated note that merely contained "the", and ranked it above the right one. The test caught it, and the fix is the stopword set. Real systems use meaning-based search with embeddings, which RAG and vector databases covers, but the shape is the same: score, rank, take the top few.

Notice that retrieve also records last_used on whatever it returns. That timestamp feeds the next step.

Forget on purpose

Forgetting is a feature. Memory that never expires fills up with things that no longer matter, and some of it is wrong. Anthropic's memory tool documentation says the same in practical terms: track and cap file sizes, and periodically delete memory files that have not been accessed in a long time.

forget applies a simple policy. It removes entries that have not been used for max_age_days, based on last_used and falling back to the creation date, and it never removes pinned kinds, such as rules. An entry that is still being retrieved stays alive, which is the point of recording last_used.

A forgetting policy by memory type: rules are kept until a person changes them, profile facts are kept while true and reconfirmed when old, the event log keeps recent entries and summarizes older ones, session history is dropped after the run, and sensitive details are never stored

Different kinds of memory deserve different treatment:

KindKeep forHow it agesWho decides
Rules and proceduresUntil changedReviewed on a scheduleA person approves every change
Profile factsWhile trueReconfirm when old or contradictedAgent proposes, the user can edit
Event logRecent in full, older summarizedCompact, then expire what is never usedAutomatic, with a size cap
Session historyUntil the run endsTrim, then discardAutomatic
Sensitive detailsNeverBlocked at write timeYour code, not the model

A limit such as 90 days idle is only an example. Choose a period that fits how fast your data goes stale, and prefer archiving to deleting when you are unsure, so a mistake can be undone.

Privacy and trust

Stored memory is stored data about real people, and everything in this section is your responsibility, because the memory tool is client-side: Anthropic's documentation says you control where the data lives and how long it is kept.

  • Store the minimum. Every extra fact is something you must protect and may have to delete.
  • Block sensitive data at write time. Anthropic's documentation says Claude usually refuses to write sensitive information to memory, and advises adding your own validation that strips sensitive data before your handler writes a file. looks_sensitive is a small version: a pattern check for card-like numbers and words such as password and secret. It is a shortcut with a known limit, since patterns miss many forms of sensitive data, so treat it as a first gate and not a guarantee.
  • Separate users. One memory per user, enforced in your code, so one person's facts can never appear in another's session.
  • Let people see and delete their memory. A deletion request must remove the data everywhere it lives, including summaries and backups. Privacy laws in many places give people rights over their data, so ask someone qualified about your situation.
  • Do not trust what you save from outside. A web page or email can contain text meant to steer the agent. If that text is written into memory, it can influence every later session. Mark entries with their source, treat anything from untrusted content as data, and review it before it becomes a rule. Prompt injection and tool sandboxing explains the attack.
  • Keep secrets out. Passwords, API keys and tokens never belong in memory files.

Checking that memory works

Treat memory like any other part of the agent, and test it:

  • Round trip. Tell the agent a fact, start a fresh run and confirm it is recalled.
  • Correction. Change the fact and confirm the old one is gone, not just joined by a new one.
  • Noise. Fill memory with unrelated entries and confirm retrieval still finds the right ones.
  • Forgetting. Age some entries and confirm the policy removes only what it should.
  • Safety. Try to store a fake card number and a planted instruction, and confirm both are stopped or flagged.

The measuring habits from Designing tools that work apply: same tasks, before and after each change.

Mistakes beginners make

  1. Saving everything. A transcript is not memory. Save what a future run needs.
  2. Only appending. Contradictory facts pile up. Update in place.
  3. Letting a guess overwrite a fact the user stated.
  4. No metadata. Without sources and timestamps, you cannot age or trust entries.
  5. Loading all memory every run. Retrieve what the task needs.
  6. Summaries that drop decisions and open problems.
  7. Never forgetting, so stale and wrong facts live forever.
  8. Deleting without a way back when archiving would be safer.
  9. Storing sensitive data, or saving untrusted content as if it were true.
  10. Not testing memory, and discovering a problem in front of a user.

Quick recap

  • Memory needs a cycle: capture, judge, write or merge, retrieve, then review and forget.
  • Save only what a future run will need, at natural moments, with a source and a timestamp.
  • Split memory by kind: a small profile, an event log and a few reviewed rules. Use structured formats where the shape matters.
  • Update facts in place, and never let an inference overwrite what the user said.
  • Summarize old log entries and keep recent ones verbatim, so memory does not grow without limit.
  • Retrieve by relevance and recency, and ignore common words when matching.
  • Forget by age and use, pin what must stay, and prefer archiving to deleting when unsure.
  • Protect privacy: store the minimum, block sensitive data at write time, separate users, support deletion, and treat content from outside as untrusted.

Try it yourself

Copy the helpers and write your own tests first: add a fact, correct it, retrieve it among twenty unrelated entries, compact a log of ten entries with a fake summarizer, and forget entries older than a limit while a rule survives. Then connect them to the memory handler from the last lesson, so the agent writes to files through looks_sensitive and records last_used when it reads. Finally, write your own one-page memory policy: what is stored, for how long, who can see it, and how a user deletes it.

Next lesson: RAG and vector databases.