Agentic loops for autonomous task execution
What this covers
- Drive a loop from
stop_reasonrather than from the text of a reply - Feed tool results back into conversation history so the next turn can reason about them
- Continue the loop only on
tool_use, and finish only onend_turn - Give
pause_turn,max_tokens,refusal, and the remaining stop reasons a branch of their own - Read an iteration cap firing as a signal that termination failed, not as a normal ending
- Tell model-driven decisions apart from a pre-wired sequence of tool calls
instrument plate / 1.1
Read the loop from the stop reason
Choose the stop reason Claude returned. The brass path shows what the application does next.
observed / tool_use
Execute the requested client tools.
Append each tool_result, then send the conversation again.
Read the complete diagram as text
- Send the conversation and inspect the returned stop reason.
- On
tool_use, execute client tools, append their results, and send again. - On
pause_turn, append the assistant response and continue the server-side loop. - On
end_turn, treat the turn as normally complete. - Handle truncation, refusal, stop sequences, and context limits with explicit policy.
Key terms
- API
- A defined way for one program to request data or actions from another.
- SDK
- A software development kit: code and tools that help developers use a platform.
- Stop reason
- A structured value explaining why a model response ended.
- Structured output
- Model output constrained to a defined format that software can read reliably.
- Tool call
- A structured request from a model to run a tool with specific input.
The agentic loop is the smallest complete unit of agent design, and almost every mistake in it comes from asking the wrong question about when to stop.
How the loop works
Send a request and inspect stop_reason.
tool_use— run the client tools the model requested, append their results to the conversation, and send again.end_turn— normal completion.
Continue on the first, finish on the second, and infer neither of them from the text of the reply. That's the loop this task statement asks you to build.
Written out, with illustrative helper names, the whole loop is one branch:
while (true) {
const response = await send(conversation);
conversation.append(response);
if (response.stop_reason !== 'tool_use') break;
// Results go back as tool_result blocks, or the next turn cannot see them.
conversation.append(await runRequestedTools(response));
}while True:
response = send(conversation)
conversation.append(response)
if response.stop_reason != "tool_use":
break
# Results go back as tool_result blocks, or the next turn cannot see them.
conversation.append(run_requested_tools(response))Notice what's absent? Nothing reads the assistant's text, and nothing counts iterations. The loop turns on stop_reason and on that alone.
The important part is that control follows a structural signal, not a semantic guess. stop_reason has defined values. AI generated text is ambiguous, probabilistic, and shouldn't be relied upon for control flow.
A dryer tells you two things: the sound it makes and its status light. You can guess from the sound or read the light. Unlike a dryer, though, several of these defined API states mean something other than finished.
Handle every other stop reason
Beyond the task statement. Task 1.1 is scoped to tool_use and end_turn, and nothing in this section is needed to satisfy it. It's here because it is what keeps a real loop from hanging or truncating in silence.
pause_turn— the server-side sampling loop hit its per-request iteration limit while running a server tool such as web search. Append the assistant response and send again so that loop can finish.max_tokens— the reply reached the limit you set. It is cut off, not complete.model_context_window_exceeded— the response filled the model's context window. Treat what came back as truncated.refusal— Claude declined to answer. This arrives as an ordinary successful response rather than an error, so code that only checks for transport failures walks straight past it.stop_sequence— one of your own configured stop sequences fired.
One thing here does carry back into the required part. A loop that treats everything that is not end_turn as "keep going" will spin on pause_turn and loop on a refusal. Only tool_use means run tools and continue; end_turn means finish, and every remaining reason needs a branch of its own, even when that branch is to fail loudly.
Every value, each with somewhere to go:
switch (response.stop_reason) {
case 'tool_use': return runToolsAndContinue(response);
case 'end_turn': return finish(response);
case 'pause_turn': return appendAndContinue(response);
case 'max_tokens':
case 'model_context_window_exceeded': return handleTruncated(response);
case 'stop_sequence': return handleOwnStopSequence(response);
case 'refusal': return handleRefusal(response);
}match response.stop_reason:
case "tool_use": return run_tools_and_continue(response)
case "end_turn": return finish(response)
case "pause_turn": return append_and_continue(response)
case "max_tokens" | "model_context_window_exceeded":
return handle_truncated(response)
case "stop_sequence": return handle_own_stop_sequence(response)
case "refusal": return handle_refusal(response)Compare that with a loop written as "continue unless the reason is end_turn". Five of these six non-terminal values would take the branch meant for one of them.
Why tool results go back into history
A tool result that is not appended to the conversation is a result the model cannot use. The next turn needs that result to choose its next action. Without it, the model cannot adjust its plan based on what the tool found.
Model-driven versus pre-configured
A decision tree encodes what you already know. A model-driven loop lets Claude choose the next tool from context you could not have enumerated in advance.
Neither is universally better, so the question is always which one fits. Fixed sequences win where the path is known and compliance matters. Model-driven selection wins where the path depends on what the earlier steps turn up.
Common mistakes
- Parsing natural language to decide termination. Looking for "I'm done" or similar. Brittle, and it fails silently.
- An iteration cap as the primary stop. A cap is a circuit breaker. If it is what normally ends your loop, the loop has no real termination condition.
- Treating the presence of assistant text as completion. The model can emit text and request a tool in the same turn.
Claude Agent SDKAnthropic APIstop_reasontool_use
Field note — common misconceptions
- MythThat an iteration cap is the stopping mechanism rather than a safety net
- ActuallyA cap is a circuit breaker; a loop that normally ends there has no termination condition.
- MythThat reading the assistant's prose tells you the task is finished
- ActuallyThe model can emit text and request a tool in the same turn, so read
stop_reasoninstead. - MythThat a fixed tool order is equivalent to letting the model choose
- ActuallyA fixed sequence encodes a path chosen in advance; model-driven selection decides it from context.
Guided review
Review this lesson as a study deck
Review the lesson's main ideas in five guided slides, then test yourself with three flashcards.
Open Task 1.1 study deckCross-domain reasoning
Connect this idea
Reliability connects the whole workflow
A reliable workflow must know when to stop, enforce required steps, return useful errors, validate results, and keep failures visible across agent handoffs.
Applied practice
Practice this lesson in a lab
Use a related lab to create a decision, implementation or diagram, evidence record, and review.
- Lab 1 · Manual tool-use loop
Build the application-controlled loop behind a small note assistant, then make every stop state visible.