The loop at a glance
Every agent session follows the same cycle:- Receive prompt. Claude receives your prompt, along with the system prompt, tool definitions, and conversation history. The SDK yields a
SystemMessagewith subtype"init"containing session metadata. - Evaluate and respond. Claude evaluates the current state and determines how to proceed. It may respond with text, request one or more tool calls, or both. The SDK yields one or more
AssistantMessageobjects, one for each content block, such as a text block or a tool call request. - Execute tools. The SDK runs each requested tool and collects the results. Each set of tool results feeds back to Claude for the next decision. You can use hooks to intercept, modify, or block tool calls before they run.
- Repeat. Steps 2 and 3 repeat as a cycle. Each full cycle is one turn. Claude continues calling tools and processing results until it produces a response with no tool calls.
- Return result. The SDK yields a final
AssistantMessagewith the text response (no tool calls), followed by aResultMessagewith the final text, token usage, cost, and session ID.
Glob and responding with the results. A complex task (“refactor the auth module and update the tests”) can chain dozens of tool calls across many turns, reading files, editing code, and running tests, with Claude adjusting its approach based on each result.
Turns and messages
A turn is one round trip inside the loop: Claude produces output that includes tool calls, the SDK executes those tools, and the results feed back to Claude automatically. This happens without yielding control back to your code. Turns continue until Claude produces output with no tool calls, at which point the loop ends and the final result is delivered. Consider what a full session might look like for the prompt “Fix the failing tests in auth.ts”. First, the SDK sends your prompt to Claude and yields aSystemMessage with the session metadata. Then the loop begins:
- Turn 1: Claude calls
Bashto runnpm test. The SDK yields anAssistantMessagewith the tool call, executes the command, then yields aUserMessagewith the output (three failures). - Turn 2: Claude calls
Readonauth.tsandauth.test.ts. The SDK yields anAssistantMessagefor each call and returns the file contents. - Turn 3: Claude calls
Editto fixauth.ts, then callsBashto re-runnpm test. All three tests pass. The SDK yields anAssistantMessagefor each call. - Final turn: Claude produces a text-only response with no tool calls: “Fixed the auth bug, all three tests pass now.” The SDK yields a final
AssistantMessagewith this text, then aResultMessagewith the same text plus cost and usage.
max_turns / maxTurns, which counts tool-use turns only. For example, max_turns=2 in the loop above would have stopped before the edit step. You can also use max_budget_usd / maxBudgetUsd to cap turns based on a spend threshold.
Without limits, the loop runs until Claude finishes on its own, which is fine for well-scoped tasks but can run long on open-ended prompts (“improve this codebase”). Setting a budget is a good default for production agents. See Turns and budget below for the option reference.
Message types
As the loop runs, the SDK yields a stream of messages. Each message carries a type that tells you what stage of the loop it came from. The five core types are:-
SystemMessage: session lifecycle events. Thesubtypefield distinguishes them:"init": session metadata for the run. When aSessionStartorSetuphook runs during session startup, its hook lifecycle messages arrive before theinitmessage"compact_boundary": fires after compaction"informational": plain-text status banners from the loop"worker_shutting_down": the host is exiting or Remote Control disconnected
"init"is its own type in theSDKMessageunion rather than a subtype ofSDKSystemMessage. -
AssistantMessage: emitted for each content block in Claude’s responses, including the final text-only one. Each carries a single content block, such as text or a tool call, and the messages from one response share a message ID. -
UserMessage: emitted after each tool execution with the tool result content sent back to Claude. Also emitted for any user inputs you stream mid-loop. -
StreamEvent: only emitted when partial messages are enabled. Contains raw API streaming events (text deltas, tool input chunks). See Stream responses. -
ResultMessage: marks the end of the agent loop. Contains the final text result, token usage, cost, and session ID. Check thesubtypefield to determine whether the task succeeded or hit a limit. A small number of trailing system events, such asprompt_suggestion, can arrive after it, so iterate the stream to completion rather than breaking on the result. See Handle the result.
Handle messages
Which messages you handle depends on what you’re building:- Final results only: handle
ResultMessageto get the output, cost, and whether the task succeeded or hit a limit. - Progress updates: handle
AssistantMessageto see what Claude is doing each turn, including which tools it called. - Live streaming: enable partial messages (
include_partial_messagesin Python,includePartialMessagesin TypeScript) to getStreamEventmessages in real time. See Stream responses in real-time.
- Python: check message types with
isinstance()against classes imported fromclaude_agent_sdk(for example,isinstance(message, ResultMessage)). - TypeScript: check the
typestring field (for example,message.type === "result").AssistantMessageandUserMessagewrap the raw API message in a.messagefield, so content blocks are atmessage.message.content, notmessage.content.
Example: Check message types and handle results
Example: Check message types and handle results
Tool execution
Tools give your agent the ability to take action. Without tools, Claude can only respond with text. With tools, Claude can read files, run commands, search code, and interact with external services.Built-in tools
The SDK includes the same tools that power Claude Code:
On the models that don’t get the task-tracking tools, Claude Code provides
TaskCreate and TaskUpdate only when you opt in.
Beyond built-in tools, you can:
- Connect external services with MCP servers (databases, browsers, APIs)
- Define custom tools with custom tool handlers
- Load project skills via setting sources for reusable workflows
Tool permissions
Claude determines which tools to call based on the task, but you control whether those calls are allowed to execute. You can auto-approve specific tools, block others entirely, or require approval for everything. Three options work together to determine what runs:allowed_tools/allowedToolsauto-approves listed tools. A read-only agent with["Read", "Glob", "Grep"]in its allowed tools list runs those tools without prompting. Tools not listed are still available, and calls to them that need approval fall through to the permission mode andcanUseTool.disallowed_tools/disallowedToolsblocks listed tools, regardless of other settings. See Permissions for the order that rules are checked before a tool runs.permission_mode/permissionModecontrols how much human oversight you want. The SDK evaluates the active mode together with your allow and deny rules in a fixed order, described in How permissions are evaluated. See Permission mode for available modes.
"Bash(npm *)" to allow only specific commands. See Permissions for the full rule syntax.
When a tool is denied, Claude receives a rejection message as the tool result and typically attempts a different approach or reports that it couldn’t proceed.
Parallel tool execution
When Claude requests multiple tool calls in a single turn, both SDKs can run them concurrently or sequentially depending on the tool. Read-only tools (likeRead, Glob, Grep, and MCP tools marked as read-only) can run concurrently. Tools that modify state (like Edit, Write, and Bash) run sequentially to avoid conflicts.
Custom tools default to sequential execution. To enable parallel execution for a custom tool, set readOnlyHint in its annotations. Both the TypeScript and Python SDKs use this field name from the MCP SDK.
Control how the loop runs
You can limit how many turns the loop takes, how much it costs, how deeply Claude reasons, and whether tools require approval before running. All of these are fields onClaudeAgentOptions (Python) / Options (TypeScript).
Turns and budget
When either limit is hit, the SDK returns a
ResultMessage with a corresponding error subtype (error_max_turns or error_max_budget_usd). See Handle the result for how to check these subtypes and ClaudeAgentOptions / Options for syntax.
The budget cap covers subagents: their spend counts toward the total. Once spend reaches the cap, spawning another subagent fails with Budget limit reached, and Claude Code stops any background subagents still running. The cap-enforcement behaviors require Claude Code v2.1.217 or later.
With streaming input, a message that is still queued when a turn ends at the max-turns limit stays queued. Claude Code doesn’t add it to that turn’s last model call. It starts a new turn for the message, and the max-turns count starts over for that turn.
Effort level
Theeffort option controls how much reasoning Claude applies. Lower effort levels use fewer tokens per turn and reduce cost. Not all models support the effort parameter. See Effort for which models support it.
If you don’t set
effort, Claude Code resolves the effort level itself, in the order Adjust effort level describes.
effort trades latency and token cost for reasoning depth within each response.