An agent is a loop with a budget
That is the whole architecture. What makes it an agent rather than a pipeline is that the model chooses the next step, so the sequence is not known in advance — and everything difficult about agents follows from that one property.
It also means an agent is a tool-calling loop with two additions: a goal that survives across steps, and a stopping rule that is not "the model stopped calling tools".
while (not done) and (within budget):
decide ← the model, given the goal and everything so far
act ← a tool call, run by your code
observe ← the result goes back into the context
report
The four ways it goes wrong
The last one is the most dangerous in production, because it looks like success. Never take the model’s word for whether the task is done — check the state of the world. If the agent was supposed to file a ticket, ask the ticket system.
| Failure | What it looks like | Defence |
|---|---|---|
| Looping | The same tool, the same arguments, for ever | A step ceiling, and detect a repeated call |
| Drift | It wanders off and solves a different problem | Restate the goal every step; keep the context short |
| Compounding error | Step 3 was wrong, steps 4–9 build on it | Verify after each step; fail fast rather than continuing |
| Silent give-up | It reports success having done nothing | Check the outcome, not the model’s account of it |
Budgets, plural
A step ceiling alone is not enough, because one step can be enormous. Track all of them, check before each step, and stop with a clear reason — "budget exhausted after 8 steps" is a useful log line; a run that silently costs twenty dollars is not.
The per-tool limit is the one that prevents the expensive incidents. An agent that may send at most one email cannot send four hundred, whatever it decides.
const budget = {
steps: 10, // the hard ceiling on iterations
tokens: 100000, // the whole run, not one call
seconds: 60, // wall clock — a user is waiting
money: 0.50, // in currency, from the cost lesson
toolCalls: { send_email: 1, refund: 0 }, // per-tool limits for the irreversible ones
};
Termination
Write the stopping condition first. An agent without an explicit termination rule always has one anyway — your rate limit, your bill, or a timeout — and none of those tell you anything useful about what happened.
- Explicit completion. The model calls a
finishtool with the answer. Clearer than "it stopped calling tools", which is indistinguishable from giving up. - Verified outcome. Check the world: the file exists, the tests pass, the record is there.
- No progress. The same tool with the same arguments twice in a row means stop — it is not going to get different results.
- Budget. Any of them.
- Human. For anything irreversible: propose, and wait.
When not to build one
If the sequence of steps is known, write the sequence. A pipeline is cheaper, faster, debuggable, testable and repeatable, and most tasks described as needing an agent are a three-step pipeline with one model call in the middle.
The honest test: can you draw the flowchart? If you can, build the flowchart. An agent earns its cost when the steps genuinely depend on what is found along the way — exploring an unfamiliar codebase, debugging, research — and even then the reliability is lower and the variance is much higher than a fixed pipeline.
And when you do build one: few tools, short context, verified steps, tight budgets, and a human in front of anything that cannot be undone.