Every agent, whatever the framework, reduces to the same loop:
while not done:
context = gather(goal, history, observations)
action = model(context) # text, or a tool call
result = execute(action) # run the tool, capture output
history += (action, result)
done = check(goal, history)
The loop is trivial. The engineering is in the four functions it calls, and teams consistently underinvest in the two that look least like AI work: execute and check.
Tools are the product
A tool is a function signature plus a description, handed to the model. The model decides when to call it. That means tool design is interface design, and the consumer is a model under pressure:
- Make tools forgiving. If the model passes a relative path where you expected absolute, resolve it. Every strict validation that could have been a coercion is a wasted loop iteration.
- Return errors the model can act on.
"file not found: src/uesr.ts — did you mean src/user.ts?"saves an iteration."ENOENT"spends one. Your tool errors are prompts; write them like prompts. - Keep the tool list short. Twenty overlapping tools force the model to spend reasoning capacity on tool selection. Five orthogonal tools with crisp boundaries outperform a kitchen sink almost every time.
- Make destructive actions explicit. Separate
readfromwritefromdelete. A singlerun_anythingtool is easier to build and impossible to govern.
A useful smell test: could a new hire, given only your tool names and descriptions, guess correctly which tool to use for a task? If a human would hesitate, the model will too — you just won't see the hesitation, only the wrong call.
Stop conditions
The second-most-common agent failure after wrong tool calls is not stopping. Agents over-iterate: they keep "improving" working code, re-verify what is already verified, or loop between two inconclusive states.
Give every agent three exits:
- Success, verified. The goal-check passed — tests green, output matches spec. The only exit that should feel good.
- Budget exhausted. A hard cap on iterations, tokens, or wall-clock time. The cap is not a failure handler; it is a circuit breaker that converts a runaway loop into a reviewable partial result.
- Blocked, escalate. The agent determines it cannot proceed without information or authority it lacks — and says so, with its partial work attached. An agent that can say "I'm stuck because X" is worth ten that thrash silently.
Verification turns generation into search
The single highest-leverage addition to any agent loop is a verifier: a way for the agent to check its own output that does not rely on the model's opinion of itself.
For coding agents the verifier is obvious — run the tests, run the type checker, run the linter. For other domains you have to build one: a schema the output must validate against, a calculator that re-checks the arithmetic, a second model instance prompted as an adversarial reviewer with the rubric in hand.
The effect is qualitative, not incremental. Without a verifier, the agent's reliability is the product of its per-step reliabilities — the gap from chapter 1, compounding against you. With one, a wrong attempt becomes a recoverable event inside the loop instead of a wrong final answer, and the agent's job changes from "be right" to "search until the verifier passes." Models are much better at the second job.
Granularity: one agent or several?
The default answer is one. A single agent with a good loop, good tools, and a verifier handles a surprising range of tasks, and every agent you add multiplies the coordination surface.
Split when one of these becomes true:
- The context diverges. Two phases of the task need mostly disjoint information — research vs. writing, exploration vs. execution. A sub-agent with a clean context outperforms a main agent dragging ten thousand tokens of irrelevant history.
- The risk diverges. You want the planning agent to read everything and the execution agent to touch almost nothing. Separate agents give you separate permission boundaries.
- The work parallelizes. Independent subtasks — review these eight files for the same flaw — can fan out to cheap parallel workers whose results are easy to merge.
When you do split, make the handoff a document, not a conversation. A structured brief — goal, constraints, what was tried, what is known — survives the transfer. A chat transcript does not: the receiving agent inherits the noise along with the signal.
Designing for interruption
A production agent will be interrupted — by the user changing their mind, by a deploy, by its own budget cap. Treat interruption as a first-class path, not an exception.
Two properties make an agent interruptible. First, checkpoint at boundaries: after each completed action, the agent's externalized state (files written, notes appended, tasks marked) should be sufficient for a fresh instance to resume without replaying the conversation. Second, make actions idempotent or skippable: a resumed agent will re-attempt the step it was killed during, so either the step tolerates re-execution or the agent can detect completion and move on.
The test is brutal and simple: kill the agent mid-task, restart it with only its external state, and see whether it finishes. Agents that pass this test are also, not coincidentally, the ones whose work can be audited after the fact.