Lesson 2: The Core Loop: From One Round-Trip to Continuous Operation
Learning goals:
- Recite the four steps of the multi-turn loop that stop_reason drives, and connect one tool-call round-trip into a while loop that keeps running
- Use the value of stop_reason (tool_use / end_turn) to decide whether the loop continues or stops, and explain why that field is the loop's while condition
- Point out which boundaries this skeleton loop is missing, and explain why the history grows on every turn and why you can't rely on the model alone saying "I'm done"
Prerequisites: You've read Lesson 1 and know that a harness is the layer of control code around the model; you can read the tool_use / tool_result of a single tool-call round-trip | Prev: Lesson 1 << | Next: Lesson 3 >>
One Round-Trip Stops Being Enough
Lesson 1 already took a full tool-call round-trip apart: the model returns stop_reason: "tool_use" along with a tool_use block, your host code reads out name and input, actually runs the thing, packs the output into a tool_result and sends it back, and only then does the model give its final answer. Three chunks of JSON, one trip, done.
Real tasks are rarely that polite. Change the scenario: you're writing an on-call bot, and a user says, "Restart the api service for me, then check whether the logs still have errors, and paste them if they do." That one sentence packs in two jobs, and the second depends on the first — checking the logs means nothing until the restart has finished. The model can't do both in the first turn. All it can do is this:
- Turn one returns
tool_use, calling restart_service. You run it and send "restart succeeded" back.
- Turn two returns
tool_use again, this time calling read_logs. You run it and send the log contents back.
- Turn three finally returns
stop_reason: "end_turn", with a line like "Restart complete; the logs have two timeout errors, pasted below."
One user request, three trips back and forth. What the model can see at each step, and what it does next, depends on what came back in the previous tool_result — which is exactly Anthropic's definition of an agent: an LLM using tools based on environmental feedback in a loop.1 The single round-trip from Lesson 1 is just the special case where that loop happened to turn only once. What this lesson does is connect "one round-trip" into "round-trips that keep going," and get a clear look at what the loop in the middle is, what drives it, and where it needs a brake.
The Four Steps of the Loop
Turning a single round-trip into a loop doesn't require inventing anything new. You just repeat the moves you already know. The Claude API docs write this multi-turn process out as a fixed sequence:2
- You send a request carrying
messages and the tools manifest (tools has to go along on every single turn).
- The model returns a response. If it still needs a tool,
stop_reason is "tool_use" and content carries one or more tool_use blocks.
- You execute every
tool_use block and turn each output into a tool_result block. The key word in that step is every: however many tool_use blocks came back in one response, the next user message needs that many matching tool_result blocks, each claimed by its tool_use_id, all packed into that one immediately following user message. The docs put the rule this way: "Whichever strategy you use, return one tool_result for each tool_use block, all together in the next user message. Match each result to its call with tool_use_id, and put every tool_result block before any text content in that message."3
- You append both the model's full response for that turn (
assistant role) and the tool_result batch you assembled (user role) to messages, then send another request.
Then comes the sentence that matters most: repeat from step 2 while stop_reason is "tool_use".2 That "repeat while" is the axle that stretches a single round-trip into a loop. Lesson 1's example stopped at the first end_turn because one trip was all that task needed; the on-call bot runs steps 2 through 4 three times over, until turn three comes back end_turn.
Worth remembering: the fields on the tool_use and tool_result blocks haven't changed at all. A tool_use block carries id / name / input; a tool_result block carries tool_use_id / content, plus an optional is_error when the call failed.3 The loop doesn't rewrite what any of those fields mean. It only makes the same set of fields get filled in and sent back, over and over.
stop_reason Is the Loop's while Condition
That line above — "repeat while stop_reason is still tool_use" — translates into code as the test on a while loop. And the one sentence you should carry out of this lesson is this: deciding whether the loop continues or stops comes down to that single field, stop_reason. It has many possible values, but for loop control, telling two of them apart is enough to start:
"tool_use": the model still wants a tool. It hands the request to you and waits for you to execute and send the result back before continuing. The loop turns another round.
"end_turn": the model doesn't want a tool anymore; it thinks it's said what it has to say. The loop ends on its own and you hand the final text to the user.
An open-source roadmap on harness engineering puts this control layer bluntly: what drives a harness is the while loop running model→tools→model.4 And what sits in that while loop's condition expression is stop_reason. Same model, same tool set — how many rounds it turns and when it stops is decided entirely by how the host reads that field and how it writes that condition. Which is why Lesson 1 said "Same model, different harness, completely different result."4
One thing to settle up front, so you don't read the signal backwards: stop_reason: "tool_use" means the model wants to use a tool, not that a tool has been used. The model never executes anything on its own. It emits a structured request; the thing that actually runs the tool is your host code (or Anthropic's servers), and the result only flows back into the conversation afterward.2 So at the instant tool_use shows up in the loop, nothing has happened yet. The action happens in the few lines of your code that read out name and input and go do the work. Treating "I received a tool_use" as "the tool finished running" is the easiest trip-up when moving from a single round-trip to a loop — it makes you misjudge which step the loop is actually on right now.
Written Out, It's Only a Few Lines
Put those four steps and the stop_reason while condition into JavaScript and the skeleton is surprisingly short:
Walk the four steps once more against the code: the while line is "repeat while it's still tool_use"; inside the body, both the assistant response and the user message of tool_result blocks get pushed into messages, and then response gets reassigned. That last assignment is what makes stopping possible at all — drop it, and response.stop_reason keeps its old value forever, so the while loop never exits (that flavor of infinite loop is the star of Lesson 4).
This code runs, but it's a skeleton — simple enough to make the loop itself visible, and nowhere near safe to hand to production. It assumes the model will always come back with end_turn on some turn, assumes every tool executes fine, and assumes it doesn't matter how long the history gets. Those three assumptions are exactly what the next few lessons take apart one at a time.
Look again at those messages.push lines: every turn of the loop stuffs two more messages into messages — the model's assistant response and the tool_result batch you sent back. And the next callModel has to ship the whole of messages again, unchanged. So the longer this loop runs, the more history each request carries, and it only ever grows.
That isn't an oversight in the implementation. It's an inherent property of the loop as a structure: an agent running in a loop generates more and more data that could be relevant for the next turn of inference.5 Three turns of the on-call bot only piles up a restart result plus a chunk of logs. But a task that needs dozens of turns rolls the history into something enormous.
Hidden in here is a problem that only gets opened up in the "Agent Memory and State" course, but that has to be planted now: models have an "attention budget," and every new token introduced depletes that budget by some amount.5 Longer history means more tokens, and as the number of tokens in the context window increases, the model's ability to accurately recall information from that context decreases.5 Note that this is a performance gradient that slopes down gently with length, not a hard cliff you fall off past some threshold5 — don't read it as "over the limit means useless." But the direction is unambiguous: context has to be treated as a finite resource with diminishing marginal returns.5 That thoughtless messages.push in the skeleton loop does nothing about any of this. It assumes history can grow forever — and "Agent Memory and State" is the course that comes back to settle that bill.
A Loop Alone Isn't Enough; Boundaries Have to Be Added
You now have a loop that turns. But "can turn" and "turns safely" are two different things. The skeleton loop hands the stop-or-continue decision entirely to the model: whichever turn it returns end_turn is the turn the loop stops. Yet agents are systems where the model dynamically directs its own process and tool usage.1 That autonomy is precisely where the usefulness comes from, and precisely where the risk lives: autonomy means higher costs and the potential for compounding errors accumulating around turn after turn of the loop.1 The model will potentially operate for many turns, and you have to have some level of trust in its decision-making before you let it run.1
The catch is that trust isn't the same as no supervision. If the model gets stuck on some step, or gets pulled off course by what a tool returned, and simply never comes back with end_turn, a loop that watches nothing but stop_reason will keep spinning alongside it indefinitely. So on top of the model's own finishing signal, you usually add explicit stopping conditions as well — a cap on the maximum number of iterations, for instance, to keep control on your side.1 The bare while (response.stop_reason === "tool_use") in the skeleton has no such fuse: it trusts the model without leaving itself a way out.
That plants both of this lesson's setups: this loop needs boundaries (you can't depend on the model saying end_turn; you need explicit stopping conditions — Lesson 3), and the history this loop produces needs managing (tokens are a finite resource, so you can't just shovel things in — left to the Agent Memory and State course). What a loop actually looks like when it goes off the rails, and how to catch it, is Lesson 4's subject. For this lesson it's enough to get the axle set: how the loop turns, and that stop_reason is what drives it.
Recap
- Connecting one tool-call round-trip into a loop needs no new mechanism, just four repeated steps: send the request → read
stop_reason and the tool_use blocks → run the tools and package the tool_result blocks → append to the history and send again; repeat while stop_reason is still tool_use2
stop_reason is this loop's while condition: tool_use means the model still wants a tool and the loop continues, end_turn means the model is wrapping up and the loop ends on its own — the model→tools→model axle is driven by that field4
tool_use is the signal that the model wants a tool, not a receipt saying one has run; the model never executes anything itself, and the action happens where the host reads out name and input and goes to work2
- Every turn of the loop makes the history longer and never shorter, since an agent in a loop keeps generating more data that could be relevant5; and with a finite attention budget, recall degrades as context grows, so tokens have to be treated as a finite resource with diminishing marginal returns5
- A loop alone isn't enough: autonomy brings higher costs and compounding errors, and the model may operate for many turns1, so on top of the model's own
end_turn you usually add explicit stopping conditions (a maximum number of iterations, say) to keep control on your side1 — how to set those is Lesson 3's subject
>> Lesson 3: Stop Conditions: When an Agent Should Quit