7> Define and invoke subagents to isolate context, run tasks in parallel, and apply specialized instructions in your Claude Agent SDK applications.7> Define and invoke subagents to isolate context, run tasks in parallel, and apply specialized instructions in your Claude Agent SDK applications.
8 8
9Subagents are separate agent instances that your main agent can spawn to handle focused subtasks.9Subagents are separate agent instances that your main agent can spawn to handle focused subtasks.
10Use subagents to isolate context for focused subtasks, run multiple analyses in parallel, and apply specialized instructions without bloating the main agent's prompt.10Use subagents to isolate context for focused subtasks, run multiple analyses in parallel, and apply specialized instructions without adding to the main agent's prompt.
11 11
12This guide explains how to define and use subagents in the SDK using the `agents` parameter.12This guide explains how to define and use subagents in the SDK using the `agents` parameter.
13 13
33 33
34### Parallelization34### Parallelization
35 35
36Multiple subagents can run concurrently, dramatically speeding up complex workflows.36Multiple subagents can run concurrently, so independent subtasks finish in the time of the slowest one rather than the sum of all of them.
37 37
38**Example:** during a code review, you can run `style-checker`, `security-scanner`, and `test-coverage` subagents simultaneously, reducing review time from minutes to seconds.38**Example:** during a code review, you can run `style-checker`, `security-scanner`, and `test-coverage` subagents simultaneously instead of sequentially.
39 39
40### Specialized instructions and knowledge40### Specialized instructions and knowledge
41 41
169| `skills` | `string[]` | No | List of skill names to preload into the agent's context at startup. Unlisted skills remain invocable through the Skill tool |169| `skills` | `string[]` | No | List of skill names to preload into the agent's context at startup. Unlisted skills remain invocable through the Skill tool |
170| `memory` | `'user' \| 'project' \| 'local'` | No | Memory source for this agent |170| `memory` | `'user' \| 'project' \| 'local'` | No | Memory source for this agent |
171| `mcpServers` | `(string \| object)[]` | No | MCP servers available to this agent, by name or inline config |171| `mcpServers` | `(string \| object)[]` | No | MCP servers available to this agent, by name or inline config |
172| `initialPrompt` | `string` | No | Auto-submitted as the first user turn when this agent runs as the main thread agent |
172| `maxTurns` | `number` | No | Maximum number of agentic turns before the agent stops |173| `maxTurns` | `number` | No | Maximum number of agentic turns before the agent stops |
173| `background` | `boolean` | No | Run this agent as a non-blocking background task when invoked |174| `background` | `boolean` | No | Run this agent as a non-blocking background task when invoked |
174| `effort` | `'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max' \| number` | No | Reasoning effort level for this agent |175| `effort` | `'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max' \| number` | No | Reasoning effort level for this agent |
193A subagent's context window starts fresh (no parent conversation) but isn't empty. The only channel from parent to subagent is the Agent tool's prompt string, so include any file paths, error messages, or decisions the subagent needs directly in that prompt.194A subagent's context window starts fresh (no parent conversation) but isn't empty. The only channel from parent to subagent is the Agent tool's prompt string, so include any file paths, error messages, or decisions the subagent needs directly in that prompt.
194 195
195| The subagent receives | The subagent does not receive |196| The subagent receives | The subagent does not receive |
196| :--------------------------------------------------------------------------- | :----------------------------------------------------------------- |197| :------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------- |
197| Its own system prompt (`AgentDefinition.prompt`) and the Agent tool's prompt | The parent's conversation history or tool results |198| Its own system prompt (`AgentDefinition.prompt`) and the Agent tool's prompt | The parent's conversation history or tool results |
198| Project CLAUDE.md (loaded via `settingSources`) | Preloaded skill content, unless listed in `AgentDefinition.skills` |199| Project CLAUDE.md (loaded via [`settingSources`](/en/agent-sdk/claude-code-features#control-filesystem-settings-with-settingsources)) | Preloaded skill content, unless listed in `AgentDefinition.skills` |
199| Tool definitions (inherited from parent, or the subset in `tools`) | The parent's system prompt |200| Tool definitions (inherited from parent, or the subset in `tools`) | The parent's system prompt |
200 201
201<Note>202<Note>
304 The tool name was renamed from `"Task"` to `"Agent"` in Claude Code v2.1.63. Current SDK releases emit `"Agent"` in `tool_use` blocks but still use `"Task"` in the `system:init` tools list and in `result.permission_denials[].tool_name`. Checking both values in `block.name` ensures compatibility across SDK versions.305 The tool name was renamed from `"Task"` to `"Agent"` in Claude Code v2.1.63. Current SDK releases emit `"Agent"` in `tool_use` blocks but still use `"Task"` in the `system:init` tools list and in `result.permission_denials[].tool_name`. Checking both values in `block.name` ensures compatibility across SDK versions.
305</Note>306</Note>
306 307
307This example iterates through streamed messages, logging when a subagent is invoked and when subsequent messages originate from within that subagent's execution context.308The message structure differs between SDKs. In Python, content blocks are accessed directly via `message.content`. In TypeScript, `SDKAssistantMessage` wraps the Claude API message, so content is accessed via `message.message.content`.
308 309
309<Note>310This example iterates through streamed messages, logging when a subagent is invoked and when subsequent messages originate from within that subagent's execution context.
310 The message structure differs between SDKs. In Python, content blocks are accessed directly via `message.content`. In TypeScript, `SDKAssistantMessage` wraps the Claude API message, so content is accessed via `message.message.content`.
311</Note>
312 311
313<CodeGroup>312<CodeGroup>
314 ```python Python theme={null}313 ```python Python theme={null}
393 392
394Subagents can be resumed to continue where they left off. Resumed subagents retain their full conversation history, including all previous tool calls, results, and reasoning. The subagent picks up exactly where it stopped rather than starting fresh.393Subagents can be resumed to continue where they left off. Resumed subagents retain their full conversation history, including all previous tool calls, results, and reasoning. The subagent picks up exactly where it stopped rather than starting fresh.
395 394
396When a subagent completes, Claude receives its agent ID in the Agent tool result. To resume a subagent programmatically:395When a subagent completes, the Agent tool result includes a text block containing `agentId: <id>`. The built-in [`Explore` and `Plan` agents](/en/sub-agents#built-in-subagents) are one-shot and omit this trailer, so use a custom agent or `general-purpose` when you need to resume. To resume a subagent programmatically:
397 396
3981. **Capture the session ID**: Extract `session_id` from messages during the first query3971. **Capture the session ID**: Extract `session_id` from messages during the first query
3992. **Extract the agent ID**: Parse `agentId` from the message content3982. **Extract the agent ID**: Parse `agentId` from the Agent tool result text
4003. **Resume the session**: Pass `resume: sessionId` in the second query's options, and include the agent ID in your prompt3993. **Resume the session**: Pass `resume: sessionId` in the second query's options, and include the agent ID in your prompt
401 400
402<Note>401<Note>
403 You must resume the same session to access the subagent's transcript. Each `query()` call starts a new session by default, so pass `resume: sessionId` to continue in the same session.402 You must resume the same session to access the subagent's transcript. Each `query()` call starts a new session by default, so pass `resume: sessionId` to continue in the same session.
404 403
405 If you're using a custom agent (not a built-in one), you also need to pass the same agent definition in the `agents` parameter for both queries.404 When using a custom agent, pass the same agent definition in the `agents` parameter for both queries.
406</Note>405</Note>
407 406
408The example below demonstrates this flow: the first query runs a subagent and captures the session ID and agent ID, then the second query resumes the session to ask a follow-up question that requires context from the first analysis.407The 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.
409 408
410<CodeGroup>409<CodeGroup>
411 ```typescript TypeScript theme={null}
412 import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
413
414 // Helper to extract agentId from message content
415 // Stringify to avoid traversing different block types (TextBlock, ToolResultBlock, etc.)
416 function extractAgentId(message: SDKMessage): string | undefined {
417 if (message.type !== "assistant" && message.type !== "user") return undefined;
418 // Stringify the content so we can search it without traversing nested blocks
419 const content = JSON.stringify(message.message.content);
420 const match = content.match(/agentId:\s*([a-f0-9-]+)/);
421 return match?.[1];
422 }
423
424 let agentId: string | undefined;
425 let sessionId: string | undefined;
426
427 // First invocation - use the Explore agent to find API endpoints
428 for await (const message of query({
429 prompt: "Use the Explore agent to find all API endpoints in this codebase",
430 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"] }
431 })) {
432 // Capture session_id from ResultMessage (needed to resume this session)
433 if ("session_id" in message) sessionId = message.session_id;
434 // Search message content for the agentId (appears in Agent tool results)
435 const extractedId = extractAgentId(message);
436 if (extractedId) agentId = extractedId;
437 // Print the final result
438 if ("result" in message) console.log(message.result);
439 }
440
441 // Second invocation - resume and ask follow-up
442 if (agentId && sessionId) {
443 for await (const message of query({
444 prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
445 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], resume: sessionId }
446 })) {
447 if ("result" in message) console.log(message.result);
448 }
449 }
450 ```
451
452 ```python Python theme={null}410 ```python Python theme={null}
453 import asyncio411 import asyncio
454 import json
455 import re412 import re
456 from claude_agent_sdk import query, ClaudeAgentOptions413 from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolResultBlock
414
415 AGENTS = {
416 "endpoint-finder": AgentDefinition(
417 description="Locates and catalogs API endpoints in a codebase.",
418 prompt="You find and document API endpoints. Report each endpoint's path, method, and handler.",
419 tools=["Read", "Grep", "Glob"],
420 )
421 }
457 422
458 423
459 def extract_agent_id(text: str) -> str | None:424 def extract_agent_id(block: ToolResultBlock) -> str | None:
460 """Extract agentId from Agent tool result text."""425 """Extract agentId from an Agent tool result's text content."""
461 match = re.search(r"agentId:\s*([a-f0-9-]+)", text)426 parts = block.content if isinstance(block.content, list) else [{"text": block.content}]
462 return match.group(1) if match else None427 for part in parts:
428 if match := re.search(r"agentId:\s*([\w-]+)", part.get("text") or ""):
429 return match.group(1)
430 return None
463 431
464 432
465 async def main():433 async def main():
466 agent_id = None434 agent_id = None
467 session_id = None435 session_id = None
468 436
469 # First invocation - use the Explore agent to find API endpoints437 # First invocation - run the endpoint-finder subagent
470 async for message in query(438 async for message in query(
471 prompt="Use the Explore agent to find all API endpoints in this codebase",439 prompt="Use the endpoint-finder agent to find all API endpoints in this codebase",
472 options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"]),440 options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS),
473 ):441 ):
474 # Capture session_id from ResultMessage (needed to resume this session)442 # Capture session_id from ResultMessage (needed to resume this session)
475 if hasattr(message, "session_id"):443 if hasattr(message, "session_id"):
476 session_id = message.session_id444 session_id = message.session_id
477 # Search message content for the agentId (appears in Agent tool results)445 # Search tool results for the agentId trailer
478 if hasattr(message, "content"):446 for block in getattr(message, "content", None) or []:
479 # Stringify the content so we can search it without traversing nested blocks447 if isinstance(block, ToolResultBlock):
480 content_str = json.dumps(message.content, default=str)448 agent_id = extract_agent_id(block) or agent_id
481 extracted = extract_agent_id(content_str)
482 if extracted:
483 agent_id = extracted
484 # Print the final result449 # Print the final result
485 if hasattr(message, "result"):450 if hasattr(message, "result"):
486 print(message.result)451 print(message.result)
490 async for message in query(455 async for message in query(
491 prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",456 prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",
492 options=ClaudeAgentOptions(457 options=ClaudeAgentOptions(
493 allowed_tools=["Read", "Grep", "Glob", "Agent"], resume=session_id458 allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS, resume=session_id
494 ),459 ),
495 ):460 ):
496 if hasattr(message, "result"):461 if hasattr(message, "result"):
499 464
500 asyncio.run(main())465 asyncio.run(main())
501 ```466 ```
467
468 ```typescript TypeScript theme={null}
469 import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
470
471 const agents = {
472 "endpoint-finder": {
473 description: "Locates and catalogs API endpoints in a codebase.",
474 prompt: "You find and document API endpoints. Report each endpoint's path, method, and handler.",
475 tools: ["Read", "Grep", "Glob"]
476 }
477 };
478
479 // Stringify content to search for agentId without traversing nested block types
480 function extractAgentId(message: SDKMessage): string | undefined {
481 if (message.type !== "assistant" && message.type !== "user") return undefined;
482 const content = JSON.stringify(message.message.content);
483 const match = content.match(/agentId:\s*([\w-]+)/);
484 return match?.[1];
485 }
486
487 let agentId: string | undefined;
488 let sessionId: string | undefined;
489
490 // First invocation - run the endpoint-finder subagent
491 for await (const message of query({
492 prompt: "Use the endpoint-finder agent to find all API endpoints in this codebase",
493 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents }
494 })) {
495 // Capture session_id from ResultMessage (needed to resume this session)
496 if ("session_id" in message) sessionId = message.session_id;
497 // Search message content for the agentId (appears in Agent tool results)
498 const extractedId = extractAgentId(message);
499 if (extractedId) agentId = extractedId;
500 // Print the final result
501 if ("result" in message) console.log(message.result);
502 }
503
504 // Second invocation - resume and ask follow-up
505 if (agentId && sessionId) {
506 for await (const message of query({
507 prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
508 options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents, resume: sessionId }
509 })) {
510 if ("result" in message) console.log(message.result);
511 }
512 }
513 ```
502</CodeGroup>514</CodeGroup>
503 515
504Subagent transcripts persist independently of the main conversation:516Subagent transcripts persist independently of the main conversation: