---
title: "How the Agent Loop Works"
aliases: [agent-loop, sdk-agent-loop, message-lifecycle]
tags: [agent-sdk, agent-loop, messages, tools, context-window, sessions]
sources: [raw/How the agent loop works.md]
created: 2026-04-17
updated: 2026-04-17
---

## Overview

The Agent SDK embeds Claude Code's autonomous execution loop in your application. When you call `query()`, Claude evaluates the prompt, calls tools, receives results, and repeats — until the task is done or a limit is hit.

## The Loop Cycle

1. **Receive prompt** — Claude gets prompt + system prompt + tool definitions + conversation history. SDK yields `SystemMessage(subtype="init")`.
2. **Evaluate and respond** — Claude decides: text, tool calls, or both. SDK yields `AssistantMessage`.
3. **Execute tools** — SDK runs each requested tool and returns results. Hooks can intercept before execution.
4. **Repeat** — Steps 2–3 form one *turn*. Loop continues until Claude produces a response with **no tool calls**.
5. **Return result** — SDK yields final `AssistantMessage` (text only) + `ResultMessage` (text, cost, token usage, session ID).

## Turns

- A **turn** = one round trip: Claude outputs tool calls → SDK executes → results fed back to Claude.
- Turns happen without yielding control to your code.
- The loop ends when Claude responds with no tool calls.
- Cap turns with `max_turns` / `maxTurns` (counts tool-use turns only).
- Cap cost with `max_budget_usd` / `maxBudgetUsd`.

## Message Types

| Type | When emitted | Key content |
|------|-------------|-------------|
| `SystemMessage` | Session start + after compaction | `subtype="init"` or `"compact_boundary"` |
| `AssistantMessage` | After each Claude response | Text blocks + tool call blocks |
| `UserMessage` | After each tool execution | Tool result content |
| `StreamEvent` | Only with partial messages enabled | Raw API streaming deltas |
| `ResultMessage` | End of loop | Final text, cost, usage, session ID, `subtype` |

**Handling patterns:**
- Final result only → handle `ResultMessage`
- Progress updates → handle `AssistantMessage`
- Live streaming → enable `include_partial_messages` / `includePartialMessages`

**Python:** `isinstance(message, ResultMessage)`  
**TypeScript:** `message.type === "result"` — note content is at `message.message.content`, not `message.content`

## Tool Execution

### Built-in Tools

| Category | Tools |
|----------|-------|
| File ops | `Read`, `Edit`, `Write` |
| Search | `Glob`, `Grep` |
| Execution | `Bash` |
| Web | `WebSearch`, `WebFetch` |
| Discovery | `ToolSearch` (load tools on-demand) |
| Orchestration | `Agent`, `Skill`, `AskUserQuestion`, `TodoWrite` |

Extend with MCP servers, custom tool handlers, or skill setting sources.

### Parallel Execution

- Read-only tools (`Read`, `Glob`, `Grep`, read-only MCP tools) → run **concurrently**
- State-mutating tools (`Edit`, `Write`, `Bash`) → run **sequentially**
- Custom tools → sequential by default; opt into parallel via `readOnly` / `readOnlyHint` annotation

### Tool Permissions

- `allowed_tools` / `allowedTools` — auto-approve listed tools
- `disallowed_tools` / `disallowedTools` — hard block regardless of other settings
- Scope individual tools: `"Bash(npm *)"` allows only npm commands

When denied, Claude receives a rejection message and adjusts approach.

## Control Options

### Permission Modes

| Mode | Behavior |
|------|----------|
| `"default"` | Uncovered tools trigger approval callback; no callback = deny |
| `"acceptEdits"` | Auto-approves file edits + `mkdir`, `touch`, `mv`, `cp`; other Bash follows default |
| `"plan"` | No tool execution; Claude produces a plan only |
| `"dontAsk"` | Never prompts; pre-approved tools run, rest denied |
| `"auto"` (TS only) | Model classifier approves/denies each tool call |
| `"bypassPermissions"` | All allowed tools run without asking; not allowed as root on Unix |

### Effort Levels

| Level | Good for |
|-------|----------|
| `"low"` | File lookups, listing directories |
| `"medium"` | Routine edits, standard tasks |
| `"high"` | Refactors, debugging (TypeScript SDK default) |
| `"xhigh"` | Coding + agentic tasks; recommended on Opus 4.7 |
| `"max"` | Multi-step deep analysis |

