Subagents in the SDK changedagent-sdk/subagents
Nearest release: v2.1.304, published under an hour 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 19 Oct 2026 19:49 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+516added
Lines−530removed
From line
34
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits24to this page, all time
The whole hunk
from line 34, old and new numbered
/
The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.
from line 34
3434
3535This example creates two subagents: a code reviewer with read-only access and a test runner that can execute commands.
3636
37<CodeGroup>
38 ```python Python theme={null}
39 import asyncio
40 from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
41
42
43 async def main():
44 async for message in query(
45 prompt="Review the authentication module for security issues",
46 options=ClaudeAgentOptions(
47 # Auto-approve these tools
48 allowed_tools=["Read", "Grep", "Glob", "Agent"],
49 agents={
50 "code-reviewer": AgentDefinition(
51 # description tells Claude when to use this subagent
52 description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
53 # prompt defines the subagent's behavior and expertise
54 prompt="""You are a code review specialist with expertise in security, performance, and best practices.
55
56 When reviewing code:
57 - Identify security vulnerabilities
58 - Check for performance issues
59 - Verify adherence to coding standards
60 - Suggest specific improvements
61
62 Be thorough but concise in your feedback.""",
63 # tools restricts what the subagent can do (read-only here)
64 tools=["Read", "Grep", "Glob"],
65 # model overrides the default model for this subagent
66 model="sonnet",
67 ),
68 "test-runner": AgentDefinition(
69 description="Runs and analyzes test suites. Use for test execution and coverage analysis.",
70 prompt="""You are a test execution specialist. Run tests and provide clear analysis of results.
71
72 Focus on:
73 - Running test commands
74 - Analyzing test output
75 - Identifying failing tests
76 - Suggesting fixes for failures""",
77 # Bash access lets this subagent run test commands
78 tools=["Bash", "Read", "Grep"],
79 ),
80 },
81 ),
82 ):
83 if hasattr(message, "result"):
84 print(message.result)
85
86
87 asyncio.run(main())
88 ```
89
90 ```typescript TypeScript theme={null}
91 import { query } from "@anthropic-ai/claude-agent-sdk";
92
93 for await (const message of query({
94 prompt: "Review the authentication module for security issues",
95 options: {
96 // Auto-approve these tools
97 allowedTools: ["Read", "Grep", "Glob", "Agent"],
98 agents: {
99 "code-reviewer": {
100 // description tells Claude when to use this subagent
101 description:
102 "Expert code review specialist. Use for quality, security, and maintainability reviews.",
103 // prompt defines the subagent's behavior and expertise
104 prompt: `You are a code review specialist with expertise in security, performance, and best practices.
105
106 When reviewing code:
107 - Identify security vulnerabilities
108 - Check for performance issues
109 - Verify adherence to coding standards
110 - Suggest specific improvements
111
112 Be thorough but concise in your feedback.`,
113 // tools restricts what the subagent can do (read-only here)
114 tools: ["Read", "Grep", "Glob"],
115 // model overrides the default model for this subagent
116 model: "sonnet"
117 },
118 "test-runner": {
119 description:
120 "Runs and analyzes test suites. Use for test execution and coverage analysis.",
121 prompt: `You are a test execution specialist. Run tests and provide clear analysis of results.
122
123 Focus on:
124 - Running test commands
125 - Analyzing test output
126 - Identifying failing tests
127 - Suggesting fixes for failures`,
128 // Bash access lets this subagent run test commands
129 tools: ["Bash", "Read", "Grep"]
130 }
37```python Python theme={null}
38import asyncio
39from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
40
41
42async def main():
43 async for message in query(
44 prompt="Review the authentication module for security issues",
45 options=ClaudeAgentOptions(
46 # Auto-approve these tools
47 allowed_tools=["Read", "Grep", "Glob", "Agent"],
48 agents={
49 "code-reviewer": AgentDefinition(
50 # description tells Claude when to use this subagent
51 description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
52 # prompt defines the subagent's behavior and expertise
53 prompt="""You are a code review specialist with expertise in security, performance, and best practices.
54
55When reviewing code:
56- Identify security vulnerabilities
57- Check for performance issues
58- Verify adherence to coding standards
59- Suggest specific improvements
60
61Be thorough but concise in your feedback.""",
62 # tools restricts what the subagent can do (read-only here)
63 tools=["Read", "Grep", "Glob"],
64 # model overrides the default model for this subagent
65 model="sonnet",
66 ),
67 "test-runner": AgentDefinition(
68 description="Runs and analyzes test suites. Use for test execution and coverage analysis.",
69 prompt="""You are a test execution specialist. Run tests and provide clear analysis of results.
70
71Focus on:
72- Running test commands
73- Analyzing test output
74- Identifying failing tests
75- Suggesting fixes for failures""",
76 # Bash access lets this subagent run test commands
77 tools=["Bash", "Read", "Grep"],
78 ),
79 },
80 ),
81 ):
82 if hasattr(message, "result"):
83 print(message.result)
84
85
86asyncio.run(main())
87```
88
89```typescript TypeScript theme={null}
90import { query } from "@anthropic-ai/claude-agent-sdk";
91
92for await (const message of query({
93 prompt: "Review the authentication module for security issues",
94 options: {
95 // Auto-approve these tools
96 allowedTools: ["Read", "Grep", "Glob", "Agent"],
97 agents: {
98 "code-reviewer": {
99 // description tells Claude when to use this subagent
100 description:
101 "Expert code review specialist. Use for quality, security, and maintainability reviews.",
102 // prompt defines the subagent's behavior and expertise
103 prompt: `You are a code review specialist with expertise in security, performance, and best practices.
104
105When reviewing code:
106- Identify security vulnerabilities
107- Check for performance issues
108- Verify adherence to coding standards
109- Suggest specific improvements
110
111Be thorough but concise in your feedback.`,
112 // tools restricts what the subagent can do (read-only here)
113 tools: ["Read", "Grep", "Glob"],
114 // model overrides the default model for this subagent
115 model: "sonnet"
116 },
117 "test-runner": {
118 description:
119 "Runs and analyzes test suites. Use for test execution and coverage analysis.",
120 prompt: `You are a test execution specialist. Run tests and provide clear analysis of results.
121
122Focus on:
123- Running test commands
124- Analyzing test output
125- Identifying failing tests
126- Suggesting fixes for failures`,
127 // Bash access lets this subagent run test commands
128 tools: ["Bash", "Read", "Grep"]
131129 }
132130 }
133 })) {
134 if ("result" in message) console.log(message.result);
135 }
136 ```
137</CodeGroup>
131 }
132})) {
133 if ("result" in message) console.log(message.result);
134}
135```
138136
139137### AgentDefinition configuration
140138
from line 169
171169
172170You can also define subagents as markdown files in `.claude/agents/` directories. See the [Claude Code subagents documentation](/docs/en/sub-agents) for details on this approach. Programmatically defined agents take precedence over filesystem-based agents with the same name.
173171
174<Note>
175 When Claude calls the Agent tool without a `subagent_type`, it gets the built-in `general-purpose` subagent, which Claude can spawn even when you define no agents of your own. Setting [`CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1`](/docs/en/env-vars) removes that default, and such a call fails with [`subagent_type is required`](/docs/en/errors#subagent-type-is-required).
176</Note>
172> [!NOTE]
173>
174> When Claude calls the Agent tool without a `subagent_type`, it gets the built-in `general-purpose` subagent, which Claude can spawn even when you define no agents of your own. Setting [`CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1`](/docs/en/env-vars) removes that default, and such a call fails with [`subagent_type is required`](/docs/en/errors#subagent-type-is-required).
177175
178176## What subagents inherit
179177
from line 189
191189| Project CLAUDE.md (loaded via [`settingSources`](/docs/en/agent-sdk/claude-code-features#control-filesystem-settings-with-settingsources)). An agent that sets [`omitClaudeMd`](#agentdefinition-configuration) starts without it | Preloaded skill content, unless listed in `AgentDefinition.skills` |
192190| Tool definitions (inherited from parent or the subset in `tools`, [filtered for background runs](/docs/en/sub-agents#available-tools)) | The parent's system prompt |
193191
194<Note>
195 The parent receives the subagent's final report, but may summarize it in its own response. To preserve subagent output verbatim in the user-facing response, include an instruction to do so in the prompt or `systemPrompt` option you pass to the main `query()` call.
196
197 In v2.1.210 and later, Claude Code [scans the final message for instruction-shaped patterns](/docs/en/sub-agents#subagent-output-scanning) before the parent reads it. The scan treats three kinds of pattern differently:
198
199 * **Control-tag imitation**: Claude Code neutralizes a tag that only the harness emits, such as a `<system-reminder>` block, in place. It inserts a backslash after the opening angle bracket and deletes nothing.
200 * **Permission-configuration mentions**: Claude Code keeps references to the permission configuration, such as `.claude/settings.json`, `bypassPermissions`, or `--dangerously-skip-permissions`, as written.
201 * **Turn markers**: a line that starts with `Human:` or `Assistant:` gets a backslash before the colon, so the message can't imitate a conversation turn boundary.
202
203 For a control-tag or permission-configuration match, Claude Code prepends a `[harness: ...]` marker line naming the matched patterns; a turn-marker match doesn't add the marker line. Those are the only modifications the scan makes: it never removes or rewords the subagent's text.
204</Note>
192> [!NOTE]
193>
194> The parent receives the subagent's final report, but may summarize it in its own response. To preserve subagent output verbatim in the user-facing response, include an instruction to do so in the prompt or `systemPrompt` option you pass to the main `query()` call.
195>
196> In v2.1.210 and later, Claude Code [scans the final message for instruction-shaped patterns](/docs/en/sub-agents#subagent-output-scanning) before the parent reads it. The scan treats three kinds of pattern differently:
197>
198> * **Control-tag imitation**: Claude Code neutralizes a tag that only the harness emits, such as a `<system-reminder>` block, in place. It inserts a backslash after the opening angle bracket and deletes nothing.
199> * **Permission-configuration mentions**: Claude Code keeps references to the permission configuration, such as `.claude/settings.json`, `bypassPermissions`, or `--dangerously-skip-permissions`, as written.
200> * **Turn markers**: a line that starts with `Human:` or `Assistant:` gets a backslash before the colon, so the message can't imitate a conversation turn boundary.
201>
202> For a control-tag or permission-configuration match, Claude Code prepends a `[harness: ...]` marker line naming the matched patterns; a turn-marker match doesn't add the marker line. Those are the only modifications the scan makes: it never removes or rewords the subagent's text.
205203
206204An API error that ends the subagent early, such as a rate limit, is never delivered as its result. See [API errors in subagents](/docs/en/sub-agents#api-errors-in-subagents) for the foreground and background behavior.
207205
from line 227
229227
230228To stop Claude from starting forks, add an [`Agent(fork)` deny rule](/docs/en/agent-sdk/permissions#allow-and-deny-rules):
231229
232<CodeGroup>
233 ```python Python theme={null}
234 options = ClaudeAgentOptions(
235 # Deny only the fork subagent type; other subagents still run
236 disallowed_tools=["Agent(fork)"],
237 )
238 ```
239
240 ```typescript TypeScript theme={null}
241 const options = {
242 // Deny only the fork subagent type; other subagents still run
243 disallowedTools: ["Agent(fork)"]
230```python Python theme={null}
231options = ClaudeAgentOptions(
232 # Deny only the fork subagent type; other subagents still run
233 disallowed_tools=["Agent(fork)"],
234)
235```
236
237```typescript TypeScript theme={null}
238const options = {
239 // Deny only the fork subagent type; other subagents still run
240 disallowedTools: ["Agent(fork)"]
241};
242```
243
244### Dynamic agent configuration
245
246You can create agent definitions dynamically based on runtime conditions. This example creates a security reviewer with different strictness levels, using a more capable model for strict reviews.
247
248```python Python theme={null}
249import asyncio
250from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
251
252
253# Factory function that returns an AgentDefinition
254# This pattern lets you customize agents based on runtime conditions
255def create_security_agent(security_level: str) -> AgentDefinition:
256 is_strict = security_level == "strict"
257 return AgentDefinition(
258 description="Security code reviewer",
259 # Customize the prompt based on strictness level
260 prompt=f"You are a {'strict' if is_strict else 'balanced'} security reviewer...",
261 tools=["Read", "Grep", "Glob"],
262 # Key insight: use a more capable model for high-stakes reviews
263 model="opus" if is_strict else "sonnet",
264 )
265
266
267async def main():
268 # The agent is created at query time, so each request can use different settings
269 async for message in query(
270 prompt="Review this PR for security issues",
271 options=ClaudeAgentOptions(
272 allowed_tools=["Read", "Grep", "Glob", "Agent"],
273 agents={
274 # Call the factory with your desired configuration
275 "security-reviewer": create_security_agent("strict")
276 },
277 ),
278 ):
279 if hasattr(message, "result"):
280 print(message.result)
281
282
283asyncio.run(main())
284```
285
286```typescript TypeScript theme={null}
287import { query, type AgentDefinition } from "@anthropic-ai/claude-agent-sdk";
288
289// Factory function that returns an AgentDefinition
290// This pattern lets you customize agents based on runtime conditions
291function createSecurityAgent(securityLevel: "basic" | "strict"): AgentDefinition {
292 const isStrict = securityLevel === "strict";
293 return {
294 description: "Security code reviewer",
295 // Customize the prompt based on strictness level
296 prompt: `You are a ${isStrict ? "strict" : "balanced"} security reviewer...`,
297 tools: ["Read", "Grep", "Glob"],
298 // Key insight: use a more capable model for high-stakes reviews
299 model: isStrict ? "opus" : "sonnet"
244300 };
245 ```
246</CodeGroup>
247
248### Dynamic agent configuration
249
250You can create agent definitions dynamically based on runtime conditions. This example creates a security reviewer with different strictness levels, using a more capable model for strict reviews.
251
252<CodeGroup>
253 ```python Python theme={null}
254 import asyncio
255 from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
256
257
258 # Factory function that returns an AgentDefinition
259 # This pattern lets you customize agents based on runtime conditions
260 def create_security_agent(security_level: str) -> AgentDefinition:
261 is_strict = security_level == "strict"
262 return AgentDefinition(
263 description="Security code reviewer",
264 # Customize the prompt based on strictness level
265 prompt=f"You are a {'strict' if is_strict else 'balanced'} security reviewer...",
266 tools=["Read", "Grep", "Glob"],
267 # Key insight: use a more capable model for high-stakes reviews
268 model="opus" if is_strict else "sonnet",
269 )
270
271
272 async def main():
273 # The agent is created at query time, so each request can use different settings
274 async for message in query(
275 prompt="Review this PR for security issues",
276 options=ClaudeAgentOptions(
277 allowed_tools=["Read", "Grep", "Glob", "Agent"],
278 agents={
279 # Call the factory with your desired configuration
280 "security-reviewer": create_security_agent("strict")
281 },
282 ),
283 ):
284 if hasattr(message, "result"):
285 print(message.result)
286
287
288 asyncio.run(main())
289 ```
290
291 ```typescript TypeScript theme={null}
292 import { query, type AgentDefinition } from "@anthropic-ai/claude-agent-sdk";
293
294 // Factory function that returns an AgentDefinition
295 // This pattern lets you customize agents based on runtime conditions
296 function createSecurityAgent(securityLevel: "basic" | "strict"): AgentDefinition {
297 const isStrict = securityLevel === "strict";
298 return {
299 description: "Security code reviewer",
300 // Customize the prompt based on strictness level
301 prompt: `You are a ${isStrict ? "strict" : "balanced"} security reviewer...`,
302 tools: ["Read", "Grep", "Glob"],
303 // Key insight: use a more capable model for high-stakes reviews
304 model: isStrict ? "opus" : "sonnet"
305 };
306 }
307
308 // The agent is created at query time, so each request can use different settings
309 for await (const message of query({
310 prompt: "Review this PR for security issues",
311 options: {
312 allowedTools: ["Read", "Grep", "Glob", "Agent"],
313 agents: {
314 // Call the factory with your desired configuration
315 "security-reviewer": createSecurityAgent("strict")
301}
302
303// The agent is created at query time, so each request can use different settings
304for await (const message of query({
305 prompt: "Review this PR for security issues",
306 options: {
307 allowedTools: ["Read", "Grep", "Glob", "Agent"],
308 agents: {
309 // Call the factory with your desired configuration
310 "security-reviewer": createSecurityAgent("strict")
311 }
312 }
313})) {
314 if ("result" in message) console.log(message.result);
315}
316```
317
318## Detect subagent invocation
319
320Claude invokes subagents through the Agent tool. To detect when a subagent is invoked, check for `tool_use` blocks where `name` is `"Agent"`.
321
322Messages from within a subagent's context include a `parent_tool_use_id` field. In TypeScript, each assistant and user message a subagent produces also carries [`agent_id`](/docs/en/agent-sdk/typescript#sdkassistantmessage): the `task_id` of that subagent's [task events](/docs/en/agent-sdk/typescript#sdktaskstartedmessage). `agent_id` requires TypeScript Agent SDK v0.3.292 or later.
323
324> [!NOTE]
325>
326> The tool appears as `"Agent"` in `tool_use` blocks but as `"Task"` in the `system:init` tools list. Before Claude Code v2.1.63, `tool_use` blocks also named it `"Task"`. To keep detection working across SDK versions, match both values in `block.name`.
327
328The message structure differs between SDKs. In Python, you access content blocks directly via `message.content`. In TypeScript, `SDKAssistantMessage` wraps the Claude API message, so you access content via `message.message.content`.
329
330This example iterates through streamed messages, logging when a subagent is invoked and when subsequent messages originate from within that subagent's execution context. The TypeScript version also logs the `agent_id` of each subagent message that carries one.
331
332```python Python theme={null}
333import asyncio
334from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolUseBlock
335
336
337async def main():
338 async for message in query(
339 prompt="Use the code-reviewer agent to review this codebase",
340 options=ClaudeAgentOptions(
341 allowed_tools=["Read", "Glob", "Grep", "Agent"],
342 agents={
343 "code-reviewer": AgentDefinition(
344 description="Expert code reviewer.",
345 prompt="Analyze code quality and suggest improvements.",
346 tools=["Read", "Glob", "Grep"],
347 )
348 },
349 ),
350 ):
351 # Check for subagent invocation. Match both names: older SDK
352 # versions emitted "Task", current versions emit "Agent".
353 if hasattr(message, "content") and message.content:
354 for block in message.content:
355 if isinstance(block, ToolUseBlock) and block.name in (
356 "Task",
357 "Agent",
358 ):
359 print(f"Subagent invoked: {block.input.get('subagent_type')}")
360
361 # Check if this message is from within a subagent's context
362 if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
363 print(" (running inside subagent)")
364
365 if hasattr(message, "result"):
366 print(message.result)
367
368
369asyncio.run(main())
370```
371
372```typescript TypeScript theme={null}
373import { query } from "@anthropic-ai/claude-agent-sdk";
374
375for await (const message of query({
376 prompt: "Use the code-reviewer agent to review this codebase",
377 options: {
378 allowedTools: ["Read", "Glob", "Grep", "Agent"],
379 agents: {
380 "code-reviewer": {
381 description: "Expert code reviewer.",
382 prompt: "Analyze code quality and suggest improvements.",
383 tools: ["Read", "Glob", "Grep"]
316384 }
317385 }
318 })) {
319 if ("result" in message) console.log(message.result);
320 }
321 ```
322</CodeGroup>
323
324## Detect subagent invocation
325
326Claude invokes subagents through the Agent tool. To detect when a subagent is invoked, check for `tool_use` blocks where `name` is `"Agent"`.
327
328Messages from within a subagent's context include a `parent_tool_use_id` field. In TypeScript, each assistant and user message a subagent produces also carries [`agent_id`](/docs/en/agent-sdk/typescript#sdkassistantmessage): the `task_id` of that subagent's [task events](/docs/en/agent-sdk/typescript#sdktaskstartedmessage). `agent_id` requires TypeScript Agent SDK v0.3.292 or later.
329
330<Note>
331 The tool appears as `"Agent"` in `tool_use` blocks but as `"Task"` in the `system:init` tools list. Before Claude Code v2.1.63, `tool_use` blocks also named it `"Task"`. To keep detection working across SDK versions, match both values in `block.name`.
332</Note>
333
334The message structure differs between SDKs. In Python, you access content blocks directly via `message.content`. In TypeScript, `SDKAssistantMessage` wraps the Claude API message, so you access content via `message.message.content`.
335
336This example iterates through streamed messages, logging when a subagent is invoked and when subsequent messages originate from within that subagent's execution context. The TypeScript version also logs the `agent_id` of each subagent message that carries one.
337
338<CodeGroup>
339 ```python Python theme={null}
340 import asyncio
341 from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolUseBlock
342
343
344 async def main():
345 async for message in query(
346 prompt="Use the code-reviewer agent to review this codebase",
347 options=ClaudeAgentOptions(
348 allowed_tools=["Read", "Glob", "Grep", "Agent"],
349 agents={
350 "code-reviewer": AgentDefinition(
351 description="Expert code reviewer.",
352 prompt="Analyze code quality and suggest improvements.",
353 tools=["Read", "Glob", "Grep"],
354 )
355 },
356 ),
357 ):
358 # Check for subagent invocation. Match both names: older SDK
359 # versions emitted "Task", current versions emit "Agent".
360 if hasattr(message, "content") and message.content:
361 for block in message.content:
362 if isinstance(block, ToolUseBlock) and block.name in (
363 "Task",
364 "Agent",
365 ):
366 print(f"Subagent invoked: {block.input.get('subagent_type')}")
367
368 # Check if this message is from within a subagent's context
369 if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
370 print(" (running inside subagent)")
371
372 if hasattr(message, "result"):
373 print(message.result)
374
375
376 asyncio.run(main())
377 ```
378
379 ```typescript TypeScript theme={null}
380 import { query } from "@anthropic-ai/claude-agent-sdk";
381
382 for await (const message of query({
383 prompt: "Use the code-reviewer agent to review this codebase",
384 options: {
385 allowedTools: ["Read", "Glob", "Grep", "Agent"],
386 agents: {
387 "code-reviewer": {
388 description: "Expert code reviewer.",
389 prompt: "Analyze code quality and suggest improvements.",
390 tools: ["Read", "Glob", "Grep"]
391 }
392 }
386 }
387})) {
388 const msg = message as any;
389
390 // Check for subagent invocation. Match both names: older SDK versions
391 // emitted "Task", current versions emit "Agent".
392 for (const block of msg.message?.content ?? []) {
393 if (block.type === "tool_use" && (block.name === "Task" || block.name === "Agent")) {
394 console.log(`Subagent invoked: ${block.input.subagent_type}`);
393395 }
394 })) {
395 const msg = message as any;
396
397 // Check for subagent invocation. Match both names: older SDK versions
398 // emitted "Task", current versions emit "Agent".
399 for (const block of msg.message?.content ?? []) {
400 if (block.type === "tool_use" && (block.name === "Task" || block.name === "Agent")) {
401 console.log(`Subagent invoked: ${block.input.subagent_type}`);
402 }
396 }
397
398 // Check if this message is from within a subagent's context
399 if (msg.parent_tool_use_id) {
400 console.log(" (running inside subagent)");
401 // On assistant and user messages, agent_id matches the task_id
402 // on that subagent's task_started and other task events
403 if (msg.agent_id) {
404 console.log(` agent_id: ${msg.agent_id}`);
403405 }
404
405 // Check if this message is from within a subagent's context
406 if (msg.parent_tool_use_id) {
407 console.log(" (running inside subagent)");
408 // On assistant and user messages, agent_id matches the task_id
409 // on that subagent's task_started and other task events
410 if (msg.agent_id) {
411 console.log(` agent_id: ${msg.agent_id}`);
412 }
413 }
414
415 if ("result" in message) {
416 console.log(message.result);
417 }
418 }
419 ```
420</CodeGroup>
406 }
407
408 if ("result" in message) {
409 console.log(message.result);
410 }
411}
412```
421413
422414## Resume subagents
423415
from line 423
4314232. **Extract the agent ID**: parse `agentId` from the Agent tool result text
4324243. **Resume the session**: pass `resume: sessionId` in the second query's options, and include the agent ID in your prompt. Each `query()` call starts a new session by default, and you must resume the same session to access the subagent's transcript.
433425
434<Note>
435 When using a custom agent, pass the same agent definition in the `agents` parameter for both queries.
436</Note>
426> [!NOTE]
427>
428> When using a custom agent, pass the same agent definition in the `agents` parameter for both queries.
437429
438430The example below defines a custom `endpoint-finder` agent. The first query runs it and captures the session ID and agent ID from the Agent tool result, then the second query resumes the session to ask a follow-up question that requires context from the first analysis.
439431
440<CodeGroup>
441 ```python Python theme={null}
442 import asyncio
443 import re
444 from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolResultBlock
445
446 AGENTS = {
447 "endpoint-finder": AgentDefinition(
448 description="Locates and catalogs API endpoints in a codebase.",
449 prompt="You find and document API endpoints. Report each endpoint's path, method, and handler.",
450 tools=["Read", "Grep", "Glob"],
451 )
452 }
453
454
455 def extract_agent_id(block: ToolResultBlock) -> str | None:
456 """Extract agentId from an Agent tool result's text content."""
457 parts = block.content if isinstance(block.content, list) else [{"text": block.content}]
458 for part in parts:
459 if match := re.search(r"agentId:\s*([\w-]+)", part.get("text") or ""):
460 return match.group(1)
461 return None
462
463
464 async def main():
465 agent_id = None
466 session_id = None
467
468 # First invocation - run the endpoint-finder subagent
469 try:
470 async for message in query(
471 prompt="Use the endpoint-finder agent to find all API endpoints in this codebase",
472 options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS),
473 ):
474 # Capture session_id from ResultMessage (needed to resume this session)
475 if hasattr(message, "session_id"):
476 session_id = message.session_id
477 # Search tool results for the agentId trailer
478 for block in getattr(message, "content", None) or []:
479 if isinstance(block, ToolResultBlock):
480 agent_id = extract_agent_id(block) or agent_id
481 # Print the final result
482 if hasattr(message, "result"):
483 print(message.result)
484 except Exception as error:
485 # A single-shot query() raises after yielding an error result,
486 # so session_id and agent_id have already been captured by the loop above.
487 print(f"Session ended with an error: {error}")
488
489 # Second invocation - resume and ask follow-up
490 if agent_id and session_id:
491 async for message in query(
492 prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",
493 options=ClaudeAgentOptions(
494 allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS, resume=session_id
495 ),
496 ):
497 if hasattr(message, "result"):
498 print(message.result)
499 else:
500 print("No agentId found in the first query, so there is no subagent to resume.")
501
502
503 asyncio.run(main())
504 ```
505
506 ```typescript TypeScript theme={null}
507 import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
508
509 const agents = {
510 "endpoint-finder": {
511 description: "Locates and catalogs API endpoints in a codebase.",
512 prompt: "You find and document API endpoints. Report each endpoint's path, method, and handler.",
513 tools: ["Read", "Grep", "Glob"]
514 }
515 };
516
517 // Stringify content to search for agentId without traversing nested block types
518 function extractAgentId(message: SDKMessage): string | undefined {
519 if (message.type !== "assistant" && message.type !== "user") return undefined;
520 const content = JSON.stringify(message.message.content);
521 const match = content.match(/agentId:\s*([\w-]+)/);
522 return match?.[1];
523 }
524
525 let agentId: string | undefined;
526 let sessionId: string | undefined;
527
528 // First invocation - run the endpoint-finder subagent
529 try {
530 for await (const message of query({
531 prompt: "Use the endpoint-finder agent to find all API endpoints in this codebase",
532 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents }
533 })) {
534 // Capture session_id from ResultMessage (needed to resume this session)
535 if ("session_id" in message) sessionId = message.session_id;
536 // Search message content for the agentId (appears in Agent tool results)
537 const extractedId = extractAgentId(message);
538 if (extractedId) agentId = extractedId;
539 // Print the final result
540 if ("result" in message) console.log(message.result);
541 }
542 } catch (error) {
543 // A single-shot query() throws after yielding an error result,
544 // so sessionId and agentId have already been captured by the loop above.
545 console.error(`Session ended with an error: ${error}`);
546 }
547
548 // Second invocation - resume and ask follow-up
549 if (agentId && sessionId) {
550 for await (const message of query({
551 prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
552 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents, resume: sessionId }
553 })) {
554 if ("result" in message) console.log(message.result);
555 }
556 } else {
557 console.log("No agentId found in the first query, so there is no subagent to resume.");
558 }
559 ```
560</CodeGroup>
432```python Python theme={null}
433import asyncio
434import re
435from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolResultBlock
436
437AGENTS = {
438 "endpoint-finder": AgentDefinition(
439 description="Locates and catalogs API endpoints in a codebase.",
440 prompt="You find and document API endpoints. Report each endpoint's path, method, and handler.",
441 tools=["Read", "Grep", "Glob"],
442 )
443}
444
445
446def extract_agent_id(block: ToolResultBlock) -> str | None:
447 """Extract agentId from an Agent tool result's text content."""
448 parts = block.content if isinstance(block.content, list) else [{"text": block.content}]
449 for part in parts:
450 if match := re.search(r"agentId:\s*([\w-]+)", part.get("text") or ""):
451 return match.group(1)
452 return None
453
454
455async def main():
456 agent_id = None
457 session_id = None
458
459 # First invocation - run the endpoint-finder subagent
460 try:
461 async for message in query(
462 prompt="Use the endpoint-finder agent to find all API endpoints in this codebase",
463 options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS),
464 ):
465 # Capture session_id from ResultMessage (needed to resume this session)
466 if hasattr(message, "session_id"):
467 session_id = message.session_id
468 # Search tool results for the agentId trailer
469 for block in getattr(message, "content", None) or []:
470 if isinstance(block, ToolResultBlock):
471 agent_id = extract_agent_id(block) or agent_id
472 # Print the final result
473 if hasattr(message, "result"):
474 print(message.result)
475 except Exception as error:
476 # A single-shot query() raises after yielding an error result,
477 # so session_id and agent_id have already been captured by the loop above.
478 print(f"Session ended with an error: {error}")
479
480 # Second invocation - resume and ask follow-up
481 if agent_id and session_id:
482 async for message in query(
483 prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",
484 options=ClaudeAgentOptions(
485 allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS, resume=session_id
486 ),
487 ):
488 if hasattr(message, "result"):
489 print(message.result)
490 else:
491 print("No agentId found in the first query, so there is no subagent to resume.")
492
493
494asyncio.run(main())
495```
496
497```typescript TypeScript theme={null}
498import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
499
500const agents = {
501 "endpoint-finder": {
502 description: "Locates and catalogs API endpoints in a codebase.",
503 prompt: "You find and document API endpoints. Report each endpoint's path, method, and handler.",
504 tools: ["Read", "Grep", "Glob"]
505 }
506};
507
508// Stringify content to search for agentId without traversing nested block types
509function extractAgentId(message: SDKMessage): string | undefined {
510 if (message.type !== "assistant" && message.type !== "user") return undefined;
511 const content = JSON.stringify(message.message.content);
512 const match = content.match(/agentId:\s*([\w-]+)/);
513 return match?.[1];
514}
515
516let agentId: string | undefined;
517let sessionId: string | undefined;
518
519// First invocation - run the endpoint-finder subagent
520try {
521 for await (const message of query({
522 prompt: "Use the endpoint-finder agent to find all API endpoints in this codebase",
523 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents }
524 })) {
525 // Capture session_id from ResultMessage (needed to resume this session)
526 if ("session_id" in message) sessionId = message.session_id;
527 // Search message content for the agentId (appears in Agent tool results)
528 const extractedId = extractAgentId(message);
529 if (extractedId) agentId = extractedId;
530 // Print the final result
531 if ("result" in message) console.log(message.result);
532 }
533} catch (error) {
534 // A single-shot query() throws after yielding an error result,
535 // so sessionId and agentId have already been captured by the loop above.
536 console.error(`Session ended with an error: ${error}`);
537}
538
539// Second invocation - resume and ask follow-up
540if (agentId && sessionId) {
541 for await (const message of query({
542 prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
543 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents, resume: sessionId }
544 })) {
545 if ("result" in message) console.log(message.result);
546 }
547} else {
548 console.log("No agentId found in the first query, so there is no subagent to resume.");
549}
550```
561551
562552Subagent transcripts are stored in separate files and persist independently of the main conversation. See [resume subagents in Claude Code](/docs/en/sub-agents#resume-subagents) for compaction behavior and the `cleanupPeriodDays` cleanup period.
563553
from line 562
572562
573563This example creates a read-only analysis agent that can examine code but can't modify files or run commands.
574564
575<CodeGroup>
576 ```python Python theme={null}
577 import asyncio
578 from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
579
580
581 async def main():
582 async for message in query(
583 prompt="Analyze the architecture of this codebase",
584 options=ClaudeAgentOptions(
585 allowed_tools=["Read", "Grep", "Glob", "Agent"],
586 agents={
587 "code-analyzer": AgentDefinition(
588 description="Static code analysis and architecture review",
589 prompt="""You are a code architecture analyst. Analyze code structure,
590 identify patterns, and suggest improvements without making changes.""",
591 # Read-only tools: no Edit, Write, or Bash access
592 tools=["Read", "Grep", "Glob"],
593 )
594 },
595 ),
596 ):
597 if hasattr(message, "result"):
598 print(message.result)
599
600
601 asyncio.run(main())
602 ```
603
604 ```typescript TypeScript theme={null}
605 import { query } from "@anthropic-ai/claude-agent-sdk";
606
607 for await (const message of query({
608 prompt: "Analyze the architecture of this codebase",
609 options: {
610 allowedTools: ["Read", "Grep", "Glob", "Agent"],
611 agents: {
612 "code-analyzer": {
613 description: "Static code analysis and architecture review",
614 prompt: `You are a code architecture analyst. Analyze code structure,
615 identify patterns, and suggest improvements without making changes.`,
616 // Read-only tools: no Edit, Write, or Bash access
617 tools: ["Read", "Grep", "Glob"]
618 }
565```python Python theme={null}
566import asyncio
567from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
568
569
570async def main():
571 async for message in query(
572 prompt="Analyze the architecture of this codebase",
573 options=ClaudeAgentOptions(
574 allowed_tools=["Read", "Grep", "Glob", "Agent"],
575 agents={
576 "code-analyzer": AgentDefinition(
577 description="Static code analysis and architecture review",
578 prompt="""You are a code architecture analyst. Analyze code structure,
579identify patterns, and suggest improvements without making changes.""",
580 # Read-only tools: no Edit, Write, or Bash access
581 tools=["Read", "Grep", "Glob"],
582 )
583 },
584 ),
585 ):
586 if hasattr(message, "result"):
587 print(message.result)
588
589
590asyncio.run(main())
591```
592
593```typescript TypeScript theme={null}
594import { query } from "@anthropic-ai/claude-agent-sdk";
595
596for await (const message of query({
597 prompt: "Analyze the architecture of this codebase",
598 options: {
599 allowedTools: ["Read", "Grep", "Glob", "Agent"],
600 agents: {
601 "code-analyzer": {
602 description: "Static code analysis and architecture review",
603 prompt: `You are a code architecture analyst. Analyze code structure,
604identify patterns, and suggest improvements without making changes.`,
605 // Read-only tools: no Edit, Write, or Bash access
606 tools: ["Read", "Grep", "Glob"]
619607 }
620608 }
621 })) {
622 if ("result" in message) console.log(message.result);
623 }
624 ```
625</CodeGroup>
609 }
610})) {
611 if ("result" in message) console.log(message.result);
612}
613```
626614
627615### Common tool combinations
628616
from line 623
635623
636624## Cap subagent depth, concurrency, and spend
637625
638<Note>
639 This section describes TypeScript SDK v0.3.219 and Python SDK v0.2.127 and later, the releases that bundle Claude Code v2.1.219 or later. On earlier releases, some of these limits are missing or default differently, so upgrade before you rely on them to bound a run. The [environment variable reference](/docs/en/env-vars) and [turns and budget](/docs/en/agent-sdk/agent-loop#turns-and-budget) record the Claude Code version that added each variable and the spend cap's subagent enforcement.
640</Note>
626> [!NOTE]
627>
628> This section describes TypeScript SDK v0.3.219 and Python SDK v0.2.127 and later, the releases that bundle Claude Code v2.1.219 or later. On earlier releases, some of these limits are missing or default differently, so upgrade before you rely on them to bound a run. The [environment variable reference](/docs/en/env-vars) and [turns and budget](/docs/en/agent-sdk/agent-loop#turns-and-budget) record the Claude Code version that added each variable and the spend cap's subagent enforcement.
641629
642630Claude decides on its own when to spawn a subagent and how many to spawn. Each subagent makes its own API requests, which count toward the query's `total_cost_usd`, and a subagent can spawn subagents of its own, so one prompt can grow into a tree of agents.
643631
from line 639
651639
652640The two SDKs treat the `env` option differently: the TypeScript SDK replaces the subprocess environment with it, so spread `process.env` into it to keep variables like `PATH`, while the Python SDK merges it into the inherited environment. This example turns nesting off, allows at most five subagents at a time, and stops the query once the estimated spend reaches \$5:
653641
654<CodeGroup>
655 ```python Python theme={null}
656 import asyncio
657 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
658
659
660 async def main():
661 try:
662 async for message in query(
663 prompt="Audit every service in this repo for unhandled promise rejections",
664 options=ClaudeAgentOptions(
665 allowed_tools=["Read", "Grep", "Glob", "Agent"],
666 # env is merged on top of the inherited environment
667 env={
668 "CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH": "1",
669 "CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS": "5",
670 },
671 max_budget_usd=5.0,
672 ),
673 ):
674 if isinstance(message, ResultMessage):
675 print(f"{message.subtype}: ${message.total_cost_usd}")
676 except Exception as error:
677 # A single-shot query() raises after yielding an error result,
678 # so the budget-capped result has already been printed above.
679 print(f"Session ended with an error: {error}")
680
681
682 asyncio.run(main())
683 ```
684
685 ```typescript TypeScript theme={null}
686 import { query } from "@anthropic-ai/claude-agent-sdk";
687
688 try {
689 for await (const message of query({
690 prompt: "Audit every service in this repo for unhandled promise rejections",
691 options: {
692 allowedTools: ["Read", "Grep", "Glob", "Agent"],
693 // env replaces the subprocess environment, so spread process.env to keep PATH
694 env: {
695 ...process.env,
696 CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: "1",
697 CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: "5",
698 },
699 maxBudgetUsd: 5,
642```python Python theme={null}
643import asyncio
644from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
645
646
647async def main():
648 try:
649 async for message in query(
650 prompt="Audit every service in this repo for unhandled promise rejections",
651 options=ClaudeAgentOptions(
652 allowed_tools=["Read", "Grep", "Glob", "Agent"],
653 # env is merged on top of the inherited environment
654 env={
655 "CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH": "1",
656 "CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS": "5",
657 },
658 max_budget_usd=5.0,
659 ),
660 ):
661 if isinstance(message, ResultMessage):
662 print(f"{message.subtype}: ${message.total_cost_usd}")
663 except Exception as error:
664 # A single-shot query() raises after yielding an error result,
665 # so the budget-capped result has already been printed above.
666 print(f"Session ended with an error: {error}")
667
668
669asyncio.run(main())
670```
671
672```typescript TypeScript theme={null}
673import { query } from "@anthropic-ai/claude-agent-sdk";
674
675try {
676 for await (const message of query({
677 prompt: "Audit every service in this repo for unhandled promise rejections",
678 options: {
679 allowedTools: ["Read", "Grep", "Glob", "Agent"],
680 // env replaces the subprocess environment, so spread process.env to keep PATH
681 env: {
682 ...process.env,
683 CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: "1",
684 CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: "5",
700685 },
701 })) {
702 if (message.type === "result") {
703 console.log(`${message.subtype}: $${message.total_cost_usd}`);
704 }
686 maxBudgetUsd: 5,
687 },
688 })) {
689 if (message.type === "result") {
690 console.log(`${message.subtype}: $${message.total_cost_usd}`);
705691 }
706 } catch (error) {
707 // A single-shot query() throws after yielding an error result,
708 // so the budget-capped result has already been logged above.
709 console.error(`Session ended with an error: ${error}`);
710 }
711 ```
712</CodeGroup>
692 }
693} catch (error) {
694 // A single-shot query() throws after yielding an error result,
695 // so the budget-capped result has already been logged above.
696 console.error(`Session ended with an error: ${error}`);
697}
698```
713699
714700What you see depends on which limit, if any, the query reaches:
715701
716702
No line in this hunk matches that.