All articles
agentic systems·intermediate··Updated

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

How Claude Code and the Agent SDK execute prompts through a five-step tool loop, expose typed lifecycle messages, and bound work with permissions, turns, and budgets.

agent-sdkarchitectureclaude-codecoding-agents

The Agent SDK does not turn query() into one model call. It embeds Claude Code’s evaluate-act-observe loop: Claude requests tools, the SDK executes them, results return as user messages, and the cycle stops when Claude produces a response without tool calls.

That loop explains tool selection, parallel execution, context compaction, and termination. Its typed message stream exposes progress while permissions, turn limits, budgets, and hooks constrain execution.

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 SDK 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 core message categories as the loop runs. Each category marks a specific lifecycle stage.

Loading diagram…

SystemMessage marks session lifecycle events such as "init" and "compact_boundary". In TypeScript, every system subtype except "init" is its own type in the SDKMessage union. AssistantMessage carries each Claude response, including text and tool calls. UserMessage carries tool results and streamed user input. StreamEvent is optional and carries raw API deltas when partial messages are enabled. ResultMessage reports final text, usage, cost, session ID, and termination subtype; a few trailing system events can follow it, so consumers should iterate the stream to completion.

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, Edit, and Write for files; Glob and Grep for search; Bash for execution; WebSearch and WebFetch for web access; ToolSearch for on-demand discovery; and Agent, Skill, AskUserQuestion, TaskCreate, and TaskUpdate for orchestration. Models without task tracking expose TaskCreate and TaskUpdate only when the application opts in.

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 / allowedTools auto-approves listed tools. disallowed_tools / disallowedTools blocks listed tools regardless of other settings. permission_mode / permissionMode handles everything else: "default" calls canUseTool and denies when no callback exists; "acceptEdits" auto-approves edits and common filesystem commands; "plan" keeps source edits behind approval; "dontAsk" runs only pre-approved tools; "auto" uses a model classifier; and "bypassPermissions" removes ordinary prompts. TypeScript also requires allowDangerouslySkipPermissions: true for bypass mode, which cannot run as root on Unix.

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
Parameter What it controls Default
max_turns / maxTurns Maximum tool-use round trips before stopping No limit
max_budget_usd / maxBudgetUsd Maximum cost before stopping No limit
effort Reasoning depth per turn ("low" to "max") Unset; model default
model Which Claude model to use SDK 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. The current levels are "low", "medium", "high", "xhigh", and "max"; support depends on the model. Both SDKs leave it unset unless the application chooses a level. Effort and extended thinking are independent.


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 "compact_boundary" system event fires after compaction.

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

Long-running agents keep the main context lean in three ways. 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

Hook callbacks run in the application process rather than as model turns. A hook can still inject content into Claude’s context, so callback execution and callback output are separate concerns.

Key hook events
Hook When it fires Common uses
PreToolUse Before a tool executes Validate inputs, block dangerous commands
PostToolUse After a tool returns Audit outputs, trigger side effects
UserPromptSubmit When a prompt is sent Inject additional context
Stop When the agent finishes Validate result, save session state
PreCompact Before context compaction Archive full transcript

A PreToolUse hook that rejects a tool call prevents execution — Claude receives the rejection as the tool result and adapts. Other events expose prompt submission, tool completion, compaction, and stop boundaries without moving that policy into the prompt.


Handling the result

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

Result subtypes
Subtype Meaning result field
success Task completed normally Yes
error_max_turns Hit turn limit before finishing No
error_max_budget_usd Hit budget limit before finishing No
error_during_execution API failure or canceled request No
error_max_structured_output_retries Structured output validation failed No

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

Tool selection, parallel execution, and context management all emerge from the five-step evaluate-execute-repeat cycle.

Messages are the observability surface

Five core message categories (System, Assistant, User, StreamEvent, Result) expose each lifecycle stage while observability events cover additional status.

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 and max_budget_usd are hard stops; effort tunes reasoning depth. Without explicit limits, open-ended prompts can run long and consume more tokens.

Hooks are the interception layer

PreToolUse, PostToolUse, Stop, and PreCompact callbacks run in the application process, where they can enforce policy without hiding it in the prompt.