All articles
agentic systems · intermediate ·

Inside the Agent SDK loop: how Claude Code runs your prompts

A tour through the execution loop that powers every agent — the five-step cycle, message types, tool execution, and the control surfaces that keep agents predictable.

agent-sdkarchitectureclaude-codecoding-agents

Overview

The Agent SDK wraps Claude Code’s execution loop into a programmable interface. When query() is called with a prompt, the SDK does not make a single API request. It enters a loop — Claude evaluates the prompt, calls tools, reads results, and repeats until the task is done. The SDK yields a stream of typed messages at each stage so the application can observe progress, intercept tool calls, and handle the final result.

Understanding the loop matters for three reasons. First, every agent behavior — tool selection, parallel execution, context compaction — is a product of this cycle, not magic. Second, the control surfaces (max turns, budget, effort, permissions) directly shape how the loop runs. Third, debugging an agent that stalls, loops, or drains context is easier when the loop’s internals are visible.

1
Receive prompt
SDK sends system prompt, tool definitions, and user prompt to Claude
2
Evaluate
Claude processes context and decides: text response or tool calls?
3
Execute tools
SDK runs requested tools, feeds results back to Claude
4
Repeat
Cycle continues until Claude responds without tool calls
5
Return result
SDK yields ResultMessage with output, cost, and session ID

The loop cycle

Every agent session follows the same five-step cycle. The SDK sits between the user and Claude, forwarding prompts, executing tools, and collecting results. Claude never talks to tools directly — the SDK mediates every interaction.

Loading diagram…

The loop has a branching point. When Claude responds with tool calls (row 3), the SDK executes them and feeds results back (rows 4–6). The cycle repeats — Claude may call more tools or produce a final answer. When Claude responds with text only and no tool calls (row 7), the loop ends and the ResultMessage fires (row 8).

A quick question like “what files are here?” might take one turn: Claude calls Glob and responds with the listing. A complex task like “refactor the auth module and update the tests” can chain dozens of tool calls across many turns — reading files, editing code, running tests — with Claude adjusting its approach based on each result.


Inside a turn

A turn is one round trip inside the loop: Claude produces output, the SDK executes any tools, and the results feed back. This happens without yielding control back to the calling code. Turns repeat until Claude produces output with no tool calls.

The SDK yields five message types as the loop runs. Each type marks a specific lifecycle stage.

Loading diagram…

SystemMessage fires at session start (subtype "init", contains session ID and metadata) and after compaction (subtype "compact_boundary"). AssistantMessage fires after each Claude response — it carries text content blocks and tool call blocks from that turn. UserMessage fires after tool execution with the results sent back to Claude. ResultMessage marks the end of the loop with final text, token usage, cost, and session ID. StreamEvent is optional — it carries raw API streaming deltas when partial messages are enabled.

Consider the prompt “Fix the failing tests in auth.ts” across four turns:

  1. Claude calls Bash to run npm test — the SDK yields an AssistantMessage, executes the command, then yields a UserMessage with the output (three failures).
  2. Claude calls Read on auth.ts and auth.test.ts — the SDK returns the file contents.
  3. Claude calls Edit to fix the code, then Bash to re-run tests — all three pass.
  4. Claude produces a text-only response: “Fixed the auth bug, all three tests pass now.” The SDK yields the final AssistantMessage, then a ResultMessage.

That was four turns: three with tool calls, one final text-only response.


Tool execution

Tools turn Claude from a text generator into an agent that reads files, runs commands, searches code, and spawns subagents. The SDK includes the same tools that power Claude Code: Read, Write, Edit for file operations; Glob, Grep for search; Bash for execution; WebSearch, WebFetch for web access; and Agent, Skill, AskUserQuestion, TodoWrite for orchestration.

Tools can be extended with MCP servers, custom tool handlers, and project-level skills loaded via settingSources.

Permissions

Three options work together to control what runs. allowed_tools auto-approves listed tools — a read-only agent with ["Read", "Glob", "Grep"] runs those without prompting. disallowed_tools blocks listed tools regardless of other settings. permission_mode controls what happens to everything else: "default" triggers an approval callback, "acceptEdits" auto-approves file edits and common filesystem commands, "plan" allows only read-only exploration, and "bypassPermissions" runs everything without asking (for CI and containers).

When a tool is denied, Claude receives a rejection message and typically attempts a different approach.

Parallel execution

When Claude requests multiple tool calls in a single turn, read-only tools (Read, Glob, Grep, MCP tools marked read-only) run concurrently. Tools that modify state (Edit, Write, Bash) run sequentially to avoid conflicts. Custom tools default to sequential; set readOnlyHint in their annotations to enable parallel execution.


Control surfaces

Four parameters shape how the loop runs. All are fields on ClaudeAgentOptions (Python) or Options (TypeScript).

Loop control parameters
ParameterWhat it controlsDefault
max_turns / maxTurnsMaximum tool-use round trips before stoppingNo limit
max_budget_usd / maxBudgetUsdMaximum cost before stoppingNo limit
effortReasoning depth per turn ("low" to "max")"high" (TS), unset (Python)
modelWhich Claude model to useSDK default

When a limit is hit, the ResultMessage carries an error subtype (error_max_turns or error_max_budget_usd). Without limits, the loop runs until Claude finishes on its own — fine for well-scoped tasks but potentially expensive for open-ended prompts. Setting a budget is a good default for production agents.

The effort parameter trades latency and token cost for reasoning depth within each response. Use "low" for routine tasks like file lookups and directory listings. Use "high" or "xhigh" for refactors and debugging. Effort is independent of extended thinking — the two can be combined or used separately.


The context window

The context window does not reset between turns. Everything accumulates: system prompt, tool definitions, conversation history, tool inputs, and tool outputs. Content that stays the same across turns (system prompt, CLAUDE.md, tool schemas) is automatically prompt-cached, reducing cost for repeated prefixes.

Large tool outputs are the fastest way to fill context. Reading a big file or running a verbose command can consume thousands of tokens in a single turn.

Automatic compaction

When the context window approaches its limit, the SDK automatically compacts the conversation — it summarizes older history to free space while keeping recent exchanges and key decisions intact. A SystemMessage with subtype "compact_boundary" fires when this happens.

Compaction replaces older messages with a summary, so specific instructions from early in the conversation may not survive. Persistent rules belong in CLAUDE.md (loaded via settingSources), which is re-injected on every request rather than carried through history.

Keeping context lean

Three strategies for long-running agents. Use subagents for subtasks — each starts with a fresh context, and only the summary returns to the parent. Scope subagent tools to the minimum set they need with the tools field on AgentDefinition. Use ToolSearch for MCP servers to load tools on demand instead of preloading all schemas.


Hooks as interception points

Hooks fire at specific points in the loop without consuming context. They run in the application process, not inside Claude’s context window.

Key hook events
HookWhen it firesCommon uses
PreToolUseBefore a tool executesValidate inputs, block dangerous commands
PostToolUseAfter a tool returnsAudit outputs, trigger side effects
UserPromptSubmitWhen a prompt is sentInject additional context
StopWhen the agent finishesValidate result, save session state
PreCompactBefore context compactionArchive full transcript

A PreToolUse hook that rejects a tool call prevents execution entirely — Claude receives the rejection as the tool result and adapts. Hooks can short-circuit the loop at every critical junction.


Handling the result

When the loop ends, the ResultMessage’s subtype field is the primary way to check termination state.

Result subtypes
SubtypeMeaningresult field
successTask completed normallyYes
error_max_turnsHit turn limit before finishingNo
error_max_budget_usdHit budget limit before finishingNo
error_during_executionAPI failure or cancelled requestNo
error_max_structured_output_retriesStructured output validation failedNo

Always check the subtype before reading result. All subtypes carry total_cost_usd, usage, num_turns, and session_id for cost tracking and resumption. The stop_reason field indicates why the model stopped on its final turn — end_turn (normal), max_tokens (output limit), or refusal (declined request).

The session ID captured from ResultMessage enables resuming the full context later or forking into a different approach.


Takeaways

The loop is the engine

Every agent behavior — tool selection, parallel execution, context management — is a product of the five-step evaluate-execute-repeat cycle, not a black box.

Messages are the observability surface

Five typed messages (System, Assistant, User, StreamEvent, Result) give a structured window into what the agent is doing at each lifecycle stage.

Turns accumulate context

Context does not reset between turns. Long sessions with many tool calls build up history quickly; subagents and tool scoping keep the main thread lean.

Control surfaces prevent runaway agents

max_turns, max_budget_usd, and effort give hard stops. Without them, open-ended prompts can consume unbounded tokens.

Hooks are the interception layer

PreToolUse, PostToolUse, Stop, and PreCompact hooks fire outside the context window and can short-circuit the loop at every critical junction.