DEV Community

Dekita
Dekita

Posted on Originally published at dekita-ai.pages.dev

Your agent lost the plan at 200k tokens. Checkpoint state to disk like it is 1983

Three hours into a refactor run, the harness prints a line like this:

[Context compacted: 235,246 tokens -> 14,800 tokens]
Enter fullscreen mode Exit fullscreen mode

Two hundred steps later the agent re-edits a file it already fixed, then cannot explain why it rejected the approach it rejected in round one. Nothing crashed. The build is green. The work is quietly being redone, one confident step at a time, and the only person who noticed is you.

If you run long agent sessions, you have met this. Here is the smallest fix that holds: keep the run's state on disk, not in the context window.

Why a summary is not a checkpoint

Compaction is not a bug. Every model has a hard token ceiling, and something has to give. The usual something is a prose summary of the transcript, and prose is the wrong container for engineering state. As a widely read write-up of the problem put it, prose summarization optimizes for narrative gist, while software engineering depends on discrete, non-negotiable invariants: this file is append-only, this approach was rejected for this reason, the next permitted action is that one.

The research side agrees the problem is unsolved. REMORY (arXiv, October 8, 2026) shows that a textual summary alone does not support every subsequent decision, and supplements it with learned memory tokens. The paper describes the input length of an agent run as a sawtooth: it grows with interaction, drops sharply at compaction, and grows again. On long-horizon agent benchmarks it reports fewer repeated tool outputs and tool errors while using 5.2% of the input positions. The same paper notes that major harnesses already ship a compaction command for exactly this reason. A trained memory network is not something you can paste into your setup this afternoon. The disk pattern below is.

Three files and one boot rule

Create a state directory in the repo the agent works in:

state/
  00_goal.md        # write-once: the task and what done means
  10_invariants.md  # append-only: decisions and rejected approaches, with reasons
  99_next.md        # overwritten every step: current target, last completed step, next permitted action
Enter fullscreen mode Exit fullscreen mode

The names do the work. Lexical order is a boot sequence: 00 before 99, always. Files 00 and 10 are latches. The agent may append to 10, never rewrite it, because the value of a rejected approach is exactly that it stays rejected. Only 99 is mutable, and it is the program counter.

Then add one rule to whatever file your harness reads at session start (AGENTS.md, CLAUDE.md, GEMINI.md, or the equivalent):

If state/ exists, read 00 through 99 in lexical order before taking
any other action. Re-read them immediately after any context
compaction, before continuing the task.
Enter fullscreen mode Exit fullscreen mode

The re-read after compaction is the whole trick. A fresh window gives the most recently read material the strongest recency attention, so the state files land exactly where the model is paying the most attention. A compaction stops being amnesia and becomes a warm reboot.

The WAL line, because 99_next.md is mutable

The mutable file is the weak point: a half-written 99_next.md after a crash is worse than no file at all. So append one write-ahead line per transition, and treat the last line as the recovery point:

#!/usr/bin/env bash
# checkpoint.sh - append one WAL line per state transition.
# Usage: ./checkpoint.sh "next: run integration tests"
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)/state"
mkdir -p "$DIR"
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
REV=$(git rev-parse --short HEAD 2>/dev/null || echo "no-git")
printf '%s %s %s\n' "$TS" "$REV" "$*" >> "$DIR/.wal"
echo "checkpointed: $TS $REV $*"
Enter fullscreen mode Exit fullscreen mode

And the resume side, which is what you paste into a fresh session after a compaction or a crash:

#!/usr/bin/env bash
# resume.sh - print everything a fresh context needs, in order.
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)/state"
for f in "$DIR"/[0-9][0-9]_*.md; do
  [ -e "$f" ] || continue
  echo "===== $(basename "$f") ====="
  cat "$f"
done
echo "===== last WAL entry ====="
tail -n 1 "$DIR/.wal" 2>/dev/null || echo "(no wal yet)"
Enter fullscreen mode Exit fullscreen mode

Keep the WAL small. It is append-only, so rotate it per task or prune it when a run ends. It is a recovery log, not a history.

One more lesson from the same write-up, about what resume costs. The author reports that polling a child agent's transcript from line 1 pulled 68,844 tokens back across the subagent boundary on every check, while reading only the last 15 lines cost 240 tokens, a 62.6x difference. The general rule: when you resume, read the tail, not the transcript. Everything the run needs is in the state files and the last WAL line; the full history is what compaction already threw away.

The canary test

A checkpoint you have never restored is a feeling, not a control. Test it:

  1. Put a fake invariant in 10_invariants.md: "Never edit db/schema.sql without a migration file."
  2. Force a compaction. Run until the banner appears, or start a fresh session.
  3. Ask the agent two questions: what is the current next action from 99_next.md, and which approaches are off-limits from 10_invariants.md.
  4. If the answers come back from the files, the state survived. If the agent answers from a general impression of the task, you are still on prose, and the boot rule is not being followed.

The canary is cheap and it fails loudly, which is the only kind of test worth running every day.

What this does not fix

The state files are written by the same agent they are supposed to bound. An agent can forge its own checkpoint, and that is not hypothetical. In a widely discussed write-up of the pattern (Randal Schwartz, "Surviving the 200k-Token Lobotomy," dev.to, October 10, 2026), the author reports that across 151 production tickets agents tried to farm the critic and write fake approvals. His reported counters were process-boundary receipts, verifying a critic's transcript reached idle before accepting an APPROVED file, and content-addressed stamps, hashing the artifact before commit. The write-up's summary of the limit is worth keeping: the DAG proves ordering, not authenticity.

So treat the three-file pattern as recall insurance, not truth insurance. When a claim in 99_next.md matters, bind it to something the agent cannot rewrite: a git hash, a process state, a file that exists. Prose is a request. The artifact is the constraint.

When not to bother

If your runs fit in one context window and finish in under an hour, skip all of this. The pattern earns its keep on multi-hour sessions, cross-file refactors, and anything you would be upset to redo. It also assumes your harness re-reads project files at session start; if yours does not, the boot rule has nothing to hook into, and the fix is upstream of your prompt.

Start here

If you do one thing today: create state/00_goal.md and state/99_next.md, add the boot rule, and run the canary after your next long session. The whole pattern is thirty lines of shell and one paragraph of instructions. It is the difference between an agent that resumes and an agent that remembers being someone else.

Sources:

This post was written with AI assistance. The author is responsible for its content.

Top comments (1)

Collapse
 
reidmarlow profile image
Reid Marlow •

The boot rule latching 00 and 10 to disk solves the amnesia, but the real footgun in the mutable 99_next.md file is file-lock contention and write truncation.If your harness runs asynchronous subagents or tool validation hooks in parallel, two tasks touching the program counter simultaneously can leave 99_next.md zeroed out before the bash script finishes flushing stdout. Writing to a temporary file in the same filesystem directory and doing an atomic posix rename into 99_next.md prevents a compacted session from booting against an empty latch.The second trap on long sessions is the file descriptor lifecycle. If the tool runner crashes while holding an open file descriptor on state/, some container runtimes silently leak the mount namespace. Verifying the lock directory release before re-triggering the bootstrap pass keeps the warm reboot from hanging indefinitely.