The Agent Loop Is Mostly Error Handling
The loop that calls a model, runs a tool and repeats is the easy 20%. The other 80% is what happens when the tool call fails, the output is malformed, or the agent never decides it is done.
The short version
5 things that decide this
- 01A basic agent loop, call model, run tool, feed the result back, fits in about 50 lines and works on every demo input.
- 02Production code spends most of its length on failure paths: tool timeouts, malformed JSON from the model, and tools that succeed but return the wrong thing.
- 03An agent with no stuck-state check will call the same tool with the same arguments indefinitely, because nothing in the loop tells it that repetition is a signal.
- 04A budget cap on tokens, tool calls, or wall-clock time is not an optimization. It is the difference between a bounded task and a bill with no ceiling.
- 05Knowing when to stop and hand control to a person is a design decision, not a fallback you bolt on after the first incident.
The 50-line version always works
Every agent tutorial ends with the same loop. Ask the model what to do. Run the tool it picks. Feed the result back in. Repeat until the model says it is finished. On the tutorial's three example prompts, it works every time. That is the whole demo.
We have shipped several agents built on that loop. The core control flow stays short in each one. Everything wrapped around it is longer, because a real user does not send three clean prompts. They send a request the model misreads. They hit a tool that times out. They ask for something with no correct next step. None of that shows up in a demo. A demo is a script of inputs someone already knows will work.
Where the other 80% actually goes
A tool call fails in three different ways. It can time out with no answer. It can return an error the model has to read and react to. It can also return successfully with the wrong data. Treating all three as one generic exception has a real cost. That is how an agent tells a customer their refund went through when the payment API actually returned a 500.
The model itself produces malformed output too, separate from the tools around it. Asked to return structured arguments for a function call, it sometimes returns text that almost parses as JSON. Sometimes it picks a tool name that does not exist. Production code validates every one of those returns before acting on it. When validation fails, it retries with the error shown back to the model, or it stops and asks a person.
Stuck states are the failure mode tutorials never hit. A tutorial's tasks are always solvable. A real task sometimes is not. An agent with no loop counter will call the same search tool with the same query five times in a row, each time convinced a different result is coming. Nothing about the model catches this on its own. The loop has to.
- 01Timeouts, tool errors and wrong-but-successful results each need a different response, not one catch-all.
- 02The model's own output needs validation before a tool call runs on it, not after.
- 03A repeated identical tool call is a stuck state, and only the loop is positioned to notice it.
Design the stopping conditions before the happy path
Set a budget before the first line of the loop is written. Pick a maximum number of tool calls, a token ceiling, or a wall-clock limit, whichever fits the task. Make hitting it a normal exit path with its own message, not a crash. An agent with no ceiling does not fail loudly. It keeps running, keeps spending, and keeps looking busy.
Add a repetition check. If the same tool is called with the same arguments twice in a row, that is a signal to change strategy, not to try a third time. Log every tool call and its result somewhere a person can read later. The failures that matter happen once, on a real customer's request, when nobody is watching the terminal.
Write the handoff to a human before launch, not after the first stuck loop reaches a customer. Decide what the agent says when it stops short. Decide where that message goes. An agent that fails silently costs the same to build as one that fails with a clear message to the right person. Only one of them survives contact with production.
Questions this raises
01Why do AI agents get stuck in loops?
An agent gets stuck when a tool call fails or returns an unhelpful result, and nothing in the code recognizes the repetition. A model does not track its own history the way surrounding code can. Without a check for a repeated identical call, it retries the same failing action indefinitely. The fix is a loop counter and a rule for what happens when it trips, not a smarter prompt.
02What does production agent error handling actually cover?
It covers tool timeouts, tool errors, tool calls that succeed but return the wrong data, malformed arguments from the model, and stuck states where the same call repeats. Each needs a different response: retry, surface the error to the model, or stop and hand off to a person. A single generic try/catch around the whole loop misses the distinction that matters.
03How do you build an AI agent that knows when to stop?
Set an explicit budget before the loop runs, a cap on tool calls, tokens, or wall-clock time. Treat hitting that cap as a normal exit with its own message. Pair it with a repetition check on tool calls. Both need designing in from the start, because an agent with no ceiling just keeps running.
Related
- Claude Agent SDK →The loop this post describes, already written, with permissions as the reason to use it over a hand-rolled version.
- How do you know if an AI agent is production ready? →What separates a working demo from a system that survives a real support queue.
- Tool calling →The mechanism this post assumes: the model asks, your code decides whether to obey.

