Part 4 · Safety & Improvement

Loop Engineering · ~7 min

Runaway Guardrails & Stuck-Loop Recovery

A stuck agent is a fixed point in its own policy. Break it with the cheapest perturbation that works — then cap the recovery so it can't loop on itself.

Why this, for you: you've watched an agent edit the same file ten times, each patch a little different, none of them green — while the token meter spins. The fix isn't "catch it sooner." Detection already fired. What you're missing is a recovery move that fits the way it's stuck, and a cap so the recovery doesn't become its own runaway.

Detection catches a stuck agent; recovery is what the harness does next. They're separate disciplines, because the cheap fix that breaks a repeater fails on a wanderer — and the heaviest move, handing off to a human, is a poor first choice when one nudge would have sufficed.

1 Stuck vs. slow-but-converging

Recovery must not fire on legitimate slow work. The clean separator is a progress metric that can only rise when real work is done — failing tests resolved, unique sources gathered, checklist items completed.

A loop is stuck when the progress metric is flat across N heartbeats while activity continues; it's converging slowly when the metric is still rising, even by small increments.

Why insist on a progress metric? Because activity proxies — API call counts, file edits, log volume — keep rising during a stuck loop too. They cannot tell the two apart. Tune N against the workload: a tight refactor on one file looks identical to an edit loop if N is set too low.

2 Read the shape before you reach for a move

Stuck states come in three shapes, and the right recovery depends on the shape. Wrong-shape recovery makes things worse — telling a wanderer to "try a different approach" sends it further off-goal; telling a repeater to "reassess the goal" leaves the failed action unchallenged.

ShapeSymptomRecovery move
RepeaterSame action, same result, repeatedNudge naming the failed action; ask for a different approach
WandererActivity continues but nothing connects to the goalReassess the goal — "what would move the needle?"
LooperAlternates between a few actions without resolvingContext reset — wipe the priors that anchor the oscillation

3 Climb the recovery ladder one rung at a time

Each rung is a strictly larger perturbation of the agent's policy. You can't know in advance which rung suffices, so you climb — the cheapest rung that breaks the fixed point is the right one.

#RungCost
1Nudge — one message naming the observationOne token's worth; ignorable
2Replan / reflect — restate goal, list what's tried, propose a new planA reasoning turn, still in-context
3Escalate model or effort — stronger model / bigger budgetCompute
4Context reset from a progress file — reload durable state, git-revertLoses in-context state
5Human handoff — surface state + failed attemptsHuman capacity
6Abort with a logged failureThe run

Context reset can wipe load-bearing work

Rung 4 is safe only when state has been externalised cleanly. If the agent built a useful partial plan in the window that was never written to a progress file or git, "reset from progress file" loses it — and the next iteration just repeats the work. Reset rides on trajectory logging being already in place.

4 Bound the recovery itself

Recovery has the same runaway problem as the original loop. Without a cap, the harness builds a recovery-loop on top of the loop. Two bounds keep that from happening:

Half of automated recovery moves don't work in the wild — in one 220-loop dataset, only 6 of 12 responses reduced their target signal, and one detector generated 13.3× more signals than it suppressed by firing on its own output. Treat every rung as a hypothesis: measure its effect on the target signal before trusting it.
# Recovery, gated by shape and capped two ways on detector_fire(signal): shape = classify(signal) # repeater | wanderer | looper if attempts[signal] < 3: apply(rung[shape]) # same rung, 3 strikes else: rung = next_rung(rung); attempts[signal] = 0 if iteration > MAX_ITERS: abort_and_log() # hard backstop

↪ Your win: a stuck loop you can break on purpose

Retrieval practice — recall, don't peek

Question 1What cleanly separates a stuck loop from one that is converging slowly?

Question 2You diagnose a wanderer — activity continues but nothing connects to the goal. The right move is to…

Question 3Why climb the recovery ladder one rung at a time instead of jumping to a context reset?

Question 4Beyond the per-incident three-strikes cap, what backstop catches unproductive iterations the pattern detector misses?

Question 5 · spaced recall from an earlier lessonThe Go / No-Go gate says you should spend another iteration only when…

Ask me anything. Want help picking a progress metric for your workload, or wiring the three-strikes cap into your harness without it fighting the iteration backstop? Next in Part 4: Improvement & Flywheel Loops — closing the outer loop so the system gets better between runs.
✎ Feedback