DEV Community

yureki_lab
yureki_lab

Posted on

How I Made My Autonomous Coding Agent Survive Context Resets: 5 Lessons

TL;DR

My fully autonomous implementation system runs for days at a time, and every few hours the agent's context window fills up and gets wiped. Early on, each reset meant the agent forgot what it was working on, redid finished tasks, or quietly reversed decisions it had made an hour earlier. I fixed it with a three-file state handoff protocol (a state memo, a task file, and a decisions log), strict precedence rules, and a pre-commit hook that refuses to let the agent commit without checkpointing. Here's the design, the code, and the five lessons from six months of running it. ✅

The Problem

I run a fully autonomous implementation system on a Mac mini in my closet. It's built on Claude Code (the 2.x CLI line as of September 2026) and it works through a task backlog across several repos without me in the loop. An orchestrator module hands work to parallel implementation agents, a self-healing agent cleans up after them, and a remote control dashboard lets me peek in from my phone.

The system is great at doing work. It was terrible at remembering it.

Here's why. A single agent session has a finite context window. On a long task, the window fills up with tool output, diffs, and test logs. When it's full, the harness compacts the conversation into a summary and continues. That summary is lossy. It keeps what happened reasonably well, but it drops why and what's next with alarming frequency.

Across an overnight run, that happens four or five times. The failure modes I saw in the first month:

  • Amnesia loops. One night the agent investigated the same failing database migration test three separate times in 11 hours. Each time it reached the same conclusion (a timezone mismatch in a fixture), each time it lost the conclusion in a reset, each time it started over. That night burned roughly $40 in tokens and shipped nothing.
  • Decision reversal. The agent evaluated two HTTP client libraries, picked one for a good reason, then after a reset picked the other one because it only saw "chose a client library" in the summary and re-ran the evaluation with slightly different weights. Two PRs in the same week used different clients.
  • Ghost tasks. A task that had been finished and merged was still described as "in progress" in the summary. The agent reopened it, rewrote the same feature on a new branch, and hit merge conflicts with its own earlier work.

My first instinct was "just use git history." Git is a great record of what changed. It says nothing about what was tried and rejected, what's half-finished, or what the agent should pick up next. I needed something the agent writes for its future self.

How I Solved It

The fix is boring, which is why I trust it: a small set of plain Markdown files that the agent must read on boot and must update at specific moments. No vector database, no fancy memory service. Just files in the repo, with rules.

The three files

project-root/
├── CLAUDE.md          # project spec file: goals, rules, read order (rarely changes)
├── state/
│   ├── current.md     # state memo: where we are, what's next (overwritten)
│   ├── backlog.md     # task file: checklist with status (edited in place)
│   └── decisions.md   # decisions log: append-only, numbered, with rationale
Enter fullscreen mode Exit fullscreen mode

Each file has a different job and, crucially, a different write mode:

File Contains Write mode Size cap
State memo Current position, next action, last 3–5 decisions Overwrite ~150 lines
Task file Every task with pending / doing / done / blocked Edit in place One line per task
Decisions log Numbered entries: context, options, choice, why Append only Unbounded

The state memo is the one the agent reads first after a reset. It's deliberately short. If it grows past about 150 lines, the agent is required to compress it, moving old decisions into the log and dropping resolved context.

Here's the actual template the state memo follows:

# State memo (single source of truth)

## Where we are (as of 2026-09-24 03:12 JST)
Working on: task #47 — rate limiter for the public API
Branch: feat/rate-limiter
Status: implementation done, 2 of 9 integration tests failing

## Next action
Fix the two failing tests in the burst-window case. They fail because the
fake clock isn't advanced between requests. Do NOT touch the limiter logic.

## Recent decisions (full detail in state/decisions.md)
- D-031: token bucket over sliding window (memory footprint at 10k clients)
- D-030: limits live in config, not env vars (need per-tenant overrides)

## Do not redo
- Already evaluated leaky bucket. Rejected. See D-031.
- Migration 0042 is applied in staging. Don't regenerate it.
Enter fullscreen mode Exit fullscreen mode

That "Do not redo" section is the single highest-value thing in the whole system. It exists purely because of the amnesia loops.

Read order and precedence

Files alone don't help if the agent reads them in a random order or, worse, trusts a stale one over a fresh one. So the project spec file has a hard rule that runs at every boot, including every post-reset boot:

## On every session start (mandatory, in this order)
1. Read CLAUDE.md (this file)
2. Read state/current.md
3. Read state/backlog.md
4. Skim the last 10 entries of state/decisions.md

Do not start work before completing all four.

## When files contradict each other
state/current.md  >  state/backlog.md  >  CLAUDE.md  >  state/decisions.md

The state memo is always the latest truth. If the decisions log says X
and the memo says Y, Y wins. Update the older file, do not argue with it.
Enter fullscreen mode Exit fullscreen mode

That precedence line killed the decision-reversal problem almost overnight. Before it, the agent would find an old decision entry, a newer memo note, and "resolve" the conflict by re-deriving the answer from scratch. Now it has a tiebreaker and moves on.

Here's the loop as a diagram:

flowchart TD
    A[Session boot] --> B[Read spec → memo → tasks → decisions]
    B --> C[Pick next action from memo]
    C --> D[Do work]
    D --> E{Unit of work done?}
    E -- no --> D
    E -- yes --> F[Checkpoint: update memo + tasks, append decision if any]
    F --> G[Commit]
    G --> H{Context full?}
    H -- no --> C
    H -- yes --> I[Compaction / reset]
    I --> A

The key insight in that diagram: the checkpoint happens before the commit, and the commit happens before the reset can hurt you. If the reset lands mid-task, the worst case is losing the in-flight edits since the last commit, and the memo already says what the agent was about to do.

