Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

How the agent loop works changedagent-sdk/agent-loop

Nearest release: v2.1.304, published 6 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 20 Oct 2026 02:14 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.

Upstream edited
Recorded here
Lines+178added
Lines−184removed
From line 12 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits19to this page, all time

The whole hunk

from line 12, old and new numbered
/
lines
from line 12
1212 
1313Every agent session follows the same cycle:
1414 
15<img src="https://mintcdn.com/claude-code/ikqp3_70mqIahteV/images/agent-loop-diagram.svg?fit=max&auto=format&n=ikqp3_70mqIahteV&q=85&s=1c6e8f28d80dba14a7287419656f1237" className="dark:hidden" alt="Diagram of the agent loop: your prompt enters the agentic loop, where Claude evaluates and either requests tool calls, whose results feed back into another evaluation, or returns the final answer" width="720" height="212" data-path="images/agent-loop-diagram.svg" />
15![Diagram of the agent loop: your prompt enters the agentic loop, where Claude evaluates and either requests tool calls, whose results feed back into another evaluation, or returns the final answer](https://mintcdn.com/claude-code/ikqp3_70mqIahteV/images/agent-loop-diagram.svg?fit=max\&auto=format\&n=ikqp3_70mqIahteV\&q=85\&s=1c6e8f28d80dba14a7287419656f1237)
1616 
17<img src="https://mintcdn.com/claude-code/_xqph1dUOslCOwsj/images/agent-loop-diagram-dark.svg?fit=max&auto=format&n=_xqph1dUOslCOwsj&q=85&s=afe723c52a324d3c61fa72fb02432ab6" className="hidden dark:block" alt="Diagram of the agent loop: your prompt enters the agentic loop, where Claude evaluates and either requests tool calls, whose results feed back into another evaluation, or returns the final answer" width="720" height="212" data-path="images/agent-loop-diagram-dark.svg" />
18 
19171. **Receive prompt.** Claude receives your prompt, along with the system prompt, tool definitions, and conversation history. The SDK yields a [`SystemMessage`](#message-types) with subtype `"init"` containing session metadata.
20182. **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 [`AssistantMessage`](#message-types) objects, one for each content block, such as a text block or a tool call request.
21193. **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](/docs/en/agent-sdk/hooks) to intercept, modify, or block tool calls before they run.
from line 73
7573* **Python:** check message types with `isinstance()` against classes imported from `claude_agent_sdk` (for example, `isinstance(message, ResultMessage)`).
7674* **TypeScript:** check the `type` string field (for example, `message.type === "result"`). `AssistantMessage` and `UserMessage` wrap the raw API message in a `.message` field, so content blocks are at `message.message.content`, not `message.content`.
7775 
78<Accordion title="Example: Check message types and handle results">
79 <CodeGroup>
80 ```python Python theme={null}
81 import asyncio
82 from claude_agent_sdk import query, AssistantMessage, ResultMessage, TextBlock, ToolUseBlock
76> **Example: Check message types and handle results**
77>
78> ```python Python theme={null}
79> import asyncio
80> from claude_agent_sdk import query, AssistantMessage, ResultMessage, TextBlock, ToolUseBlock
81>
82>
83> async def main():
84> try:
85> async for message in query(prompt="Summarize this project"):
86> if isinstance(message, AssistantMessage):
87> # Each AssistantMessage carries one content block
88> for block in message.content:
89> if isinstance(block, TextBlock):
90> print(f"Claude: {block.text}")
91> elif isinstance(block, ToolUseBlock):
92> print(f"Tool call: {block.name}")
93> if isinstance(message, ResultMessage):
94> if message.subtype == "success":
95> print(message.result)
96> else:
97> print(f"Stopped: {message.subtype}")
98> except Exception as error:
99> # A single-shot query() raises after yielding an error result. If the
100> # failure was an error result, the error subtype branches above have
101> # already run; connection or process failures yield no result message.
102> print(f"Session ended with an error: {error}")
103>
104>
105> asyncio.run(main())
106> ```
107>
108> ```typescript TypeScript theme={null}
109> import { query } from "@anthropic-ai/claude-agent-sdk";
110>
111> try {
112> for await (const message of query({ prompt: "Summarize this project" })) {
113> if (message.type === "assistant") {
114> // Each assistant message carries one content block
115> for (const block of message.message.content) {
116> if (block.type === "text") {
117> console.log(`Claude: ${block.text}`);
118> } else if (block.type === "tool_use") {
119> console.log(`Tool call: ${block.name}`);
120> }
121> }
122> }
123> if (message.type === "result") {
124> if (message.subtype === "success") {
125> console.log(message.result);
126> } else {
127> console.log(`Stopped: ${message.subtype}`);
128> }
129> }
130> }
131> } catch (error) {
132> // A single-shot query() throws after yielding an error result. If the
133> // failure was an error result, the error subtype branches above have
134> // already run; connection or process failures yield no result message.
135> console.log(`Session ended with an error: ${error}`);
136> }
137> ```
83138 
84 
85 async def main():
86 try:
87 async for message in query(prompt="Summarize this project"):
88 if isinstance(message, AssistantMessage):
89 # Each AssistantMessage carries one content block
90 for block in message.content:
91 if isinstance(block, TextBlock):
92 print(f"Claude: {block.text}")
93 elif isinstance(block, ToolUseBlock):
94 print(f"Tool call: {block.name}")
95 if isinstance(message, ResultMessage):
96 if message.subtype == "success":
97 print(message.result)
98 else:
99 print(f"Stopped: {message.subtype}")
100 except Exception as error:
101 # A single-shot query() raises after yielding an error result. If the
102 # failure was an error result, the error subtype branches above have
103 # already run; connection or process failures yield no result message.
104 print(f"Session ended with an error: {error}")
105 
106 
107 asyncio.run(main())
108 ```
109 
110 ```typescript TypeScript theme={null}
111 import { query } from "@anthropic-ai/claude-agent-sdk";
112 
113 try {
114 for await (const message of query({ prompt: "Summarize this project" })) {
115 if (message.type === "assistant") {
116 // Each assistant message carries one content block
117 for (const block of message.message.content) {
118 if (block.type === "text") {
119 console.log(`Claude: ${block.text}`);
120 } else if (block.type === "tool_use") {
121 console.log(`Tool call: ${block.name}`);
122 }
123 }
124 }
125 if (message.type === "result") {
126 if (message.subtype === "success") {
127 console.log(message.result);
128 } else {
129 console.log(`Stopped: ${message.subtype}`);
130 }
131 }
132 }
133 } catch (error) {
134 // A single-shot query() throws after yielding an error result. If the
135 // failure was an error result, the error subtype branches above have
136 // already run; connection or process failures yield no result message.
137 console.log(`Session ended with an error: ${error}`);
138 }
139 ```
140 </CodeGroup>
141</Accordion>
142 
143139## Tool execution
144140 
145141Tools 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.
from line 214
218214 
219215If you don't set `effort`, Claude Code resolves the effort level itself, in the order [Adjust effort level](/docs/en/model-config#adjust-effort-level) describes.
220216 
221<Note>
222 `effort` trades latency and token cost for reasoning depth within each response. [Extended thinking](https://platform.claude.com/docs/en/build-with-claude/extended-thinking) is a separate feature that produces `thinking` blocks in the output, and the `display` field on `ThinkingConfig` for [Python](/docs/en/agent-sdk/python#thinkingconfig) or [TypeScript](/docs/en/agent-sdk/typescript#thinkingconfig) controls whether you receive their text. They are independent: you can set `effort: "low"` with extended thinking enabled, or `effort: "max"` without it.
223</Note>
217> [!NOTE]
218>
219> `effort` trades latency and token cost for reasoning depth within each response. [Extended thinking](https://platform.claude.com/docs/en/build-with-claude/extended-thinking) is a separate feature that produces `thinking` blocks in the output, and the `display` field on `ThinkingConfig` for [Python](/docs/en/agent-sdk/python#thinkingconfig) or [TypeScript](/docs/en/agent-sdk/typescript#thinkingconfig) controls whether you receive their text. They are independent: you can set `effort: "low"` with extended thinking enabled, or `effort: "max"` without it.
224220 
225221Use lower effort for agents doing simple, well-scoped tasks (like listing files or running a single grep) to reduce cost and latency. Set `effort` in the top-level `query()` options for the whole session, or per subagent with the `effort` field on [`AgentDefinition`](/docs/en/agent-sdk/subagents#agentdefinition-configuration) to override the session level.
226222 
from line 269
273269* **`PreCompact` hook:** Run custom logic before compaction occurs, for example to archive the full transcript. The hook receives a `trigger` field (`manual` or `auto`). See [hooks](/docs/en/agent-sdk/hooks).
274270* **Manual compaction:** Send `/compact` as a prompt string to trigger compaction on demand. Commands sent this way are ordinary SDK inputs. See [dispatch commands by name](/docs/en/agent-sdk/skills#dispatch-commands-by-name).
275271 
276<Accordion title="Example: Summarization instructions in CLAUDE.md">
277 Add a section to your project's CLAUDE.md telling the compactor what to preserve. The header name isn't special; use any clear label.
272> **Example: Summarization instructions in CLAUDE.md**
273>
274> Add a section to your project's CLAUDE.md telling the compactor what to preserve. The header name isn't special; use any clear label.
275>
276> ```markdown CLAUDE.md theme={null}
277> # Summary instructions
278>
279> When summarizing this conversation, always preserve:
280> - The current task objective and acceptance criteria
281> - File paths that have been read or modified
282> - Test results and error messages
283> - Decisions made and the reasoning behind them
284> ```
278285 
279 ```markdown CLAUDE.md theme={null}
280 # Summary instructions
281 
282 When summarizing this conversation, always preserve:
283 - The current task objective and acceptance criteria
284 - File paths that have been read or modified
285 - Test results and error messages
286 - Decisions made and the reasoning behind them
287 ```
288</Accordion>
289 
290286### Keep context efficient
291287 
292288A few strategies for long-running agents:
from line 302
306302 
307303See [Session management](/docs/en/agent-sdk/sessions) for the full guide on resume, continue, and fork patterns. To resume sessions across stateless containers or serverless hosts, pass a [`session_store` / `sessionStore` adapter](/docs/en/agent-sdk/session-storage) so the SDK mirrors transcripts to your own backend and another host can resume them. The Claude Code subprocess still writes to local disk first. See [Dual-write architecture](/docs/en/agent-sdk/session-storage#dual-write-architecture) for which copy outlives a fresh session versus a run resumed from the store, and how to keep the local copy ephemeral.
308304 
309<Note>
310 In Python, `ClaudeSDKClient` handles session IDs automatically across multiple calls. See the [Python SDK reference](/docs/en/agent-sdk/python#choosing-between-query-and-claudesdkclient) for details.
311</Note>
305> [!NOTE]
306>
307> In Python, `ClaudeSDKClient` handles session IDs automatically across multiple calls. See the [Python SDK reference](/docs/en/agent-sdk/python#choosing-between-query-and-claudesdkclient) for details.
312308 
313309## Handle the result
314310 
from line 327
331327 
332328The `usage` field covers only the main agent loop. Use `modelUsage`, or `model_usage` in Python, for whole-tree token and cost accounting. See [Tracking costs and usage](/docs/en/agent-sdk/cost-tracking) for details on interpreting the `usage` fields.
333329 
334<Note>
335 When a query ends on an error result:
330> [!NOTE]
331>
332> When a query ends on an error result:
333>
334> * A single-shot `query()` call yields the final result message, then raises an error that includes the failure text, such as `Reached maximum number of turns`. The raise is intentional. Wrap the loop in a try block if your code needs to continue past it. The underlying Claude Code process also exits with a nonzero code.
335> * A streaming input session stays alive, and you can keep sending messages, except after a session crash, which emits a final `error_during_execution` result and exits the process.
336336 
337 * A single-shot `query()` call yields the final result message, then raises an error that includes the failure text, such as `Reached maximum number of turns`. The raise is intentional. Wrap the loop in a try block if your code needs to continue past it. The underlying Claude Code process also exits with a nonzero code.
338 * A streaming input session stays alive, and you can keep sending messages, except after a session crash, which emits a final `error_during_execution` result and exits the process.
339</Note>
340 
341337The result also includes a `stop_reason` field (`string | null` in TypeScript, `str | None` in Python) indicating why the model stopped generating on its final turn. Common values are `end_turn` (model finished normally), `max_tokens` (hit the output token limit), and `refusal` (the model declined the request). On error results that the loop produced, `stop_reason` carries the value from the last assistant response before the loop ended; the result Claude Code synthesizes after a session crash carries `null`.
342338 
343339To detect refusals, check `stop_reason === "refusal"` (TypeScript) or `stop_reason == "refusal"` (Python). See [`SDKResultMessage`](/docs/en/agent-sdk/typescript#sdkresultmessage) (TypeScript) or [`ResultMessage`](/docs/en/agent-sdk/python#resultmessage) (Python) for the full type.
from line 361
365361 
366362Because a single-shot `query()` call raises after yielding an error result, the loop is wrapped in a try block so the script exits cleanly when a limit is hit.
367363 
368<CodeGroup>
369 ```python Python theme={null}
370 import asyncio
371 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
364```python Python theme={null}
365import asyncio
366from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
372367 
373368 
374 async def run_agent():
375 session_id = None
369async def run_agent():
370 session_id = None
376371 
377 try:
378 async for message in query(
379 prompt="Find and fix the bug causing test failures in the auth module",
380 options=ClaudeAgentOptions(
381 allowed_tools=[
382 "Read",
383 "Edit",
384 "Bash",
385 "Glob",
386 "Grep",
387 ], # Listing tools here auto-approves them (no prompting)
388 setting_sources=[
389 "project"
390 ], # Load CLAUDE.md, skills, hooks from current directory
391 max_turns=30, # Prevent runaway sessions
392 effort="high", # Thorough reasoning for complex debugging
393 ),
394 ):
395 # Handle the final result
396 if isinstance(message, ResultMessage):
397 session_id = message.session_id # Save for potential resumption
372 try:
373 async for message in query(
374 prompt="Find and fix the bug causing test failures in the auth module",
375 options=ClaudeAgentOptions(
376 allowed_tools=[
377 "Read",
378 "Edit",
379 "Bash",
380 "Glob",
381 "Grep",
382 ], # Listing tools here auto-approves them (no prompting)
383 setting_sources=[
384 "project"
385 ], # Load CLAUDE.md, skills, hooks from current directory
386 max_turns=30, # Prevent runaway sessions
387 effort="high", # Thorough reasoning for complex debugging
388 ),
389 ):
390 # Handle the final result
391 if isinstance(message, ResultMessage):
392 session_id = message.session_id # Save for potential resumption
398393 
399 if message.subtype == "success":
400 print(f"Done: {message.result}")
401 elif message.subtype == "error_max_turns":
402 # Agent ran out of turns. Resume with a higher limit.
403 print(f"Hit turn limit. Resume session {session_id} to continue.")
404 elif message.subtype == "error_max_budget_usd":
405 print("Hit budget limit.")
406 else:
407 print(f"Stopped: {message.subtype}")
408 if message.total_cost_usd is not None:
409 print(f"Cost: ${message.total_cost_usd:.4f}")
410 except Exception as error:
411 # A single-shot query() raises after yielding an error result. If the
412 # failure was an error result, the error subtype branches above have
413 # already run; connection or process failures yield no result message.
414 print(f"Session ended with an error: {error}")
394 if message.subtype == "success":
395 print(f"Done: {message.result}")
396 elif message.subtype == "error_max_turns":
397 # Agent ran out of turns. Resume with a higher limit.
398 print(f"Hit turn limit. Resume session {session_id} to continue.")
399 elif message.subtype == "error_max_budget_usd":
400 print("Hit budget limit.")
401 else:
402 print(f"Stopped: {message.subtype}")
403 if message.total_cost_usd is not None:
404 print(f"Cost: ${message.total_cost_usd:.4f}")
405 except Exception as error:
406 # A single-shot query() raises after yielding an error result. If the
407 # failure was an error result, the error subtype branches above have
408 # already run; connection or process failures yield no result message.
409 print(f"Session ended with an error: {error}")
415410 
416411 
417 asyncio.run(run_agent())
418 ```
412asyncio.run(run_agent())
413```
419414 
420 ```typescript TypeScript theme={null}
421 import { query } from "@anthropic-ai/claude-agent-sdk";
415```typescript TypeScript theme={null}
416import { query } from "@anthropic-ai/claude-agent-sdk";
422417 
423 let sessionId: string | undefined;
418let sessionId: string | undefined;
424419 
425 try {
426 for await (const message of query({
427 prompt: "Find and fix the bug causing test failures in the auth module",
428 options: {
429 allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], // Listing tools here auto-approves them (no prompting)
430 settingSources: ["project"], // Load CLAUDE.md, skills, hooks from current directory
431 maxTurns: 30, // Prevent runaway sessions
432 effort: "high" // Thorough reasoning for complex debugging
433 }
434 })) {
435 // Save the session ID to resume later if needed
436 if (message.type === "system" && message.subtype === "init") {
437 sessionId = message.session_id;
438 }
420try {
421 for await (const message of query({
422 prompt: "Find and fix the bug causing test failures in the auth module",
423 options: {
424 allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], // Listing tools here auto-approves them (no prompting)
425 settingSources: ["project"], // Load CLAUDE.md, skills, hooks from current directory
426 maxTurns: 30, // Prevent runaway sessions
427 effort: "high" // Thorough reasoning for complex debugging
428 }
429 })) {
430 // Save the session ID to resume later if needed
431 if (message.type === "system" && message.subtype === "init") {
432 sessionId = message.session_id;
433 }
439434 
440 // Handle the final result
441 if (message.type === "result") {
442 if (message.subtype === "success") {
443 console.log(`Done: ${message.result}`);
444 } else if (message.subtype === "error_max_turns") {
445 // Agent ran out of turns. Resume with a higher limit.
446 console.log(`Hit turn limit. Resume session ${sessionId} to continue.`);
447 } else if (message.subtype === "error_max_budget_usd") {
448 console.log("Hit budget limit.");
449 } else {
450 console.log(`Stopped: ${message.subtype}`);
451 }
452 console.log(`Cost: $${message.total_cost_usd.toFixed(4)}`);
435 // Handle the final result
436 if (message.type === "result") {
437 if (message.subtype === "success") {
438 console.log(`Done: ${message.result}`);
439 } else if (message.subtype === "error_max_turns") {
440 // Agent ran out of turns. Resume with a higher limit.
441 console.log(`Hit turn limit. Resume session ${sessionId} to continue.`);
442 } else if (message.subtype === "error_max_budget_usd") {
443 console.log("Hit budget limit.");
444 } else {
445 console.log(`Stopped: ${message.subtype}`);
453446 }
447 console.log(`Cost: $${message.total_cost_usd.toFixed(4)}`);
454448 }
455 } catch (error) {
456 // A single-shot query() throws after yielding an error result. If the
457 // failure was an error result, the error subtype branches above have
458 // already run; connection or process failures yield no result message.
459 console.log(`Session ended with an error: ${error}`);
460449 }
461 ```
462</CodeGroup>
450} catch (error) {
451 // A single-shot query() throws after yielding an error result. If the
452 // failure was an error result, the error subtype branches above have
453 // already run; connection or process failures yield no result message.
454 console.log(`Session ended with an error: ${error}`);
455}
456```
463457 
464458When the agent finishes successfully, the example prints a `Done:` line with the agent's summary of the fix, then a line like `Cost: $0.0312`.
465459 
Feedback