Search

Sign in to launch Copilot/Codex from the palette.

Rewind a failed agent run

20 failed coding-agent runs. Going back to the best step with one hint fixed 15. Starting over with the same hint fixed 4.

Field note 2026.11.01

When a coding agent fails, the usual fix is to start over with a better prompt. The agent reads the same files again, makes some of the same edits again, and spends its turns getting back to where it was.

I wanted to try the other way. Save the agent at every step. When it fails, go back to the step where it was closest, give it one hint, and let it continue from there. Like undo, but for an agent.

This is the second spike on amber, which pauses a running program and can copy it from any pause. The first spike asked when pausing matters at all. This one asks whether it is useful.

The setup

The agent is a small loop written in JavaScript that runs inside amber. Each turn it calls tool("think"). That call pauses the program. My host code sends the history to the model and answers with its reply. So every turn is a pause, and amber saves the whole agent there: the chat history, the files it has edited, and the turn counter.

for (let turn = 0; turn < limit && !finished; turn++) {
  const answer = tool("think", { history, turn });
  if (typeof answer.limit === "number") limit = answer.limit;
  if (answer.note) history.push({ role: "user", content: answer.note });
  history.push({ role: "assistant", content: answer.content });
  const action = parse(answer.content);
  if (action.tool === "write") files[action.path] = action.content;
  if (action.tool === "check") errors = tool("tsc", { files }).errors;
  if (action.tool === "done") finished = true;
}

The agent has four actions: read a file, write a file, check, and done. Check runs tsc through accrete's kept TypeScript tool, which took about 0.6 seconds per call and no container time.

The tasks are 8 small TypeScript repairs: a renamed field, an optional email with exactOptionalPropertyTypes, unchecked index access, a new union member, a function that became async, a generic result type, readonly arrays, and a config that gained a field. A run succeeds when tsc reports 0 errors and the agent did not edit tsconfig.json or use any, @ts-ignore or @ts-expect-error. No run cheated.

Finding failures

The first problem was that agents did not fail. glm-5.3-flash fixed every task run that finished. Gemma 4 26B fixed 11 of 12 runs with 8 turns. Llama 3.2 3B fixed 0 of 16, which is a test of a model that cannot do the task, not of rewinding.

So I used Gemma with 5 turns. That is tight. The model usually reads every file first, then checks, and runs out of turns right after it learns what is wrong. That is a real failure: an agent out of budget. Of 26 first runs, 3 succeeded. I kept the first 20 failures.

Rewind versus start over

For each failure I wrote one hint for the task, the kind a person would give, and tried two things:

  • Rewind. Go back to the last step with the fewest type errors. Copy the saved agent with DurableScript.fork. Add the hint and give it 5 more turns.
  • Start over. A fresh run with the hint added to the task, and 5 turns.
const saved = run.pauses[best].exported;
const fork = DurableScript.fork(saved, { programId, storage, tools, wasm });
const hint = "A person looked at your work so far. Hint: ...";
const reply = await model([...history, { role: "user", content: hint }]);
await fork.answer(run.pauses[best].token, { content: reply, note: hint, limit: turn + 5 });
FixedTokensTokens per fix
Rewind to the best step, with the hint15 of 2068,559about 4,600
Start over with the hint in the task4 of 2042,405about 10,600

Rewinding spends more tokens per run, because every turn carries the earlier history. It spends fewer tokens per fix, because most of its runs end fixed. Starting over spends its 5 turns reading the same files again and usually runs out at the same place.

The split matters. In 4 of the 20 failures, the agent had never run a check, so its best step was turn 0. Those rewinds are just a restart with a hint, and they fixed 0 of 4. On the other 16, rewinding fixed 15. Starting over fixed 3.

Each saved step was about 8,389 bytes (median). A run of 5 steps is about 40 KB.

What this means

In this test, the work an agent did before it failed is worth keeping. A hint at the right step finished the job most of the time. The same hint at the start did not, because the agent had to redo the same reading and ran out of turns again.

You can get part of this without amber: save the chat history and restore it. amber also saves the agent's own state, here the files it edited and the turn counter, with no code to save or reload them. That matters more as the agent's state grows past a chat log.

Limits

  • One model, 8 synthetic tasks, 5 turns. The tight budget is what made failures common. With more turns there was little to rewind.
  • I wrote one hint per task. A person looking at a real run writes a hint that fits the failure.
  • A rule picks the rewind point: the last step with the fewest errors. A person might pick better.
  • Both sides got the same 5 turns after the hint. Rewind also keeps the turns before the rewind point, which is the point of rewinding, but it means the rewound agent has more context.

The agent, tasks, harness and receipt are in the agent-rewind repo. The receipt has every run.