Agent Loop
The tool-use loop
The core agent cycle in which the model calls a tool, the result is fed back, and these turns repeat until a stop condition is met.
At the heart of every agent is a simple loop:
1. The harness sends the conversation history and tool definitions to
the model.
2. The model either writes a final answer or emits one (or several)
tool calls.
3. The harness runs the tool and appends the result to the history as
a tool_result.
4. Go back to step 1.
In the Anthropic API you read this from stop_reason: tool_use means
the model is waiting on a tool; end_turn means it's done. Claude
Code's docs describe the same loop as three phases: gather context,
take action, verify results — repeated as many times as needed.
The academic root of the idea is ReAct (Yao et al., 2022, "ReAct:
Synergizing Reasoning and Acting in Language Models"): the model
interleaves reasoning steps and actions, and each action's observation
feeds the next thought. Today's APIs have made this native: thinking
blocks and tool_use blocks arrive in the same response.
A loop may never stop on its own, so stop conditions are mandatory: the model finishing, a maximum turn count, a token/cost/time budget, an unrecoverable error, or the user interrupting. Remember the API is stateless: the full history is resent every turn, so each call gets a little bigger as turns pile up.
Like feeling your way to a light switch in a dark room. You take a step (act), touch the wall (observe), think "switches are usually next to the door" (reason), and take the next step accordingly. Once you find it, you stop.
But a sensible person also sets a rule: "if I haven't found it in ten steps, I'll turn on my phone's flashlight." That rule is the loop's max turns limit.
User: "Fix the tests." The loop runs like this:
- Turn 1: The model asks for bash("npm test") → the harness runs
it → result: 3 tests failing.
- Turn 2: The model asks for read("src/auth.ts") → file contents
come back.
- Turn 3: The model calls edit(...) to fix the bug → "ok".
- Turn 4: The model runs bash("npm test") again → all green.
- Turn 5: The model asks for no tools and writes "Fixed 3 tests,
the cause was…". stop_reason = end_turn → the loop ends.
Had the model gone down the wrong path and called the same tool for 25 turns, the harness's turn cap would have cut the loop off.
import anthropic
client = anthropic.Anthropic()
MAX_TURNS = 20
tools = [{
"name": "run_command",
"description": "Runs a shell command in the project folder and returns its output",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
}]
messages = [{"role": "user", "content": "Run the tests and fix them"}]
for turn in range(MAX_TURNS):
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
tools=tools,
messages=messages,
)
# Append the whole response (text + tool_use blocks)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break # end_turn, max_tokens, refusal… → leave the loop
results = []
for block in response.content:
if block.type == "tool_use":
try:
output = run_tool(block.name, block.input) # your code
results.append({"type": "tool_result",
"tool_use_id": block.id,
"content": output})
except Exception as err:
results.append({"type": "tool_result",
"tool_use_id": block.id,
"content": str(err),
"is_error": True})
# All results go back in a single user message
messages.append({"role": "user", "content": results})
else:
print("Turn cap reached, loop stopped")from agents import Agent, Runner, function_tool, MaxTurnsExceeded
@function_tool
def get_order_status(order_id: str) -> str:
"""Return the shipping status for an order number."""
return lookup_order(order_id) # your code
agent = Agent(
name="Support",
instructions="Answer order questions using the tools.",
tools=[get_order_status],
)
try:
# max_turns defaults to 10; exceeding it raises
result = Runner.run_sync(agent, "Where is TR-9921?", max_turns=6)
print(result.final_output)
except MaxTurnsExceeded:
print("The agent couldn't finish in 6 turns")- Tasks where the number of steps isn't known up front — debugging, research, code changes
- When each step's result decides the next one (observe → decide → act)
- When the model must verify its own output — run tests, read results, try again
- When writing your own harness — this loop sits under every agent framework
- Jobs that finish with a single tool call — one turn is enough, don't build a loop
- Fixed-step workflows — sequential, code-driven calls are more predictable
- UIs with strict latency limits — every turn is another model call and another wait
No stop condition
If the model can't reach an answer it may call the same tool again and again. Always set a turn cap and, if possible, a token, cost or time budget; when the cap is hit, tell the user clearly what happened.
Missing or scattered tool_results
Every tool_use block the model emits needs a matching tool_result; otherwise the API rejects the request. Return the results of parallel calls together in a single user message.
Crashing the loop on a tool error
When a tool fails, don't swallow the exception and end the loop — send the error back to the model with is_error: true. The model will often try another route.
Appending only the text to history
If you keep only the text from a response, the tool_use and thinking blocks are lost and the next turn breaks. Append the whole response.content.