Retry, Repair, or Replan? A Recovery Ladder for When an Agent's Step Fails
Blind retry burns money on a call that will never succeed; premature replanning throws away correct work and produces a worse plan. The deciding signal isn't how bad the error looks — it's what the error proves false.
A step in your agent's plan just failed. Most agent loops handle this one of two ways: they retry the identical call until a counter runs out, or they hand the whole transcript back to the model and ask it to start over. Both are the same mistake at different scales — treating every failure as the same kind of failure.
There are three distinct recovery moves, and picking the wrong one is expensive in a specific way. Blind retry burns tokens and wall-clock on an action that will never succeed. Premature replanning throws away correct work and often produces a worse plan, because the model now has a failure in its context and starts hedging. The fix is a ladder: climb one rung only when the rung below it is provably wrong.
🪜 The three rungs
Rung 1 — Retry. Same action, same arguments. You are betting the world was momentarily unavailable, not that your request was wrong.
Rung 2 — Repair. Same sub-goal, different action or different arguments. The intent was right; the way you expressed it was not. Narrow a date range, drop an unsupported filter, switch from the bulk endpoint to the single-record one, re-resolve an ID that turned out to be stale.
Rung 3 — Replan. Discard the remaining steps and decompose the goal again. You do this when the failure invalidates an assumption the plan was built on — not merely the step that failed.
The rungs cost roughly an order of magnitude apart. A retry is one tool call. A repair is one model turn plus one tool call. A replan is a long-context model turn plus every downstream step re-derived. Treating them as interchangeable is why "the agent got stuck" and "the agent went off the rails" are both common complaints about the same system.
The signal is the error's scope, not its severity
Teams usually route recovery on how bad the error looks. That's the wrong axis. Route on what the error proves false.
- Proves nothing false — a timeout, a 503, a rate limit, a dropped connection. The request may well be valid. → Retry
- Proves the call wrong — a 400, a schema validation failure, "unknown column", "date range too wide", an empty result where the plan needed rows. → Repair
- Proves a precondition wrong — the table doesn't exist, the account was closed, the export format changed, permission was revoked, the upstream job never ran. → Replan
That third bullet is the one worth internalizing. A precondition failure means later steps are now built on a fact that isn't true, so they will fail too. Repairing step 3 in that situation just walks you into the same wall at step 4.
A walkthrough: the nightly backfill agent
An agent is asked to backfill a reporting table for the last quarter. Its plan:
- Confirm the source export landed
- Read the export's schema
- Load rows in weekly batches
- Recompute the derived aggregates
- Post a summary
Step 3 returns a gateway timeout on the second batch. Nothing about the plan is falsified — the batch is a legitimate request that didn't complete. Retry, with backoff. → Rung 1.
Step 3 then returns payload too large. The sub-goal ("load week 2") is still correct; the batch size is not. Repair: split the week into days and continue. The plan survives. → Rung 2.
Step 3 returns column revenue_net not found. Now stop. Step 2 read a schema and the plan was built assuming that schema. If the column is missing, the export format changed, which means steps 4 and 5 are also wrong — the aggregates reference a field that doesn't exist. Replanning here is cheaper than discovering the same truth three steps later with half a table written. → Rung 3.
Same step, three failures, three correct responses. An agent with a single max_retries: 3 handles all three identically, and gets two of them wrong.
Give each rung its own budget
A single global step counter is the usual implementation, and it lets one flapping endpoint consume the entire run. Budget per rung instead:
BUDGET = {"retry": 3, "repair": 2, "replan": 1}
def recover(step, error, spent):
rung = classify(error) # transient | invalid_call | invalid_precondition
if rung == "transient" and spent["retry"] < BUDGET["retry"]:
return Retry(step, backoff=True)
if rung in ("transient", "invalid_call") and spent["repair"] < BUDGET["repair"]:
return Repair(step, error) # model sees only this step + the error
if spent["replan"] < BUDGET["replan"]:
return Replan(goal, completed_steps, error)
return Escalate(goal, error) # human, with the full ladder history
Two details matter more than the structure:
- A rung that exhausts its budget falls upward, not sideways. Three failed retries is itself evidence that the call is wrong, so it becomes a repair — it does not become a fourth retry.
- The replan budget is usually one. A second replan on the same goal almost always means the goal is under-specified or the environment is broken, and that's an escalation, not another model turn. Agents that replan repeatedly produce plans that look increasingly plausible and work increasingly poorly.
What the repair turn should actually see
The most common implementation bug in rung 2 is handing the model the whole conversation again. Repair is a local operation: the model needs the failed step's intent, the exact arguments sent, the verbatim error, and the tool's schema. That's it. A wide context invites the model to relitigate the plan — which is rung 3's job, and you didn't authorize it.
Conversely, replan genuinely needs breadth: the original goal, which steps completed and what they produced, and the precondition that turned out to be false. Explicitly telling it what was falsified is the difference between a replan and a reroll.
🔍 The instrumentation that pays for itself
Log the rung on every recovery, and a distribution falls out of it for free. Mostly rung 1 means your infrastructure is flaky, not your agent. Mostly rung 2 means your tool schemas are under-constrained — the model keeps composing calls the API won't accept, and the fix is a tighter schema, not a smarter model. Mostly rung 3 means your planner is building on assumptions it never verified, and the fix is a cheap precondition check step before the expensive work starts.
Without the rung label, all three of those show up in a dashboard as the same undifferentiated "retries" line, and every one of them gets misdiagnosed as a model problem.
The ladder isn't about recovering more often. It's about making the agent's response proportional to what actually broke — and making the failures legible enough that you can fix the real cause instead of raising the retry limit again.