Chapter 1 of 7 · 18 min read
A model, tools, a loop, and a condition that ends it
On this page
An agent, throughout this topic, is a program that repeatedly asks a large language model — "the model", from here on — which of a fixed set of actions to run next, runs that action itself, shows the model what came back, and asks again, until something stops it. Nothing in that description is unusual software apart from the asking. The model never runs anything; it names one of your functions and the arguments to call it with, and your code decides whether and how to execute it.
Agents exist because a single model call can only answer from what it was given at the start, and a great many tasks cannot be finished that way. You do not know how many invoices a customer has until you have looked them up, and you cannot say which file sets a service's version number until you have read the file that points at it. Putting the call in a loop and feeding each result back is what allows the second step to depend on what the first one returned. You have almost certainly used something built this way: a coding assistant that opens files, runs the test suite and edits until it passes; a support chat that looks up your order before it answers; a research tool that searches, reads what it found, and searches again with better terms. In each of them a model is being asked the same question over and over — what should happen next, given everything so far — and a loop somewhere is carrying out what it said.
Those four parts produce all of the behaviour that gets called agentic: a system that takes several steps, decides each step after seeing the result of the previous one, and stops when it is done. A deletion exercise — take one component out, re-run the tests, and record what the result loses — has a stable end point, and it is worth knowing where it lands before you write the first file rather than after. The list is short enough that any component not on it has to say what it adds.
The four parts, in about forty lines
The reason to write the loop out rather than describe it is that the description hides how small it is. This is a complete agent, using the tool-calling protocol from earlier, with the four parts marked:
from anthropic import Anthropic
client = Anthropic()
MAX_ITERATIONS = 10
def run_agent(user_input: str) -> str:
messages = [{"role": "user", "content": user_input}]
for i in range(MAX_ITERATIONS):
response = client.messages.create( # 1. the model
model="claude-opus-5",
max_tokens=2048,
tools=TOOLS, # 2. the tools
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use": # 4a. an exit
return text_of(response)
calls = [b for b in response.content if b.type == "tool_use"]
results = [
{
"type": "tool_result",
"tool_use_id": call.id,
"content": dispatch(call.name, call.input),
}
for call in calls
]
messages.append({"role": "user", "content": results}) # 3. the loop
return "Reached the iteration limit without a final answer." # 4b. the other exitThe model is one HTTP call. What makes it usable here rather than as a text generator is that the response can contain a structured request — a tool name and a JSON argument object — instead of a sentence describing what someone should do. That structure is what lets the next line of code act on it without parsing prose.
Two things about that first part are easy to blur together. The model decides; it does not act. Every side effect in the system happens inside dispatch, which is ordinary code you wrote, running with credentials you granted, against systems you chose to expose. The model's entire contribution per iteration is a few hundred bytes of JSON naming one of your functions and its arguments. Keeping that boundary in view is what makes the rest of the topic tractable, because the questions that matter — what can this thing touch, how often, and for how long — are all questions about code on your side of the line.
That last sentence is a conclusion with four steps under it, and the steps are worth walking rather than skipping, because skipping them is how people end up believing the model is doing more than it is. The response carries a name and an argument object, so it can be dispatched rather than interpreted. Because it is dispatched, the code that acts is code you wrote. Because that code is yours, it runs with the credentials you granted, against the systems you chose to expose, at whatever rate your loop calls it. And because all three hold at once, every question about reach, frequency and duration is answerable by reading your own repository rather than by reasoning about the model.
The tools are the list passed as TOOLS, and their significance to this definition is that the set is fixed before the loop starts. The model chooses among them; it does not invent new ones, and it cannot reach anything you have not written a schema for. An agent's action space is exactly as large as its toolset, which is a property worth stating plainly because it is the main thing that makes the behaviour bounded in any useful sense.
The mechanism under that is worth spelling out, because bounded is a strong word to accept on trust. The model's output is a name. Your loop looks that name up in a dispatch table you wrote. A name that is not in the table has no code behind it, so the worst it can produce is an error result handed back to the model. The bound is therefore a property of the dispatch table rather than of the prompt — which is why an instruction telling the model not to do something is a preference, and a tool you never wrote is a limit.
The loop is the two append calls and the for. Its job is to make the result of one action visible to the decision about the next one. Remove the second append and the model asks for the same tool forever because it never learns what came back; remove the for and you have a single tool-calling round trip, which is a different and often better-behaved thing. The loop is where the "several steps, each chosen after the last" behaviour actually lives, and it is four lines.
The condition that ends it appears twice in that listing, marked 4a and 4b, and the difference between them is the subject of most of this topic. 4a fires when the model returns a turn with no tool-use block, which is the model saying it is finished. 4b fires when the loop has gone round ten times, which is you saying it is finished. Both are exits. Only one of them is something you control.
| Part | What it contributes | Delete it and you get |
|---|---|---|
| A model that can request actions | Turns a question into a structured, executable request | A text generator; the calling code has to parse prose to act |
| A fixed set of tools | Defines the entire action space, bounded before runtime | A model that can describe actions but not take any |
| A loop that feeds results back | Lets step three depend on what step two returned | A single round trip: one call, one result, one answer |
| A condition that ends it | Puts a ceiling on iterations, latency and spend | A loop whose exit is decided by something other than you |
The right-hand column is the one to read carefully, and it reads best as a set of gaps rather than as a set of levels. Delete the model's ability to request actions and the gap is a parser: something still has to turn prose into a call. Delete the fixed toolset and the gap is any effect at all. Delete the loop and the gap is exactly one thing — the ability of step three to depend on what step two returned — which is a narrower loss than most people expect, and often the right one to accept. Delete the condition and nothing at all is missing from a run that goes well, which is precisely why it is the part that goes missing.
The part teams leave implicit
The loop is usually written as while response.stop_reason == "tool_use": with no counter, and everyone who reads it understands it as terminating. It does terminate, in the same sense that a while not done: loop terminates: whenever the thing being waited on eventually happens. The question a reviewer should ask is who decides that it happens, and the answer is the model. Nothing in the code caps anything.
This is the strongest form a hidden defect takes, and it is worth naming as a shape rather than as an anecdote: a quantity that somebody checks stays constant while the property that actually matters varies. The quantity is everything visible from a demo. The code is the same length with or without a counter. The per-iteration latency does not move. The median iteration count does not move — most turns finish in two or three passes whether or not a bound exists. What varies is who owns the exit and what the worst case costs, and neither of those leaves a mark on any of the three things a reviewer was watching. The belief being defended is that the model stops calling tools, and that belief is true almost every time, which is exactly what makes it a poor defence: a guard that holds on every input you have seen tells you nothing about the input you have not.
Three parts plus an implicit fourth is the most common shape of agent code in the wild, and it is tempting for a reason that has nothing to do with carelessness. The three explicit parts are the ones that make the demo work. The fourth part only does anything on inputs that do not appear in the demo, and adding it means answering a question the demo never asks: what do you return to the caller when the budget is gone and there is no answer? That question is genuinely harder than adding a counter, which is why teams that add the counter often add it as raise RuntimeError("max iterations") and consider the matter closed. Chapter 5 is about the exits in detail — how many there are, which ones the model owns, and what the twelfth iteration should hand back.
For now the point is structural: a loop with three parts has an exit, but the exit belongs to a component whose behaviour you can influence and cannot constrain. If you can only remember one thing from this chapter, make it the fourth part, because the other three are hard to leave out by accident.
The arithmetic of a runaway turn
The bill for a runaway conversation is worth doing in your head, because it makes the case better than the principle does. Input tokens grow on every iteration, since each request resends the whole conversation plus every result appended so far, so 41 iterations of a conversation that ends up around 40,000 tokens costs on the order of 900,000 cumulative input tokens rather than 40,000. At a list price around $5 per million input tokens that is roughly $4.50 for one question that was never answered, against about a cent for the same question when it goes well.
Those figures are worth deriving rather than quoting, because a derivation settles every cap you might choose while a pair of figures settles one. Call the number of iterations n, and suppose each iteration adds roughly the same amount to the list — one assistant turn and one set of results. The first request then carries about one unit of conversation, the second about two, and the n-th about n. What you are charged for on the input side is the sum of all of them rather than the size of the last of them, because each request is priced on its own contents and the provider holds nothing between requests. Adding one, then two, then three, up to n, gives n times n-plus-one, halved — which for anything but the smallest n is close to half of n squared.
Two readings come out of that and both are worth carrying. Measured against the conversation you can see at the end, which is n units long, the cumulative input is about half of n times that final size, so the number on the bill exceeds the number in the transcript by a factor of roughly half the iteration count. Measured against n alone, spend grows with the square of the iteration count while wall-clock time grows only with n, because each iteration is one round trip whatever the list weighs. The second reading is the one that catches people out, since the clock is the thing they are watching while the loop runs.
Evaluating that relation at the figures above reproduces them. One unit here is whatever a single iteration adds, which for that turn was about a thousand tokens. Half of forty-one is a little over twenty, so the cumulative input is about twenty times forty thousand tokens — a little over eight hundred thousand, which is the same order as the nine hundred thousand actually recorded. The even-growth assumption accounts for the gap: results are not all the same size, and a large one returned early is re-sent more times than a large one returned late, which pushes the true sum above the estimate. The money then follows with nothing left over, since nine hundred thousand tokens priced at five dollars a million is nine tenths of five dollars.
Reading the same relation at the small end produces the other figure without a second measurement. A question that goes well resolves in one or two iterations, which is one to three units of conversation — a few thousand tokens, and about a cent at the same rate. So the two prices in this chapter are one relation evaluated at two values of n, and that is what makes the multiplier predictable rather than surprising. Forty-one iterations against two is a ratio of about twenty in n, and twenty squared is four hundred, which is the size of the gap between the two prices.
The multiplier is what to notice: a runaway turn does not cost a little more than a normal turn, it costs two or three orders of magnitude more, and it does so while holding a worker and a user connection open. What the relation licenses beyond that is pricing a bound before anyone has hit one. With the ten-iteration limit in the listing above, the worst a turn can reach is about fifty-five units of conversation — a few tens of thousands of tokens, a quarter of a dollar or so at the same rate — and nobody had to produce a runaway to learn that number. That is the practical difference between a derivation and an incident report.
What the four-part definition does make visible is that cost structure, which is the part that surprises people migrating from single-call features. Each iteration is a full model round trip — two to five seconds for a request of this size — plus however long the tools take, and the input side of every request contains everything that came before it. A turn that resolves in one iteration feels like the single-call feature it replaced; the same code taking six iterations takes twenty to thirty seconds and costs several times more, and nothing in the four parts prevents that from happening on a Tuesday afternoon when a tool starts returning empty results. The loop is what buys the capability and the loop is what spends the money, and those are the same line of code.
"Several times more" is the conservative end of that range, and which end applies depends on a split the derivation glossed over. Every request carries a fixed part that does not grow — the system prompt and the tool schemas — alongside a part that accumulates. Where the fixed part dominates, six iterations cost about six times one, because almost nothing is compounding and the growth is linear after all. Where the accumulated conversation dominates, the square law takes over and six iterations cost about twenty-one times one. By the fortieth iteration of a runaway there is almost nothing left in a request that is not conversation, which is why a runaway sits at the far end of that range and a short turn sits at the near end.
A boundary around a shape of program
The four-part definition says nothing about whether building the thing was a good idea. Anthropic's own guidance in Building more effective AI agents opens by arguing that most tasks people reach for an agent to solve are better served by a single call or a fixed pipeline, and the four parts do not contradict that — they tell you what the thing is, not whether you want one. A minimal definition is a defence against cargo cult, not an endorsement.
It says nothing about quality either. An agent with two badly described tools and a vague system prompt is still an agent by this definition, and it will still be worse than a well-written if statement at the same task. Nothing in "model, tools, loop, stop" implies that the model chooses well, that the tools return useful things, or that the conversation converges. The definition draws a boundary around a shape of program; the engineering is all in the contents.
It is also not the same claim as "this system uses a language model." That confusion is the most common one and it is worth naming here even though chapter 4 does the work of drawing the line properly: a nightly job that calls a model once per row and writes the result to a column uses a model, has tools in the loose sense that it writes to a database, and runs in a loop over rows — and it is not an agent, because the model never chooses what happens next. The distinction is about where control flow is decided, and it is sharp enough to settle arguments in design reviews once you have it.
Finally, the definition does not tell you that the additions are worthless. A planner, a memory layer, a second model reviewing the first — each of these solves a real problem for someone. What the four-part definition gives you is a baseline to measure them against, so that "we need a supervisor agent" becomes a claim with a control condition rather than an architectural default. Chapter 3 runs that experiment: a stubbed model, one real tool, a while loop and a counter, built in an afternoon, to see how much of the behaviour survives with the machinery removed.
The four parts are worth being able to write from memory, and the exercise that fixes them in place is deletion rather than addition: take a component out, re-run the tests, and see whether anything is worse. What survives is a model that can be asked for an action, a fixed set of tools it may request, a loop that feeds each result back, and a condition that ends the loop. The first three are hard to leave out by accident, because a run that omits any of them visibly fails. The fourth is easy to leave out and expensive to leave out, and the arithmetic above is why: the exit belongs to the model, and the price of letting it decide grows with the square of how long it takes.
Look back at the listing once more, at the list called messages. Every part of the agent's state is in it. The loop appends to it twice per iteration, in a specific order, with a specific correspondence between the identifiers in one append and the identifiers in the next. That list is the only thing that survives from one iteration to the next, and it is the first thing to break.
What an agent is — read the current version at https://daviddominguez.dev/learn/en/what-an-agent-is/a-model-tools-a-loop-and-a-condition-that-ends-it