An agent does not fail because it forgets. It fails because it forgets quietly, and then speaks with the confidence of something that still remembers.
That distinction is the whole of memory engineering. A person who forgets a meeting knows they missed it; the gap announces itself. A model that has lost the third paragraph of a task — the one that said do not email this supplier — will keep producing fluent, plausible work, and nothing in its output marks the missing constraint. The output looks like an answer. It is an answer to a slightly different question.
I have spent a fair amount of this year building and supervising long-running assistants, and the lessons that survived contact with real tasks are less about retrieval than about what must not be allowed to disappear. Here is the model I use.
Three kinds of memory, three different promises
Most "agent memory" discussions collapse into one bucket. That is the first mistake. The things an agent carries across time are not one resource with one budget; they are three, with incompatible properties.
Working memory is the context window: the live conversation, the last few tool results, the current draft. It is fast, expensive per token, and meant to be disposable. Its job is to hold the state of the task in progress, and its natural end-of-life is the end of the task.
The episodic record is what happened: this request came in at this time, this call returned this, this write was verified this way. It is append-only, boring, and can be enormous, because nobody reads it whole. Its job is to be queryable later, not present now. Keeping the raw record and reading it selectively is almost always cheaper than keeping a summary of it in context.
Durable commitments are the small set of statements that change what the agent is allowed to do: the price the user agreed to, the account the work must not touch, the condition under which a message may be sent, the fact that a login is the user's alone. These are not memories of events. They are constraints that must hold across every future action.
The failure mode is that these three live in the same place — the window — and die in the same way: silently, when the window is compacted.
What compaction actually loses
Every long-running agent ships some form of compaction: older turns get summarized so the task can continue. Compaction is necessary. But a summary is a lossy encoder trained by nobody, and it is systematically bad at exactly three things.
It compresses away numbers. "The quote was 184,000 KRW including delivery, agreed on the 12th, and does not include the second box" becomes "the user discussed pricing". Numbers are the facts most likely to matter later and least likely to survive a paraphrase, because a summarizer has no reason to know which numbers are load-bearing.
It compresses away negatives. Instructions of the form don't, never, not until read like background conditions rather than events, so they are the first thing a summary drops. But a negative constraint is not context; it is part of the permission model. Losing it does not make the agent less informed, it makes the agent less restricted — the one kind of forgetting that turns into damage.
It compresses away the provenance. "The ticket count is 28" survives. "The ticket count is 28 as displayed in the app on the 13th, and the API does not return a number" does not. What is left is a fact with no expiry and no source, which is how an agent ends up confidently quoting a two-day-old number to a stranger.
A practical rule that costs almost nothing: compaction may summarize events, but it may not paraphrase commitments. Facts with money, permissions, deadlines, names and negative constraints get carried verbatim, in their own section, or they do not get carried at all. Everything else can be digested.
The ledger: what to pin, and in what shape
The workable shape I have settled on is a small structured ledger that sits beside the episodic record and survives every compaction untouched. Three fields matter more than the rest.
{ "fact": "invite link gives 30 seats per link, first come first served", "kind": "commitment", "source": "app notice, 2026-11-13", "expires": null, "immutable": true } kind separates three things that read identically in prose and behave nothing alike:
- established — verified from a primary source, with the source named.
- interpretation — my inference, which may be wrong and must be labelled as mine when it reaches the user.
- proposal — something not yet agreed, which must never be executed as if it were.
A ledger that does not make this distinction produces the most common failure of assistants in public: a proposal that gets restated three turns later as a decision, and then acted on. The agent was not lying. It lost the field that said this is not settled yet.
expires is the second discipline. Facts about the world go stale — stock counts, quotas, prices, who has replied — and an assistant that never expires anything becomes confidently wrong in exactly the domains where being wrong is expensive. A quota fact should die in hours. A delivery address should live until superseded. A refund policy should be pinned to the document version it came from.
Recovery is a different problem from remembering
The second half of the design is what happens after a failure, and it is where most of the actual engineering lives. The relevant question is not "can the agent remember?" but "when it comes back, can it tell what happened?"
Three states exist for any action, and only two of them are easy:
| State | Evidence the agent has | Correct move |
|---|---|---|
| Done | verified effect, recorded | report, move on |
| Not done | no effect, recorded failure | retry with a changed approach |
| Unknown | request sent, no verified result | read the external state before anything else |
The third row is the whole risk. A timeout is not a failure; it is an unknown. Retrying an unknown is how agents send the same message twice, book the same slot twice, and — worse — cancel someone's work twice. And because an unknown looks exactly like a failure in most logs, the retry feels reasonable.
The design that fixes this is old and unglamorous: write the intent before acting, and read the world before resuming.
journal: intent → effect → verification (what I will do, with an idempotency key) (what the tool returned) (what the external state says afterwards) Two properties make this worth the write cost. First, the journal entry exists before the call, so a crash mid-call leaves a record that says "an attempt with key msg-2291 may exist" rather than nothing. Second, on resume the agent's first action is a read of the external system, not a replay. If the message is there, the state is done. If it is not, the key has not been used and the retry is safe.
This is also why per-step verification beats end-of-task verification. An agent that verifies only at the end has one unknown to resolve; an agent that verifies per step has a chain where the last known-good state is obvious, and the failure is bounded to one step.
Budget the window like a resource with a policy
A context window is not a bucket that fills; it is a budget with categories, and a policy about what gets cut first is more useful than a bigger window.
In practice, the allocation that has held up looks like this: a fixed carve-out for the ledger and the current task's constraints (small, never evicted); a working region for the live conversation and the last two or three tool results (large, freely recycled); and a reserve that stays empty on purpose, because tool output is bursty and the failure mode of a full window mid-task is a truncated result that gets acted on as if complete.
The reserve is the part people skip. A 200k window that runs at 100% is not efficient; it is one large tool response away from silently dropping the oldest lines, which are often the constraints you pinned at the start.
Three tests I run on any memory design
The amnesia test. Delete the summary and keep only the raw record. Can the agent reconstruct every commitment it was following? If not, those commitments were living in prose, and prose is where they will die.
The contradiction test. Put two conflicting facts in memory — the old address and the new one. A good design surfaces the conflict and refuses to pick. A bad design resolves it by recency and says nothing, which is how a package goes to the wrong city.
The expiry test. Leave the agent idle for two days and ask it a question whose answer changed. A design without expiry gives the confident, stale answer. A design with expiry says "that one was true on the 13th; here is how to check it now."
None of these tests need a bigger model. They need the agent to know which of its statements are commitments, which are inferences, and which are just the story of what happened.
That is the summary I would give anyone building this: memory is not a store, it is a set of promises about durability. The window will always be too small; that is fine, and it is the reason the design matters. What has to survive is not the conversation. It is the handful of facts that decide whether the next action is allowed.
If you want the concrete version of this on a phone-side assistant — what it keeps, where it stops at verification, and how a daily usage limit behaves — I keep two public notes:
- The English guide: https://pickle-invite-code-en.deed.page/ — the app's actual behaviour, including the 4 AM reset and what a reset ticket does.
- The invite link: https://pickle.com/i/RDKXK77G9ZRT
Disclosure: I use Pickle, a phone-side AI assistant app, and I am not its vendor. The links above are mine; if a sign-up completes through the invite link, both sides receive reset tickets, and the app's own notices govern any benefit. Nothing here is a paid placement, and the memory model described is the one I run on my own long-running tasks.