- Python SDK: unset by default (defers to model default)
- `effort` is independent of Extended Thinking — they can be combined freely

## Context Window

Context **accumulates across all turns** within a session — it does not reset.

| Source | Impact |
|--------|--------|
| System prompt | Small, fixed; always present |
| CLAUDE.md | Full content every request (prompt-cached after first) |
| Tool definitions | Each tool adds its schema |
| Conversation history | Grows with every turn |
| Skill descriptions | Short summaries; full content only on invocation |

Large tool outputs (big files, verbose commands) consume significant context fast.

### Automatic Compaction

When context approaches its limit, the SDK summarizes older history. A `compact_boundary` message is emitted. Instructions from early prompts may be lost — **put persistent rules in CLAUDE.md**, not in the initial prompt.

Customize compaction:
- **CLAUDE.md section** — tell the compactor what to preserve (free-form header, matched by intent)
- **`PreCompact` hook** — archive full transcript before compaction
- **Manual** — send `/compact` as a prompt string

### Context Efficiency Tips

- Use subagents for subtasks — each starts with a fresh context; only final result returns to parent
- Scope subagent tool lists to the minimum needed
- Use `ToolSearch` to load MCP tools on-demand instead of preloading all
- Set `effort: "low"` for routine read-only tasks

## Sessions and Continuity

- Capture `ResultMessage.session_id` to resume a session later
- Resuming restores the full context: files read, analysis performed, actions taken
- Sessions can be forked to branch into a different approach
- Python `ClaudeSDKClient` manages session IDs automatically across calls

## Result Subtypes

| Subtype | Meaning | `result` field? |
|---------|---------|----------------|
| `success` | Task completed normally | Yes |
| `error_max_turns` | Hit `maxTurns` limit | No |
| `error_max_budget_usd` | Hit `maxBudgetUsd` limit | No |
| `error_during_execution` | API failure or cancellation | No |
| `error_max_structured_output_retries` | Structured output validation failed | No |

Always check `subtype` before reading `result`. All subtypes carry `total_cost_usd`, `usage`, `num_turns`, `session_id`.

`stop_reason` values: `end_turn`, `max_tokens`, `refusal` — check to detect model refusals.

## Hooks

Hooks run in your application process (not inside the agent context window — no context cost).

| Hook | When | Common use |
|------|------|-----------|
| `PreToolUse` | Before tool executes | Validate inputs, block dangerous commands |
| `PostToolUse` | After tool returns | Audit outputs, trigger side effects |
| `UserPromptSubmit` | When prompt is sent | Inject context |
| `Stop` | When agent finishes | Validate result, save state |
| `SubagentStart/Stop` | Subagent spawns/completes | Track parallel tasks |
| `PreCompact` | Before compaction | Archive transcript |

A `PreToolUse` hook that rejects a call prevents execution; Claude receives the rejection and adjusts.

See [[wiki/agent-sdk/hooks-guide|Hooks Guide]] for full event list and callback API.

## Key Takeaways

- The agent loop = evaluate → tool calls → results → repeat, until no tool calls remain
- A **turn** is one round trip; `max_turns` counts tool-use turns only (not the final text-only response)
- `ResultMessage` always arrives last — always iterate the stream to completion, never break early
- Context accumulates across turns; use subagents, ToolSearch, and CLAUDE.md to keep it efficient
- Automatic compaction preserves recent history but can lose early instructions — put rules in CLAUDE.md
- `permission_mode` + `allowed_tools` + `disallowed_tools` together determine what actually runs
- `effort` controls reasoning depth per turn; lower effort = lower cost for simple tasks
- Session IDs enable resumption, continuation, and forking of any session

## Related Articles

- [[wiki/agent-sdk/overview|Agent SDK Overview]]
- [[wiki/agent-sdk/hooks-guide|Hooks Guide]]
- [[wiki/agent-sdk/configure-permissions|Configure Permissions]]
- [[wiki/agent-sdk/mcp-integration|MCP Integration]]
- [[wiki/agent-sdk/hosting-production|Hosting & Production]]
- [[wiki/agent-sdk/python-api-reference|Python API Reference]]
- [[wiki/agent-sdk/typescript-api-reference|TypeScript API Reference]]

## Sources

- `raw/How the agent loop works.md` — sourced from https://code.claude.com/docs/en/agent-sdk/agent-loop
