SDK 里的子智能体
在 Agent SDK 里定义和调用子智能体:编程方式与文件系统方式、AgentDefinition 字段、子智能体继承什么、自动与显式调用、动态配置、检测调用、恢复子智能体、工具限制、限制深度并发与花费、动态工作流和排障。
子智能体是你的主智能体可以派生出来处理聚焦子任务的独立智能体实例。用它们来隔离上下文、并行运行多项分析,并在不增加主智能体提示的情况下应用专门的说明。
概览
你可以用三种方式创建子智能体:
- 编程方式:在
query()选项里使用agents参数(见 TypeScript 和 Python 参考) - 基于文件系统:把智能体定义为
.claude/agents/目录里的 markdown 文件(见「把子智能体定义为文件」) - 内置的 general-purpose:Claude 可以随时通过 Agent 工具调用内置的
general-purpose子智能体,不需要你定义任何东西
本指南聚焦于编程方式,这也是推荐给 SDK 应用的方式。
使用子智能体的好处
因为子智能体是独立的智能体实例,把工作委派给它们有四个好处:
- 上下文隔离:每个子智能体运行在自己的对话里,起始是全新的(除非该子智能体是 fork)。无论哪种情况,中间的工具调用和结果都留在子智能体内部,只有它的最终消息返回给父级。一个
research-assistant子智能体可以探索几十个文件,而这些内容一点也不会累积进主对话;父级收到的是简洁的摘要,而不是子智能体读过的每个文件(子智能体上下文里究竟有什么,见「子智能体继承什么」)。 - 并行化:多个子智能体可以并发运行,所以独立的子任务在最慢那个的时间内完成,而不是所有时间之和。代码审查时可以同时而不是依次运行
style-checker、security-scanner和test-coverage子智能体。 - 专门的说明和知识:每个子智能体可以有量身定做的系统提示,带有特定的专业知识、最佳实践和约束。一个
database-migration子智能体可以有关于 SQL 最佳实践、回滚策略和数据完整性检查的详细知识,这些放在主智能体的说明里会是不必要的噪音。 - 工具限制:子智能体可以被限制在特定工具上,降低意外动作的风险。
doc-reviewer子智能体可能只能访问 Read 和 Grep 工具,确保它能分析但绝不会意外修改你的文档文件。
创建子智能体
编程方式定义(推荐)
用 agents 参数直接在代码里定义子智能体。Claude 通过 Agent 工具调用子智能体。本页多数示例只打印最终结果;要确认 Claude 确实委派给了子智能体而不是直接回答,见「检测子智能体调用」。这个例子创建两个子智能体:一个只读的代码审查者,和一个能执行命令的测试运行者。
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(
# 自动批准这些工具
allowed_tools=["Read", "Grep", "Glob", "Agent"],
agents={
"code-reviewer": AgentDefinition(
# description 告诉 Claude 何时使用这个子智能体
description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
# prompt 定义子智能体的行为和专长
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 限制子智能体能做什么(这里是只读)
tools=["Read", "Grep", "Glob"],
# model 覆盖该子智能体的默认模型
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""",
# Bash 访问让这个子智能体能运行测试命令
tools=["Bash", "Read", "Grep"],
),
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())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 告诉 Claude 何时使用这个子智能体
description:
"Expert code review specialist. Use for quality, security, and maintainability reviews.",
// prompt 定义子智能体的行为和专长
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 限制子智能体能做什么(这里是只读)
tools: ["Read", "Grep", "Glob"],
// model 覆盖该子智能体的默认模型
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`,
// Bash 访问让这个子智能体能运行测试命令
tools: ["Bash", "Read", "Grep"]
}
}
}
})) {
if ("result" in message) console.log(message.result);
}AgentDefinition 配置
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
description | string | 是 | 何时使用该智能体的自然语言描述 |
prompt | string | 是 | 定义该智能体角色和行为的系统提示 |
tools | string[] | 否 | 允许的工具名数组;省略时继承子智能体可用的每个工具 |
disallowedTools | string[] | 否 | 要从智能体工具集里移除的工具名数组;也接受 MCP 服务器级模式:mcp__server 或 mcp__server__* 移除该服务器的每个工具,mcp__* 移除任何服务器的每个 MCP 工具 |
model | string | 否 | 该智能体的模型覆盖;接受 'fable'、'opus'、'sonnet'、'haiku'、'inherit' 等别名或完整模型 ID;'inherit' 使用主模型;省略时 Claude Code 按子智能体的模型顺序选择 |
skills | string[] | 否 | 启动时预加载进智能体上下文的 skill 名列表;未列出的 skills 仍可通过 Skill 工具调用 |
memory | 'user' | 'project' | 'local' | 否 | 该智能体的记忆来源 |
mcpServers | (string | object)[] | 否 | 该智能体可用的 MCP 服务器,按名字或内联配置 |
initialPrompt | string | 否 | 该智能体作为主线程智能体运行时自动作为第一个用户轮次提交;作为子智能体调用时忽略 |
maxTurns | number | 否 | 智能体停止前最大的智能体轮次数;达到限制时,Claude Code 返回标为部分的输出,你可以恢复该智能体继续(部分标记需要 Claude Code v2.1.246 及以上) |
background | boolean | 否 | 被调用时作为非阻塞的后台任务运行该智能体 |
omitClaudeMd | boolean | 否 | 该智能体作为子智能体运行时,不带用户、项目和本地 CLAUDE.md 文件(托管策略文件仍加载);作为主线程智能体运行时忽略;需要 TypeScript Agent SDK v0.3.271 及以上,Python SDK 的 AgentDefinition 没有这个字段 |
effort | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | number | 否 | 该智能体的推理努力级别 |
permissionMode | PermissionMode | 否 | 该智能体内工具执行的权限模式,何时适用由子智能体继承规则决定 |
在 Python SDK 里,disallowedTools 和 mcpServers 这类多词字段名保持 camelCase 拼写以匹配线上格式,而不遵循 Python 的 snake_case 约定(细节见 AgentDefinition 参考)。
子智能体默认在后台运行:省略 run_in_background 输入的 Agent 工具调用会启动后台子智能体,Claude 在需要先拿到结果才能继续时设 run_in_background: false。把 background 字段设为 true 可对特定智能体强制后台执行,不论 Claude 请求什么(Claude Code v2.1.198 之前,后台默认是逐步推出的,省略 run_in_background 的 Agent 工具调用可能同步运行子智能体)。子智能体也可以派生自己的子智能体;要限制嵌套多深、同时运行多少个子智能体以及一次查询花多少,见「限制子智能体的深度、并发和花费」。
基于文件系统的定义(替代方式)
你也可以把子智能体定义为 .claude/agents/ 目录里的 markdown 文件(这种方式的细节见 Claude Code 子智能体文档)。编程方式定义的智能体优先于同名的基于文件系统的智能体。当 Claude 调用不带 subagent_type 的 Agent 工具时,它得到内置的 general-purpose 子智能体,即使你没定义任何自己的智能体 Claude 也能派生它;设 CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1 会移除这个默认值,这样的调用会以 subagent_type is required 失败。
子智能体继承什么
除非子智能体是 fork,它的上下文窗口起始是全新的,没有父对话,但并非空的。你从父级传给子智能体的唯一内容是 Agent 工具的提示字符串,所以要把子智能体需要的任何文件路径、错误消息或决定直接包含在那个提示里。有 SendMessage 工具的子智能体一开始就有会话里其他已命名智能体的列表,所以它知道能给哪些名字发消息;Claude Code 自动把该列表加到子智能体的第一个轮次里;fork 不会得到该列表,因为它改为继承父对话。子智能体还继承主会话的扩展思考配置。下表列出非 fork 子智能体的上下文包含什么、不含什么:
| 子智能体接收 | 子智能体不接收 |
|---|---|
它自己的系统提示(AgentDefinition.prompt)和 Agent 工具的提示 | 父级的对话历史或工具结果 |
项目 CLAUDE.md(经 settingSources 加载),除非智能体设了 omitClaudeMd | 预加载的 skill 内容,除非列在 AgentDefinition.skills 里 |
工具定义(从父级继承,或 tools 里的子集,对后台运行做了过滤) | 父级的系统提示 |
父级把子智能体的最终消息作为 Agent 工具结果收到,但可能在自己的响应里对它做摘要。要在面向用户的响应里逐字保留子智能体的输出,在你传给主 query() 调用的提示或 systemPrompt 选项里加一条这样做的说明。在 v2.1.210 及以上,Claude Code 在父级读取之前扫描最终消息里形如指令的模式,扫描对三类模式的处理不同:控制标签模仿——Claude Code 就地中和只有 harness 才发出的标签(如 <system-reminder> 块),在开尖括号之后插入一个反斜杠,不删除任何东西;权限配置提及——Claude Code 原样保留对权限配置的引用,如 .claude/settings.json、bypassPermissions 或 --dangerously-skip-permissions;轮次标记——以 Human: 或 Assistant: 开头的行会在冒号前加一个反斜杠,使消息无法模仿对话轮次的边界。对控制标签或权限配置匹配,Claude Code 在前面加一行点名匹配模式的 [harness: ...] 标记行;轮次标记匹配不加标记行;这些是扫描做出的仅有的修改,它从不移除或改写子智能体的文字。提前结束子智能体的 API 错误(如速率限制)从不作为它的结果交付(前台和后台的行为见「子智能体中的 API 错误」)。
调用子智能体
自动调用
Claude 根据任务和每个子智能体的 description 自动决定何时调用子智能体。例如,如果你定义了描述为"用于查询调优的性能优化专家"的 performance-optimizer 子智能体,Claude 会在你的提示提到优化查询时调用它。要写清晰、具体的描述,让 Claude 能把任务匹配到正确的子智能体。
显式调用
要保证 Claude 使用某个特定子智能体,在提示里按名字提到它:
"Use the code-reviewer agent to check the authentication module"这会绕过自动匹配,直接调用点名的子智能体。
动态智能体配置
你可以根据运行时条件动态创建智能体定义。这个例子创建一个带不同严格程度的安全审查者,严格审查用更强的模型:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
# 返回 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())import { query, type AgentDefinition } from "@anthropic-ai/claude-agent-sdk";
// 返回 AgentDefinition 的工厂函数
// 这种模式让你能按运行时条件定制智能体
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);
}检测子智能体调用
Claude 通过 Agent 工具调用子智能体。要检测子智能体何时被调用,检查 name 为 "Agent" 的 tool_use 块。来自子智能体上下文内部的消息包含 parent_tool_use_id 字段。该工具在 tool_use 块里显示为 "Agent",在 system:init 工具列表里显示为 "Task";Claude Code v2.1.63 之前,tool_use 块里也叫它 "Task",所以要让检测在不同 SDK 版本里都有效,就在 block.name 里匹配两个值。两个 SDK 的消息结构不同:在 Python 里,你直接通过 message.content 访问内容块;在 TypeScript 里,SDKAssistantMessage 包着 Claude API 消息,所以通过 message.message.content 访问内容。这个例子遍历流式消息,记录子智能体何时被调用,以及后续消息何时源自该子智能体的执行上下文:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolUseBlock
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"],
)
},
),
):
# 检查子智能体调用。两个名字都要匹配:较旧的 SDK
# 版本发出 "Task",当前版本发出 "Agent"。
if hasattr(message, "content") and message.content:
for block in message.content:
if isinstance(block, ToolUseBlock) and block.name in (
"Task",
"Agent",
):
print(f"Subagent invoked: {block.input.get('subagent_type')}")
# 检查这条消息是否来自子智能体的上下文内部
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())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;
// 检查子智能体调用。两个名字都要匹配:较旧的 SDK 版本
// 发出 "Task",当前版本发出 "Agent"。
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);
}
}恢复子智能体
你可以恢复子智能体,让它从上次停下的地方继续,而不是从头开始。被恢复的子智能体保留它完整的对话历史,包括所有先前的工具调用、结果和推理。子智能体在它的 maxTurns 限制处停止时,Claude Code 把 Agent 工具结果里的输出标为部分,使 Claude 知道这次运行没有完成。子智能体完成时,Agent 工具结果包含含 agentId: <id> 的文本块。内置的 Explore 和 Plan 智能体是一次性的,不返回 agentId,所以需要恢复时用自定义智能体或 general-purpose。以编程方式恢复子智能体:
- 捕获会话 ID:在第一次查询期间从消息里提取
session_id - 提取智能体 ID:从 Agent 工具结果文本里解析
agentId - 恢复会话:在第二次查询的选项里传
resume: sessionId,并在提示里包含智能体 ID。每个query()调用默认开始新会话,你必须恢复同一个会话才能访问子智能体的记录
使用自定义智能体时,两次查询都要在 agents 参数里传同一个智能体定义。下面的例子定义一个自定义的 endpoint-finder 智能体:第一次查询运行它并从 Agent 工具结果里捕获会话 ID 和智能体 ID,然后第二次查询恢复该会话,问一个需要第一次分析上下文的追问。
import asyncio
import re
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition, ToolResultBlock
AGENTS = {
"endpoint-finder": AgentDefinition(
description="Locates and catalogs API endpoints in a codebase.",
prompt="You find and document API endpoints. Report each endpoint's path, method, and handler.",
tools=["Read", "Grep", "Glob"],
)
}
def extract_agent_id(block: ToolResultBlock) -> str | None:
"""从 Agent 工具结果的文本内容里提取 agentId。"""
parts = block.content if isinstance(block.content, list) else [{"text": block.content}]
for part in parts:
if match := re.search(r"agentId:\s*([\w-]+)", part.get("text") or ""):
return match.group(1)
return None
async def main():
agent_id = None
session_id = None
# 第一次调用——运行 endpoint-finder 子智能体
try:
async for message in query(
prompt="Use the endpoint-finder agent to find all API endpoints in this codebase",
options=ClaudeAgentOptions(allowed_tools=["Read", "Grep", "Glob", "Agent"], agents=AGENTS),
):
# 从 ResultMessage 捕获 session_id(恢复该会话需要)
if hasattr(message, "session_id"):
session_id = message.session_id
# 在工具结果里搜索 agentId 尾部
for block in getattr(message, "content", None) or []:
if isinstance(block, ToolResultBlock):
agent_id = extract_agent_id(block) or agent_id
# 打印最终结果
if hasattr(message, "result"):
print(message.result)
except Exception as error:
# 单次 query() 在产出错误结果之后抛出,
# 所以 session_id 和 agent_id 已经被上面的循环捕获了。
print(f"Session ended with an error: {error}")
# 第二次调用——恢复并提追问
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"], agents=AGENTS, resume=session_id
),
):
if hasattr(message, "result"):
print(message.result)
else:
print("No agentId found in the first query, so there is no subagent to resume.")
asyncio.run(main())import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
const agents = {
"endpoint-finder": {
description: "Locates and catalogs API endpoints in a codebase.",
prompt: "You find and document API endpoints. Report each endpoint's path, method, and handler.",
tools: ["Read", "Grep", "Glob"]
}
};
// 把内容字符串化以搜索 agentId,而不必遍历嵌套的块类型
function extractAgentId(message: SDKMessage): string | undefined {
if (message.type !== "assistant" && message.type !== "user") return undefined;
const content = JSON.stringify(message.message.content);
const match = content.match(/agentId:\s*([\w-]+)/);
return match?.[1];
}
let agentId: string | undefined;
let sessionId: string | undefined;
// 第一次调用——运行 endpoint-finder 子智能体
try {
for await (const message of query({
prompt: "Use the endpoint-finder agent to find all API endpoints in this codebase",
options: { allowedTools: ["Read", "Grep", "Glob", "Agent"], agents }
})) {
// 从 ResultMessage 捕获 session_id(恢复该会话需要)
if ("session_id" in message) sessionId = message.session_id;
// 在消息内容里搜索 agentId(出现在 Agent 工具结果里)
const extractedId = extractAgentId(message);
if (extractedId) agentId = extractedId;
// 打印最终结果
if ("result" in message) console.log(message.result);
}
} catch (error) {
// 单次 query() 在产出错误结果之后抛出,
// 所以 sessionId 和 agentId 已经被上面的循环捕获了。
console.error(`Session ended with an error: ${error}`);
}
// 第二次调用——恢复并提追问
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"], agents, resume: sessionId }
})) {
if ("result" in message) console.log(message.result);
}
} else {
console.log("No agentId found in the first query, so there is no subagent to resume.");
}子智能体记录存放在单独的文件里,独立于主对话持久化(压缩行为和 cleanupPeriodDays 清理周期见 Claude Code 里的「恢复子智能体」)。
工具限制
用 tools 字段限制子智能体能做什么:省略 tools——子智能体得到子智能体可用的每个工具;列出工具——子智能体只得到这些,例如永远不该编辑文件的代码审查者得到 ["Read", "Grep", "Glob"]。你省略的工具根本不在子智能体的会话里:Claude 没有它也能工作,没有权限提示或错误。这个例子创建一个只读的分析智能体,它能检查代码但不能修改文件或运行命令:
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.""",
# 只读工具:没有 Edit、Write 或 Bash 访问
tools=["Read", "Grep", "Glob"],
)
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())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.`,
// 只读工具:没有 Edit、Write 或 Bash 访问
tools: ["Read", "Grep", "Glob"]
}
}
}
})) {
if ("result" in message) console.log(message.result);
}常见的工具组合
| 用例 | 工具 | 说明 |
|---|---|---|
| 只读分析 | Read、Grep、Glob | 能检查代码但不能修改或执行 |
| 测试执行 | Bash、Read、Grep | 能运行命令并分析输出 |
| 代码修改 | Read、Edit、Write、Grep、Glob | 完整的读写访问,但不能执行命令 |
| 完全访问 | 所有工具 | 继承子智能体可用的工具(省略 tools 字段) |
限制子智能体的深度、并发和花费
本节描述 TypeScript SDK v0.3.219 和 Python SDK v0.2.127 及以上(它们捆绑 Claude Code v2.1.219 及以上)。在更早的发布上,其中一些限制缺失或默认值不同,所以依赖它们来限定一次运行之前要先升级(各变量由哪个 Claude Code 版本添加,以及支出上限对子智能体的强制,见环境变量参考和「轮次与预算」)。
Claude 自己决定何时派生子智能体以及派生多少个。每个子智能体发出自己的 API 请求,计入查询的 total_cost_usd,而子智能体又可以派生自己的子智能体,所以一个提示可能长成一棵智能体树。你可以用三种方式限制这种增长:子智能体嵌套多深、同时运行多少个,以及整个查询花多少。深度和并发限制通过 env 选项设为环境变量,支出限制作为查询选项设置:
| 限制 | 设置方式 | 默认 | 达到限制时 Claude Code 怎么做 |
|---|---|---|---|
| 深度 | CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH | 主智能体之下 3 层子智能体;1 让你的子智能体不能派生任何自己的子智能体 | 让处于最底层的子智能体无法派生,所以它自己完成被委派的工作(见嵌套子智能体) |
| 并发 | CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS | 同时运行 20 个子智能体,统计 Claude 用 Agent 工具派生的每个子智能体 | 拒绝派生另一个子智能体,返回 Concurrent subagent limit reached,直到运行数降到限制以下;启用 ultracode 的会话从不被拒绝 |
| 花费 | TypeScript 里的 maxBudgetUsd,Python 里的 max_budget_usd | 无限制;统计该调用自己的花费,包括子智能体请求 | 以三种方式强制该上限:拒绝派生更多子智能体,返回 Budget limit reached;停止仍在运行的后台子智能体;以 error_max_budget_usd 结果 subtype 结束查询(上限在整个会话里如何表现,见「轮次与预算」) |
两个 SDK 对 env 选项的处理不同:TypeScript SDK 用它替换子进程环境,所以要把 process.env 展开进去以保留 PATH 之类的变量;Python SDK 则把它合并进继承的环境。这个例子关闭嵌套,同时最多允许五个子智能体,并在估算花费达到 5 美元时停止查询:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
try:
async for message in query(
prompt="Audit every service in this repo for unhandled promise rejections",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob", "Agent"],
# env 合并在继承的环境之上
env={
"CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH": "1",
"CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS": "5",
},
max_budget_usd=5.0,
),
):
if isinstance(message, ResultMessage):
print(f"{message.subtype}: ${message.total_cost_usd}")
except Exception as error:
# 单次 query() 在产出错误结果之后抛出,
# 所以受预算限制的结果已经在上面打印了。
print(f"Session ended with an error: {error}")
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
try {
for await (const message of query({
prompt: "Audit every service in this repo for unhandled promise rejections",
options: {
allowedTools: ["Read", "Grep", "Glob", "Agent"],
// env 替换子进程环境,所以展开 process.env 以保留 PATH
env: {
...process.env,
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: "1",
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: "5",
},
maxBudgetUsd: 5,
},
})) {
if (message.type === "result") {
console.log(`${message.subtype}: $${message.total_cost_usd}`);
}
}
} catch (error) {
// 单次 query() 在产出错误结果之后抛出,
// 所以受预算限制的结果已经在上面记录了。
console.error(`Session ended with an error: ${error}`);
}你看到什么取决于查询达到了哪个限制(如果有的话):在花费上限之下——你看到 success 和估算的成本;在花费上限处——你看到成本达到或超过 5 的 error_max_budget_usd,然后你的错误处理程序运行;在并发限制处——你在消息流里看到一个携带 Concurrent subagent limit reached 的 tool_result 块,Claude 收到与 Agent 工具结果相同的块。
用子智能体运行 Opus 5
Claude Opus 5 比较早的模型更愿意委派给子智能体,所以深度、并发和花费限制在运行 Opus 5 的查询上最重要。Opus 5 提示指南里有一条可以加到任何提示里的委派说明。Claude Code 是否自己添加说明,取决于你用哪个系统提示:claude_code 预设——模型是 Opus 5 时,Claude Code 往它的系统提示里加一行,告诉 Claude 除非被要求,否则不要调用 Agent 工具(Agent 工具仍然可用);自定义提示,或没有 systemPrompt——Claude Code 不构建它的系统提示,所以该行不存在,要把提示指南的委派说明加到你自己的提示里。任一说明都只是引导 Claude,所以也要设置这些限制:不论 Claude 如何决定委派,Claude Code 都会强制执行它们。
用动态工作流扩展
子智能体适合每个轮次委派少量任务。对协调几十到几百个智能体的运行,用 Workflow 工具,它把编排移进由运行时在对话上下文之外执行的脚本(工作流与逐轮子智能体委派有什么不同,见「动态工作流」)。Workflow 工具在 TypeScript Agent SDK v0.3.149 及以上可用;把 Workflow 放进 allowedTools 可自动批准工作流运行;工具的输入和输出 schema 列在 TypeScript 参考里。
排障
Claude 不委派给子智能体
如果 Claude 直接完成任务而不是委派给你的子智能体:用显式提示——在提示里按名字提到该子智能体,例如 "Use the code-reviewer agent to check the authentication module";写清晰的描述——准确解释何时使用该子智能体,让 Claude 能恰当地匹配任务。
基于文件系统的智能体没有加载
Claude Code 监视 ~/.claude/agents/ 和 .claude/agents/,并在几秒内拾取新的或编辑过的智能体文件,无需重启。如果某个定义始终没有出现,按这些原因排查:
- 新的
agents目录:监视器只覆盖会话启动时已经存在的目录,所以新目录里的第一个文件需要重启会话;这是最常见的原因。 - 无效的 frontmatter 或重复的
name:检查文件的 YAML,以及是否已有智能体使用该name。 --disable-slash-commands:用这个标志启动的会话不监视这些目录,加载新文件总要重启。- 添加目录下的文件:Claude Code 会加载经
add_dirs(Python)或additionalDirectories(TypeScript)选项,或 CLI 的--add-dir或/add-dir添加的目录里的.claude/agents/,但不监视它们,所以那里新的或编辑过的文件需要重启会话。 - 同名的编程方式智能体:传给
query()的agents会覆盖同名的文件系统智能体。
文件格式见「如何编写子智能体文件」。
相关文档
- Claude Code 子智能体:全面的子智能体文档,包括基于文件系统的定义
- 动态工作流:从脚本编排许多子智能体,用于一次对话装不下的工作
- SDK 概览:Claude Agent SDK 入门