When the Agent Goes in Circles
The third attempt at the double offer looks like the first one with more code around it. The agent re-applies a change it reverted ten minutes earlier, its explanation contradicts the one it gave before, and the diff has grown from 40 lines to 180 while the test stays red. This is a stuck session, and the way out is not a better message inside it.
Every engineer who works with an agent meets this session, usually late in the day and usually on the bug that matters most. The instinct is to explain the problem once more, at greater length. This page is about recognising the loop early, understanding why more words feed it, and leaving it with everything useful it produced.
The Signs
A circling session announces itself in a handful of ways, and none of them requires reading the code closely. A fix comes back that already failed earlier in the session, sometimes word for word. An explanation contradicts an earlier one: first the sweep was at fault, now the cancellation path is, with equal confidence both times. The diff grows every round while the test stays red. Tests that used to pass start failing. And late in the session, changes appear to the tests themselves or to the checks around them, which is the move Chapter 7 called bending the test, arriving because nothing else has worked.
you The test is still red. Two offers, W1 and W2.
agent The sweep re-reads before the cancellation's write is visible. I'll add a
version check to the class state and retry the read when it changes.
... edits 3 files, diff now 112 lines ... test: red (2 offers)
you Still red.
agent The real issue is the cancellation path: it should not offer at all when a
sweep is due within 30 seconds. Removing the version check and adding a
sweep-due flag.
... edits 4 files, diff now 151 lines ... test: red, and 2 booking tests now fail
you Still red, and booking tests broke.
agent Re-adding the second active-offer check from earlier, and updating the two
booking tests to match the new flow.
... edits 6 files, diff now 180 lines ... test: red (2 offers)
The transcript above shows all five signs in three rounds. The "second active-offer check" was the first plausible fix from the previous page, tried and reverted ten minutes earlier. The explanations swing between the two paths. The diff went from 112 lines to 180. Two booking tests broke, and the agent's response was to update them.
Why It Happens
The mechanism is the one Chapter 4 described for long sessions, with a twist. The context is now full of failed attempts: their diffs, their error messages, their explanations. The agent reads all of it on every turn. It anchors on its earlier framing of the problem, because that framing is the most repeated idea in its window. And each new patch answers the latest error message rather than the original question, which is the symptom-fix shape from Chapter 7 applied to the agent's own previous output.
Your messages are part of the same context. "Still red" tells the agent that the last change failed, not why, and it responds by changing something nearby. A long, careful re-explanation lands in the same window as the five failed attempts that came before it, and it competes with them for attention. The loop is a property of the context, so no message inside the context reliably breaks it.
Stop
Stop at the second sign, not the fifth. One failed attempt is normal; it is how the loop from Chapter 1 works. A second attempt that repeats the first, or a second explanation that contradicts the first, means the session is no longer converging. Every further round costs more than the last: more tokens, more of your attention, and more failed material in the context that is producing the loop.
Two habits make the second sign easy to see. After every round, look at the size of the diff against the red-test commit with git diff --stat, and note whether it grew. And read each round's explanation next to the one before it. When the culprit moves from the sweep to the cancellation path and back again, the agent is not narrowing anything down; it is sampling stories.
The sunk-cost trap from Chapter 9 is strongest here. Forty minutes in, the session feels like an investment, and the 180-line diff feels like progress. A fix that is getting closer to right gets smaller, not larger. Growth with a red test is divergence.
Reset
Go back to the commit that holds the red test, the one you made before any fix attempt. Because every attempt was a diff against it, the reset is one command and it loses nothing you meant to keep.
$ git log --oneline -3 2204631 waitlist: wip, sweep-due flag instead of the version check 1e3c521 waitlist: wip, version check 21d9cea waitlist: step-driven test for one offer per freed place (red) $ git reset --hard 21d9cea HEAD is now at 21d9cea waitlist: step-driven test for one offer per freed place (red)
Before you reset, take one thing out of the session: the list of what the attempts ruled out. Not the code, which failed, and not the explanations, which contradicted each other, but the facts the failures established. The extra check did not help, because the double holds both paths through it. The sweep-due flag broke booking. The version check did not change the result. Three lines like that are the whole useful residue of forty minutes.
Re-Scope
The fresh session gets a smaller question. "Fix the double offer" is a symptom stated as a task, and it is what produced the loop. The question one level down names the mechanism or one step towards it: "walk through, with line numbers, how two offers can be sent for one place", or "write only the conditional update in the storage layer, nothing else". A new session given the red test, the evidence from the previous page and the ruled-out list starts where the useful part of the old one ended. Chapter 1 put it plainly: nothing carries over unless it is written down, and here that is a feature.
The fresh request is shorter and carries more information. It names the test, which is the definition of done. It lists what failed, so the agent does not rediscover it. And it states the mechanism-level task, which is small enough to finish in one diff you can read.
Take It Over
Sometimes the re-scoped attempt fails too. When two well-scoped attempts at the same step have failed, the step may need understanding the agent cannot get from its context: a storage behaviour that is documented nowhere, an assumption only the original author held, a constraint that lives in a production configuration. That is the moment Chapter 5 described for writing the code yourself. Take the one step that resists, write it by hand, and hand the agent the work around it: the tests for the sibling races, the removal of Kai's guard, the commit message draft.
Taking a step over is not a verdict on the tool. It is the same division of labour as always, adjusted to where the difficulty actually sits. The agent is fast at the parts that are well specified; you are better at the part that needed a fact nobody wrote down.
- "One more clarification will get it there." The clarification lands in the same polluted context. After two failed rounds on one point, each further round is less likely to work, not more.
- "A longer, more detailed message breaks the loop." The loop is in the context, not in the message. A short request in a fresh session beats a long one in the stuck session, because the long one competes with every failed attempt above it.
- "A new session throws away what it understood." What it understood exists only as text in the transcript. The useful part fits in three lines of the new request, and the rest is what caused the loop.
- "Going in circles means the task is beyond the tool." Often the task was stated as a symptom, "fix the double offer". Restated as a mechanism, "make check and record one storage call", it finishes in one diff.
- "A growing patch is converging." Growth with a red test is divergence. A fix that is getting closer to right gets smaller; one that keeps adding code is adding guesses.
- Stop at the second sign of circling, not the fifth. Every further round adds failed material to the context that is producing the loop.
- Reset to the red-test commit and keep only the list of what was ruled out. The commit makes the reset free; the list makes it lossless.
- Restart in a fresh session with a smaller, mechanism-level question. A task the agent can finish in one readable diff is a task it can stop circling on.
- Take the step over yourself when two well-scoped attempts at it have failed. The missing piece is usually a fact that was never in any context to begin with.
Knowledge Check
Which description is the clearest sign that a session is circling rather than making slow progress?
- The agent asks a clarifying question about the storage layer before proposing its next change to the code
- The first fix fails the test, and the agent then proposes a different approach with a much smaller diff
- It re-applies a check it reverted earlier, its diff has grown each round, and the test is still red
- The agent's explanation is long and detailed, and it cites the exact lines it plans to change
Why does a longer, more careful explanation sent into the stuck session rarely break the loop?
- The model ignores messages over a fixed length, so most of a long explanation is never actually read
- The loop lives in a context full of failed attempts, and the new message competes with all of them
- Long messages switch the agent into a planning mode that cannot edit any files until the session is reset
- The agent needs a new instructions file before it can accept corrections to its earlier work
You decide to reset and start a fresh session. What should you carry into it?
- The full transcript of the stuck session, so that the new session sees everything that was tried
- The 180-line diff from the last attempt, so the new session can finish what it had started
- Nothing at all, so that the new session approaches the problem without any earlier bias
- The red test, the evidence, and a short list of the approaches the failed attempts ruled out
When is it right to stop delegating a step and write it yourself?
- When two well-scoped attempts at that one step have failed in fresh sessions
- As soon as the first fix proposed for the step fails the deterministic test
- When the session has run longer than an hour, whatever has happened inside it so far
- Whenever the step touches storage, since agents cannot be trusted with database code
You got correct