Claude Agent SDK 子代理 - 上下文隔离 / 并行任务
在 Claude Agent SDK 应用程序中定义和调用子代理以隔离上下文、并行运行任务,并应用专门的指令。
SDK 中的子代理
定义和调用子代理以隔离上下文、并行运行任务,以及在 Claude Agent SDK 应用程序中应用专门的指令。
子代理是您的主代理可以生成的独立代理实例,用于处理专注的子任务。 使用子代理来隔离专注子任务的上下文、并行运行多个分析,以及应用专门的指令,而不会使主代理的提示词过于复杂。
本指南说明如何使用 agents 参数在 SDK 中定义和使用子代理。
概述
您可以通过三种方式创建子代理:
- 以编程方式:在您的
query()选项中使用agents参数(TypeScript、Python) - 基于文件系统:在
.claude/agents/目录中将代理定义为 markdown 文件(请参阅将子代理定义为文件) - 内置通用代理:Claude 可以随时通过 Agent 工具调用内置的
general-purpose子代理,无需您定义任何内容
本指南重点介绍编程方法,这是 SDK 应用程序的推荐方法。
定义子代理时,Claude 根据每个子代理的 description 字段确定是否调用它。编写清晰的描述,说明何时应使用子代理,Claude 将自动委派适当的任务。您也可以在提示词中按名称显式请求子代理(例如,“使用代码审查员代理来…”)。
使用子代理的好处
上下文隔离
每个子代理在其自己的新对话中运行。中间工具调用和结果保留在子代理内部;只有其最终消息返回到父代理。
示例: research-assistant 子代理可以探索数十个文件,而这些内容都不会在主对话中累积。父代理收到的是简洁的摘要,而不是子代理读取的每个文件。
并行化
多个子代理可以并发运行,大大加快复杂工作流的速度。
示例: 在代码审查期间,您可以同时运行 style-checker、security-scanner 和 test-coverage 子代理,将审查时间从几分钟减少到几秒钟。
专门的指令和知识
每个子代理都可以有定制的系统提示词,具有特定的专业知识、最佳实践和约束。
示例: database-migration 子代理可以具有关于 SQL 最佳实践、回滚策略和数据完整性检查的详细知识,这些在主代理的指令中将是不必要的噪音。
工具限制
子代理可以限制为特定工具,降低意外操作的风险。
示例: doc-reviewer 子代理可能只能访问 Read 和 Grep 工具,确保它可以分析但永远不会意外修改您的文档文件。
创建子代理
以编程方式定义(推荐)
使用 agents 参数直接在代码中定义子代理。此示例创建两个子代理:一个具有只读访问权限的代码审查员和一个可以执行命令的测试运行器。Agent 工具必须包含在 allowedTools 中,因为 Claude 通过 Agent 工具调用子代理。
Python:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
async def main():
async for message in query(
prompt="Review the authentication module for security issues",
options=ClaudeAgentOptions(
# Agent tool is required for subagent invocation
allowed_tools=["Read", "Grep", "Glob", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
prompt="""You are a code review specialist with expertise in security, performance, and best practices.
When reviewing code:
- Identify security vulnerabilities
- Check for performance issues
- Verify adherence to coding standards
- Suggest specific improvements
Be thorough but concise in your feedback.""",
tools=["Read", "Grep", "Glob"],
model="sonnet",
),
"test-runner": AgentDefinition(
description="Runs and analyzes test suites. Use for test execution and coverage analysis.",
prompt="""You are a test execution specialist. Run tests and provide clear analysis of results.
Focus on:
- Running test commands
- Analyzing test output
- Identifying failing tests
- Suggesting fixes for failures""",
tools=["Bash", "Read", "Grep"],
),
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
TypeScript:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Review the authentication module for security issues",
options: {
allowedTools: ["Read", "Grep", "Glob", "Agent"],
agents: {
"code-reviewer": {
description:
"Expert code review specialist. Use for quality, security, and maintainability reviews.",
prompt: `You are a code review specialist with expertise in security, performance, and best practices.
When reviewing code:
- Identify security vulnerabilities
- Check for performance issues
- Verify adherence to coding standards
- Suggest specific improvements
Be thorough but concise in your feedback.`,
tools: ["Read", "Grep", "Glob"],
model: "sonnet"
},
"test-runner": {
description:
"Runs and analyzes test suites. Use for test execution and coverage analysis.",
prompt: `You are a test execution specialist. Run tests and provide clear analysis of results.
Focus on:
- Running test commands
- Analyzing test output
- Identifying failing tests
- Suggesting fixes for failures`,
tools: ["Bash", "Read", "Grep"]
}
}
}
})) {
if ("result" in message) console.log(message.result);
}
AgentDefinition 配置
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
description | string | 是 | 何时使用此代理的自然语言描述 |
prompt | string | 是 | 代理的系统提示词,定义其角色和行为 |
tools | string[] | 否 | 允许的工具名称数组。如果省略,继承所有工具 |
disallowedTools | string[] | 否 | 要从代理的工具集中移除的工具名称数组 |
model | string | 否 | 此代理的模型覆盖。接受别名,如 'sonnet'、'opus'、'haiku'、'inherit',或完整的模型 ID。如果省略,默认为主模型 |
skills | string[] | 否 | 在启动时预加载到代理上下文中的技能名称列表。未列出的技能仍可通过 Skill 工具调用 |
memory | 'user' | 'project' | 'local' | 否 | 此代理的内存源 |
mcpServers | (string | object)[] | 否 | 此代理可用的 MCP 服务器,按名称或内联配置 |
maxTurns | number | 否 | 代理停止前的最大代理轮数 |
background | boolean | 否 | 调用时将此代理作为非阻塞后台任务运行 |
effort | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | number | 否 | 此代理的推理工作量级别 |
permissionMode | PermissionMode | 否 | 此代理内工具执行的权限模式 |
在 Python SDK 中,这些字段名称使用 camelCase 以匹配线路格式。
ℹ️ 子代理无法生成自己的子代理。不要在子代理的
tools数组中包含Agent。
基于文件系统的定义(替代方案)
您也可以在 .claude/agents/ 目录中将子代理定义为 markdown 文件。有关此方法的详细信息,请参阅 Claude Code 子代理文档。以编程方式定义的代理优先于具有相同名称的基于文件系统的代理。
ℹ️ 即使不定义自定义子代理,当
Agent在您的allowedTools中时,Claude 也可以生成内置的general-purpose子代理。这对于委派研究或探索任务而无需创建专门的代理很有用。
子代理继承的内容
子代理的上下文窗口从新开始(无父对话),但不是空的。从父代理到子代理的唯一通道是 Agent 工具的提示词字符串,因此请直接在该提示词中包含子代理需要的任何文件路径、错误消息或决策。
| 子代理接收 | 子代理不接收 |
|---|---|
其自己的系统提示词(AgentDefinition.prompt)和 Agent 工具的提示词 | 父代理的对话历史或工具结果 |
项目 CLAUDE.md(通过 settingSources 加载) | 预加载的技能内容,除非在 AgentDefinition.skills 中列出 |
工具定义(从父代理继承,或 tools 中的子集) | 父代理的系统提示词 |
ℹ️ 父代理逐字接收子代理的最终消息作为 Agent 工具结果,但可能在其自己的响应中总结它。要在面向用户的响应中逐字保留子代理输出,请在您传递给主
query()调用的提示词或systemPrompt选项中包含一条指令。
调用子代理
自动调用
Claude 根据任务和每个子代理的 description 自动决定何时调用子代理。例如,如果您定义了一个 performance-optimizer 子代理,其描述为”用于查询调优的性能优化专家”,当您的提示词提到优化查询时,Claude 将调用它。
编写清晰、具体的描述,以便 Claude 可以将任务匹配到正确的子代理。
显式调用
要保证 Claude 使用特定的子代理,请在您的提示词中按名称提及它:
"Use the code-reviewer agent to check the authentication module"
这绕过自动匹配并直接调用命名的子代理。
动态代理配置
您可以根据运行时条件动态创建代理定义。此示例创建一个安全审查员,具有不同的严格级别,对严格审查使用更强大的模型。
Python:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
def create_security_agent(security_level: str) -> AgentDefinition:
is_strict = security_level == "strict"
return AgentDefinition(
description="Security code reviewer",
prompt=f"You are a {'strict' if is_strict else 'balanced'} security reviewer...",
tools=["Read", "Grep", "Glob"],
model="opus" if is_strict else "sonnet",
)
async def main():
async for message in query(
prompt="Review this PR for security issues",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob", "Agent"],
agents={
"security-reviewer": create_security_agent("strict")
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
TypeScript:
import { query, type AgentDefinition } from "@anthropic-ai/claude-agent-sdk";
function createSecurityAgent(securityLevel: "basic" | "strict"): AgentDefinition {
const isStrict = securityLevel === "strict";
return {
description: "Security code reviewer",
prompt: `You are a ${isStrict ? "strict" : "balanced"} security reviewer...`,
tools: ["Read", "Grep", "Glob"],
model: isStrict ? "opus" : "sonnet"
};
}
for await (const message of query({
prompt: "Review this PR for security issues",
options: {
allowedTools: ["Read", "Grep", "Glob", "Agent"],
agents: {
"security-reviewer": createSecurityAgent("strict")
}
}
})) {
if ("result" in message) console.log(message.result);
}
检测子代理调用
子代理通过 Agent 工具调用。要检测何时调用子代理,请检查 tool_use 块,其中 name 是 "Agent"。来自子代理上下文内的消息包含 parent_tool_use_id 字段。
ℹ️ 工具名称在 Claude Code v2.1.63 中从
"Task"重命名为"Agent"。当前 SDK 版本在tool_use块中发出"Agent",但在system:init工具列表和result.permission_denials[].tool_name中仍使用"Task"。检查block.name中的两个值可确保跨 SDK 版本的兼容性。
此示例遍历流式消息,记录何时调用子代理以及后续消息何时源自该子代理的执行上下文。
ℹ️ 消息结构在 SDK 之间有所不同。在 Python 中,内容块直接通过
message.content访问。在 TypeScript 中,SDKAssistantMessage包装 Claude API 消息,因此内容通过message.message.content访问。
Python:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
async def main():
async for message in query(
prompt="Use the code-reviewer agent to review this codebase",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Expert code reviewer.",
prompt="Analyze code quality and suggest improvements.",
tools=["Read", "Glob", "Grep"],
)
},
),
):
# Check for subagent invocation
if hasattr(message, "content") and message.content:
for block in message.content:
if getattr(block, "type", None) == "tool_use" and block.name in (
"Task",
"Agent",
):
print(f"Subagent invoked: {block.input.get('subagent_type')}")
# Check if this message is from within a subagent's context
if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
print(" (running inside subagent)")
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
TypeScript:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Use the code-reviewer agent to review this codebase",
options: {
allowedTools: ["Read", "Glob", "Grep", "Agent"],
agents: {
"code-reviewer": {
description: "Expert code reviewer.",
prompt: "Analyze code quality and suggest improvements.",
tools: ["Read", "Glob", "Grep"]
}
}
}
})) {
const msg = message as any;
for (const block of msg.message?.content ?? []) {
if (block.type === "tool_use" && (block.name === "Task" || block.name === "Agent")) {
console.log(`Subagent invoked: ${block.input.subagent_type}`);
}
}
if (msg.parent_tool_use_id) {
console.log(" (running inside subagent)");
}
if ("result" in message) {
console.log(message.result);
}
}
恢复子代理
子代理可以恢复以继续中断的地方。恢复的子代理保留其完整的对话历史,包括所有先前的工具调用、结果和推理。子代理从停止的地方继续,而不是重新开始。
当子代理完成时,Claude 在 Agent 工具结果中接收其代理 ID。要以编程方式恢复子代理:
- 捕获会话 ID:在第一个查询期间从消息中提取
session_id - 提取代理 ID:从消息内容中解析
agentId - 恢复会话:在第二个查询的选项中传递
resume: sessionId,并在您的提示词中包含代理 ID
ℹ️ 您必须恢复同一会话以访问子代理的记录。默认情况下,每个
query()调用都会启动一个新会话,因此请传递resume: sessionId以在同一会话中继续。如果您使用的是自定义代理(而不是内置代理),您还需要在两个查询的
agents参数中传递相同的代理定义。
下面的示例演示了此流程:
TypeScript:
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
// Helper to extract agentId from message content
function extractAgentId(message: SDKMessage): string | undefined {
if (!("message" in message)) return undefined;
const content = JSON.stringify(message.message.content);
const match = content.match(/agentId:\s*([a-f0-9-]+)/);
return match?.[1];
}
let agentId: string | undefined;
let sessionId: string | undefined;
// First invocation - use the Explore agent to find API endpoints
for await (const message of query({
prompt: "Use the Explore agent to find all API endpoints in this codebase",
options: { allowedTools: ["Read", "Grep", "Glob", "Agent"] }
})) {
if ("session_id" in message) sessionId = message.session_id;
const extractedId = extractAgentId(message);
if (extractedId) agentId = extractedId;
if ("result" in message) console.log(message.result);
}
// Second invocation - resume and ask follow-up
if (agentId && sessionId) {
for await (const message of query({
prompt: `Resume agent ${agentId} and list the top 3 most complex endpoints`,
options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], resume: sessionId }
})) {
if ("result" in message) console.log(message.result);
}
}
Python:
import asyncio
import json
import re
from claude_agent_sdk import query, ClaudeAgentOptions
def extract_agent_id(text: str) -> str | None:
"""Extract agentId from Agent tool result text."""
match = re.search(r"agentId:\s*([a-f0-9-]+)", text)
return match.group(1) if match else None
async def main():
agent_id = None
session_id = None
# First invocation
async for message in query(
prompt="Use the Explore agent to find all API endpoints in this codebase",
options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"]),
):
if hasattr(message, "session_id"):
session_id = message.session_id
if hasattr(message, "content"):
content_str = json.dumps(message.content, default=str)
extracted = extract_agent_id(content_str)
if extracted:
agent_id = extracted
if hasattr(message, "result"):
print(message.result)
# Second invocation - resume and ask follow-up
if agent_id and session_id:
async for message in query(
prompt=f"Resume agent {agent_id} and list the top 3 most complex endpoints",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob", "Agent"], resume=session_id
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
子代理记录独立于主对话而持久存在:
- 主对话压缩:当主对话压缩时,子代理记录不受影响。它们存储在单独的文件中。
- 会话持久性:子代理记录在其会话内持久存在。您可以通过恢复同一会话在重启 Claude Code 后恢复子代理。
- 自动清理:记录根据
cleanupPeriodDays设置进行清理(默认:30 天)。
工具限制
子代理可以通过 tools 字段具有受限的工具访问:
- 省略该字段:代理继承所有可用工具(默认)
- 指定工具:代理只能使用列出的工具
此示例创建一个只读分析代理,可以检查代码但无法修改文件或运行命令。
Python:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
async def main():
async for message in query(
prompt="Analyze the architecture of this codebase",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob", "Agent"],
agents={
"code-analyzer": AgentDefinition(
description="Static code analysis and architecture review",
prompt="""You are a code architecture analyst. Analyze code structure,
identify patterns, and suggest improvements without making changes.""",
tools=["Read", "Grep", "Glob"],
)
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
TypeScript:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Analyze the architecture of this codebase",
options: {
allowedTools: ["Read", "Grep", "Glob", "Agent"],
agents: {
"code-analyzer": {
description: "Static code analysis and architecture review",
prompt: `You are a code architecture analyst. Analyze code structure,
identify patterns, and suggest improvements without making changes.`,
tools: ["Read", "Grep", "Glob"]
}
}
}
})) {
if ("result" in message) console.log(message.result);
}
常见工具组合
| 用例 | 工具 | 描述 |
|---|---|---|
| 只读分析 | Read、Grep、Glob | 可以检查代码但不能修改或执行 |
| 测试执行 | Bash、Read、Grep | 可以运行命令并分析输出 |
| 代码修改 | Read、Edit、Write、Grep、Glob | 完整的读/写访问,无命令执行 |
| 完全访问 | 所有工具 | 从父代理继承所有工具(省略 tools 字段) |
故障排除
Claude 不委派给子代理
如果 Claude 直接完成任务而不是委派给您的子代理:
- 包含 Agent 工具:子代理通过 Agent 工具调用,因此它必须在
allowedTools中 - 使用显式提示:在您的提示词中按名称提及子代理(例如,“使用代码审查员代理来…”)
- 编写清晰的描述:准确解释何时应使用子代理,以便 Claude 可以适当地匹配任务
基于文件系统的代理未加载
在 .claude/agents/ 中定义的代理仅在启动时加载。如果在 Claude Code 运行时创建新的代理文件,请重启会话以加载它。
Windows:长提示词失败
在 Windows 上,具有非常长提示词的子代理可能因命令行长度限制(8191 个字符)而失败。保持提示词简洁或使用基于文件系统的代理来处理复杂指令。
相关文档
- Claude Code 子代理:包括基于文件系统的定义的全面子代理文档
- SDK 概述:Claude Agent SDK 入门
本文翻译自 Anthropic Claude Code 官方文档,最近一次同步:2025-05-01。