The Agent Loop
Understand the core async message loop that powers Claude Code — how it processes messages, routes tool calls, and decides when to stop.
What You’ll Learn
In this first session, you’ll understand the heart of Claude Code: the agent loop. Every interaction — from a simple “fix this bug” to a complex multi-file refactor — flows through this single loop.
By the end, you’ll know:
- How the message loop processes each turn
- What
stop_reasonrouting means and why it matters - How tool results feed back into the loop
- Why this architecture enables complex multi-step tasks
The Problem
When you type a prompt into Claude Code, something remarkable happens: the AI doesn’t just respond once. It can read files, run commands, edit code, and keep going — all in a single interaction. How?
The naive approach would be a simple request-response:
User → AI → Response (done)
But Claude Code does something fundamentally different. It runs a loop that keeps going until the AI decides it’s done:
User → AI → Tool Call → Result → AI → Tool Call → Result → AI → Response (done)
This is the agent loop.
How It Works
Here’s the core architecture, simplified:
┌─────────────────────────────────────────┐
│ Agent Loop │
│ │
│ ┌──────────┐ │
│ │ Prompt │◄──── User message │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Claude │◄──── System prompt + │
│ │ API │ message history │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ stop_reason? │ │
│ └──┬───────┬───┘ │
│ │ │ │
│ "end_turn" "tool_use" │
│ │ │ │
│ ▼ ▼ │
│ Done ┌──────────┐ │
│ │ Execute │ │
│ │ Tools │ │
│ └────┬─────┘ │
│ │ │
│ │ Append results │
│ │ to messages │
│ │ │
│ └──────► Loop back ◄──────│
│ │
└──────────────────────────────────────────┘
The key routing decision is based on stop_reason:
| stop_reason | Action | What it means |
|---|---|---|
end_turn | Stop the loop | AI is done, return response to user |
tool_use | Execute tools, loop back | AI wants to use tools before responding |
The Message Array
The loop maintains a growing array of messages:
const messages = [
{ role: "user", content: "Fix the bug in auth.ts" },
{ role: "assistant", content: [
{ type: "text", text: "Let me look at the file..." },
{ type: "tool_use", id: "1", name: "Read", input: { file_path: "auth.ts" } }
]},
{ role: "user", content: [
{ type: "tool_result", tool_use_id: "1", content: "// file contents..." }
]},
{ role: "assistant", content: [
{ type: "text", text: "I see the issue..." },
{ type: "tool_use", id: "2", name: "Edit", input: { ... } }
]},
// ... continues until stop_reason === "end_turn"
];
Notice something crucial: tool results are injected as user messages. This is how the AI “sees” the output of its actions. The API has no concept of tools executing — it only sees the message array grow.
Parallel Tool Calls
In a single turn, Claude can request multiple tool calls simultaneously:
{
"role": "assistant",
"content": [
{ "type": "tool_use", "id": "1", "name": "Read", "input": { "file_path": "src/a.ts" } },
{ "type": "tool_use", "id": "2", "name": "Read", "input": { "file_path": "src/b.ts" } },
{ "type": "tool_use", "id": "3", "name": "Read", "input": { "file_path": "src/c.ts" } }
]
}
The agent loop executes all three in parallel (where safe), then sends all results back:
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "1", "content": "..." },
{ "type": "tool_result", "tool_use_id": "2", "content": "..." },
{ "type": "tool_result", "tool_use_id": "3", "content": "..." }
]
}
This is why Claude Code can read multiple files at once — it’s not magic, it’s parallel tool execution within the loop.
Key Insight
The agent loop is not a chat bot. Chat bots process one message and respond. The agent loop is more like an event loop in Node.js — it keeps processing until there’s nothing left to do.
This distinction matters because:
- The AI controls the flow. It decides when to use tools and when to stop. The loop doesn’t impose a fixed number of steps.
- Context accumulates. Each tool result adds to the message array, giving the AI more information for its next decision.
- Errors are recoverable. If a tool fails, the error becomes a tool result. The AI can read the error and try a different approach.
Many simplified implementations use a fixed while True loop with a maximum step count. The real architecture is more nuanced — it uses stop_reason routing, which means the AI itself decides the termination condition.
Hands-On Example
Here’s a minimal agent loop you can experiment with:
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "read_file",
"description": "Read a file's contents",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path to read"}
},
"required": ["path"]
}
}
]
def execute_tool(name, input):
if name == "read_file":
try:
with open(input["path"]) as f:
return f.read()
except Exception as e:
return f"Error: {e}"
def agent_loop(user_message):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=tools,
messages=messages,
)
# Route based on stop_reason
if response.stop_reason == "end_turn":
# Extract text response
return [b.text for b in response.content if b.type == "text"]
# stop_reason == "tool_use" → execute tools
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
})
messages.append({"role": "user", "content": tool_results})
# Usage
result = agent_loop("What's in the README.md file?")
print(result)
Try modifying this to:
- Add a
write_filetool - Add a maximum iteration limit as a safety net
- Log each loop iteration to see the flow
What Changed
| Before (Chat Bot) | After (Agent Loop) |
|---|---|
| One request → one response | One request → many tool calls → one response |
| Fixed interaction pattern | AI controls the flow |
| No tool execution | Tools executed within the loop |
| Context is static | Context grows with each tool result |
| Errors are terminal | Errors are recoverable (AI reads error, tries again) |
Next Session
Now that you understand the loop, Session 2 dives into the Tool System & Permissions — how Claude Code decides which tools to expose, how permission callbacks work, and why some tools require approval while others don’t.