The checkpoint rule (and the hook that enforces it)

Telling an agent "update the state files regularly" doesn't work. "Regularly" gets interpreted as "when I remember," and the agent remembers less as the context fills. So I made the rule mechanical and enforced it with a hook.

The rule is write on completion, not on a timer. Specifically, the agent must update the state memo:

  1. After finishing a unit of work (a task reaches done or blocked)
  2. Before any operation that's hard to undo (migration, force push, deleting a branch)
  3. Immediately after making any decision that has a rationale

And the enforcement is a plain Git pre-commit hook:

#!/usr/bin/env bash
# .git/hooks/pre-commit
# Refuse to commit if the state memo hasn't been updated recently.
# The agent runs for hours; a memo older than the last 45 minutes of
# commits means it's drifting and the next reset will hurt.

set -euo pipefail

MEMO="state/current.md"
MAX_AGE_MIN=45

if [[ ! -f "$MEMO" ]]; then
  echo "❌ $MEMO is missing. Create it before committing." >&2
  exit 1
fi

# Skip the check if the memo itself is part of this commit.
if git diff --cached --name-only | grep -qx "$MEMO"; then
  exit 0
fi

memo_age_min=$(( ( $(date +%s) - $(stat -f %m "$MEMO") ) / 60 ))

if (( memo_age_min > MAX_AGE_MIN )); then
  echo "❌ $MEMO was last updated ${memo_age_min} min ago (limit: ${MAX_AGE_MIN})." >&2
  echo "   Update 'Where we are' and 'Next action', then commit again." >&2
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

(That stat -f %m is macOS. On Linux it's stat -c %Y.)

When the hook fires, the agent sees the error in its tool output, updates the memo, and retries the commit. It took zero prompt engineering to get this behavior. The agent already knows how to read a failed command and fix the cause. I just had to make the cause explicit.

What NOT to persist

The first version of the memo got polluted fast. The agent dumped test output into it, pasted whole stack traces, and once wrote a 900-line "current understanding of the codebase" section. A 900-line memo is as useless as no memo, because the agent skims it and misses the two lines that matter.

So the spec file now has an explicit blocklist:

## Never write to state/current.md
- Raw tool output (test logs, stack traces, diffs). Link to the file instead.
- Speculation ("might be caused by..."). Only write what you verified.
- Anything you can derive from git (what changed, who changed it).
- Secrets, tokens, credentials, or absolute paths on this machine.
Enter fullscreen mode Exit fullscreen mode

That last line matters more than it looks. The state files get committed. If the agent copies an API key into the memo "for reference," it's in history forever.

The resume test

You can't improve what you don't measure, so I built a small test for the handoff. Once a day, a script kills the running session mid-task, boots a fresh one, and asks a single question:

"Without doing any work: what task are you on, what's the next concrete action, and what should you not redo?"

I diff the answer against the memo and against what actually happened in git. It's scored by a second, cheap model run on three yes/no questions: correct task, correct next action, no contradiction with the log.

Month 1 (summary only) Month 6 (three-file protocol)
Resume accuracy ~40% ~95%
Amnesia loops per week 4–6 0–1
Wasted overnight token spend ~$120/week <$15/week

Those numbers are from my own logs on my own system, so treat them as one data point, not a benchmark. But the direction is not subtle.

Lessons Learned

  1. Compaction is not memory. Every harness will eventually summarize your context, and every summary drops the "why." If you're running agents for longer than a single sitting, you need an explicit, agent-written handoff. Don't wait for the amnesia loop to teach you this at $40 a night.

  2. One file has to be the boss. The single biggest win wasn't the files themselves, it was the precedence rule. When two records disagree, the agent needs a tiebreaker it doesn't have to think about. "Memo wins, update the other one" removed an entire class of re-litigated decisions.

  3. Write on completion, never on a timer. "Every 30 minutes" produces memos written mid-thought that describe a state that no longer exists. "After every finished unit of work" produces memos that describe a real, committed checkpoint. And enforce it with a hook, because the agent's memory of the rule degrades exactly when the context is fullest.

  4. Small enough to read in one gulp. The memo has a hard size cap and the agent is responsible for enforcing it. The moment it becomes a dumping ground, the agent skims it and you're back to square one. A "Do not redo" section of five lines beats a "Full context" section of five hundred.

  5. Match the write mode to the data. State gets overwritten. Decisions get appended. Tasks get edited in place. When I had everything in one file with one write mode, the agent would overwrite decisions or append state, and both were wrong. Three files, three write modes, zero ambiguity.

What's Next

Three things on my list:

  • Structured front matter. The memo is prose right now. I'm adding a YAML block at the top (current task ID, branch, status enum) so the remote control dashboard can render it without parsing Markdown.
  • A staleness detector in the observability layer. The pre-commit hook catches drift at commit time. I want a background check that flags a memo whose "Where we are" hasn't changed in two hours while commits keep landing.
  • Cross-repo handoff. When the orchestrator module moves an agent from one repo to another, the memo in repo A doesn't help in repo B. I'm experimenting with a lightweight "last seen elsewhere" pointer.

Wrap-up

If you take one thing from this: the moment your agent runs longer than a context window, its memory is your problem, not the model's. Give it a place to write for its future self, tell it exactly when to write there, and make the rule mechanical.

I'm writing up the fully autonomous implementation system one piece at a time here on Dev.to: the orchestrator, the parallel agents, the self-healing loop, the remote dashboard. Follow me to catch the next one. 🚀

And I'd genuinely like to know: how do you handle context resets on long-running agents? Files, a database, a memory MCP server, something else? Drop it in the comments. 💬

Top comments (0)