在 Claude Code 的工具体系中,AgentTool 是一个极为特殊的存在。它不像 BashTool 那样直接与操作系统交互,也不像 FileReadTool 那样专注于文件系统读写。它的唯一职责是创建并调度另一个 Agent——一个能够独立运行、拥有自己的系统提示词、工具池和生命周期的子 Agent。
如果说 Claude Code 的其余工具扩展了 Agent 的"能力边界",那么 AgentTool 则扩展了它的"组织边界"。它让 Claude Code 从单一的对话线程,演化为一个可以并发执行多任务、分工协作的 Agent 系统。本文将深入解析 AgentTool 的源码实现,揭示它如何完成从"任务描述"到"子 Agent 运行"的完整调度链路。
一、AgentTool 的定位:从单线程到多 Agent 的分水岭
在 Claude Code 的工具注册列表中,AgentTool 通常排在第一位(AGENT_TOOL_NAME)。这不是偶然——它代表了 Claude Code 架构设计中最核心的哲学:Agent 应该是递归的、可组合的。
1.1 为什么是工具池中的第一个工具
从 AgentTool.tsx 的源码结构可以看出,AgentTool 被赋予了极高的架构优先级:
// AgentTool.tsx:270-280
export const AgentTool = buildTool({
async prompt({
agents,
tools,
getToolPermissionContext,
allowedAgentTypes
}) {
// ... 构建包含所有可用 Agent 类型的提示词
return await getPrompt(filteredAgents, isCoordinator, allowedAgentTypes);
},
name: AGENT_TOOL_NAME,
searchHint: 'delegate work to a subagent',
aliases: [LEGACY_AGENT_TOOL_NAME],
maxResultSizeChars: 100_000,
// ...
});它被赋予 searchHint: 'delegate work to a subagent',意味着当 Claude Code 的 LLM 需要"委派工作"时,会优先检索到这个工具。同时,maxResultSizeChars: 100_000 的高配额也反映了子 Agent 可能产生大量输出的场景。
1.2 单线程到多 Agent 的跃迁
在没有 AgentTool 的系统中,所有任务都在单一对话线程中顺序执行:读取文件 → 分析 → 修改 → 测试。当任务复杂度上升时,上下文窗口会被快速耗尽,且不同任务之间的状态会互相干扰。
AgentTool 引入了一个关键抽象:每个子 Agent 拥有独立的上下文和生命周期。主 Agent 可以将一个大任务拆分为多个子任务,分别派发给不同的子 Agent 并行执行,最后汇总结果。这种模型类似于操作系统中的进程 fork——父进程创建子进程,子进程独立运行,完成后向父进程汇报。
flowchart TD
A[主 Agent
Main Thread] -->|AgentTool| B[子 Agent A
异步/同步]
A -->|AgentTool| C[子 Agent B
异步/同步]
A -->|AgentTool| D[子 Agent C
后台任务]
B -->|TaskOutputTool| A
C -->|TaskOutputTool| A
D -->|通知/回调| A
style A fill:#4a90d9,color:#fff
style D fill:#e67e22,color:#fff二、输入 Schema:精确控制子 Agent 的每一个维度
AgentTool 的输入参数设计体现了对子 Agent 行为的精细化控制。源码中定义了两层 Schema:baseInputSchema(基础参数)和 fullInputSchema(扩展参数,包含多 Agent 协作相关字段)。
2.1 基础输入参数
// AgentTool.tsx:96-104
const baseInputSchema = lazySchema(() => z.object({
description: z.string().describe('A short (3-5 word) description of the task'),
prompt: z.string().describe('The task for the agent to perform'),
subagent_type: z.string().optional().describe('The type of specialized agent to use for this task'),
model: z.enum(['sonnet', 'opus', 'haiku']).optional().describe("Optional model override..."),
run_in_background: z.boolean().optional().describe('Set to true to run this agent in the background...')
})); description
任务的简短描述(3-5 个词)。这个字段不仅是给人看的,更会被用于任务通知、进度追踪和日志记录。当子 Agent 以异步方式运行时,系统会使用这个描述生成任务状态更新。
prompt
派发给子 Agent 的具体任务指令。这是子 Agent 用户消息(User Message)的核心内容。在 Fork 子 Agent 路径中,这个 prompt 会被包装在特殊的 Fork 指令消息中(buildForkedMessages)。
subagent_type
指定要使用的专业化 Agent 类型。Claude Code 支持多种内置 Agent 类型(如 Explore、Plan、通用 Agent 等),也支持用户自定义 Agent。如果省略此参数,系统会根据 Feature Gate 决定是走 Fork 路径(isForkSubagentEnabled())还是默认使用通用 Agent(GENERAL_PURPOSE_AGENT)。
model
可选的模型覆盖参数,允许为特定子任务指定不同的 Claude 模型。取值范围限定为 'sonnet'、'opus'、'haiku'。这个参数会覆盖 Agent 定义中的模型设置,也会覆盖父 Agent 继承的模型。
run_in_background
是否以异步/后台方式运行子 Agent。这是 AgentTool 最核心的行为控制开关之一:
false(默认):同步运行。主 Agent 的当前回合会被阻塞,直到子 Agent 完成并返回结果。true:异步运行。子 Agent 被注册到任务系统中独立运行,主 Agent 立即收到一个async_launched状态的结果,后续通过通知或TaskOutputTool获取结果。
2.2 扩展输入参数(多 Agent 协作)
当 KAIROS 或 PROACTIVE Feature Gate 开启时,Schema 会扩展为 fullInputSchema,增加多 Agent 协作相关的参数:
// AgentTool.tsx:108-121
const multiAgentInputSchema = z.object({
name: z.string().optional().describe('Name for the spawned agent...'),
team_name: z.string().optional().describe('Team name for spawning...'),
mode: permissionModeSchema().optional().describe('Permission mode for spawned teammate...')
}); name 与 team_name
用于Agent Swarms(Agent 集群)场景。当同时提供 name 和 team_name 时,AgentTool 不会创建传统的子 Agent,而是通过 spawnTeammate() 创建一个队友(Teammate)Agent。队友之间可以通过 SendMessageTool 进行双向通信,实现真正的多 Agent 协作。
isolation
隔离模式,目前支持 'worktree'。当设置为 worktree 时,系统会为子 Agent 创建一个临时的 Git worktree,使其在隔离的代码副本上工作,避免对主工作区造成意外修改。
cwd
覆盖子 Agent 的工作目录。与 isolation: 'worktree' 互斥。
三、子 Agent 创建流程:从调用到运行
当 LLM 发起一次 AgentTool 调用后,代码进入 call() 方法,经历一个复杂但井然有序的创建流程。
3.1 总体流程概览
sequenceDiagram
participant LLM as LLM/主Agent
participant AT as AgentTool.call()
participant RA as runAgent()
participant Query as query()
participant Task as Task系统
LLM->>AT: AgentTool({description, prompt, subagent_type, ...})
AT->>AT: 1. 解析Agent类型 & 权限检查
AT->>AT: 2. MCP服务器依赖检查
AT->>AT: 3. 决定同步/异步模式
AT->>AT: 4. 组装工具池 (assembleToolPool)
AT->>AT: 5. 生成系统提示词
AT->>AT: 6. 构建prompt消息
alt 异步模式
AT->>Task: registerAsyncAgent()
AT-->>LLM: {status: 'async_launched'}
Task->>RA: 后台启动runAgent()
else 同步模式
AT->>RA: runAgent()
RA->>Query: query() 对话循环
Query-->>RA: 消息流
RA-->>AT: 最终消息
AT-->>LLM: {status: 'completed'}
end3.2 Agent 类型解析与路由
call() 方法首先解析用户请求的 Agent 类型:
// AgentTool.tsx:311-340
const effectiveType = subagent_type ?? (isForkSubagentEnabled() ? undefined : GENERAL_PURPOSE_AGENT.agentType);
const isForkPath = effectiveType === undefined;
let selectedAgent: AgentDefinition;
if (isForkPath) {
// Fork路径:拒绝递归Fork
if (toolUseContext.options.querySource === `agent:builtin:${FORK_AGENT.agentType}` || isInForkChild(toolUseContext.messages)) {
throw new Error('Fork is not available inside a forked worker...');
}
selectedAgent = FORK_AGENT;
} else {
// 正常路径:从已激活的Agent列表中查找
const found = agents.find(agent => agent.agentType === effectiveType);
selectedAgent = found;
}这里有两个关键分支:
Fork 路径:当 subagent_type 未指定且 isForkSubagentEnabled() 返回 true 时,系统走 Fork 子 Agent 路径。Fork Agent 的特殊之处在于它继承父 Agent 的系统提示词和工具池,以保证 API 请求的缓存前缀完全一致(prompt cache hit)。源码中的注释明确说明了这一点:
"Fork path: child inherits the PARENT’s system prompt (not FORK_AGENT’s) for cache-identical API request prefixes."
正常路径:根据 subagent_type 查找对应的 AgentDefinition。如果没有找到,会抛出错误并列出所有可用的 Agent 类型。这里还会检查权限规则——如果某个 Agent 类型被 AgentTool(AgentName) 语法显式拒绝,会返回专门的错误信息。
3.3 系统提示词的重新生成
对于正常路径的子 Agent,系统需要为其生成独立的系统提示词:
// AgentTool.tsx:440-460
const agentPrompt = selectedAgent.getSystemPrompt({ toolUseContext });
// 应用环境详情增强
enhancedSystemPrompt = await enhanceSystemPromptWithEnvDetails(
[agentPrompt],
resolvedAgentModel,
additionalWorkingDirectories
);这个过程与主 Agent 启动时的系统提示词生成类似,但有几点关键差异:
- Agent 特定的提示词:每个
AgentDefinition可以定义自己的getSystemPrompt,提供专业化的角色定义和能力描述。 - 环境上下文注入:
enhanceSystemPromptWithEnvDetails会将当前工作目录、Git 状态、环境变量等信息附加到系统提示词中。 - Fork 路径的差异:Fork 子 Agent 不重新生成系统提示词,而是直接使用父 Agent 的
renderedSystemPrompt,这是为了保证缓存一致性。
3.4 工具池的重新裁剪
子 Agent 不应该拥有和父 Agent 完全相同的工具权限。例如,子 Agent 不应该能够创建新的子 Agent(避免无限递归),也不应该访问某些敏感工具。
// AgentTool.tsx:570-575
const workerPermissionContext = {
...appState.toolPermissionContext,
mode: selectedAgent.permissionMode ?? 'acceptEdits'
};
const workerTools = assembleToolPool(workerPermissionContext, appState.mcp.tools);在 runAgent() 内部,工具池会进一步通过 resolveAgentTools() 进行裁剪:
// runAgent.ts:508-510
const resolvedTools = useExactTools
? availableTools
: resolveAgentTools(agentDefinition, availableTools, isAsync).resolvedToolsresolveAgentTools 函数(定义在 agentToolUtils.ts)执行以下过滤逻辑:
// agentToolUtils.ts:68-95
export function filterToolsForAgent({
tools, isBuiltIn, isAsync = false, permissionMode
}: {
tools: Tools; isBuiltIn: boolean; isAsync?: boolean; permissionMode?: PermissionMode
}): Tools {
return tools.filter(tool => {
if (tool.name.startsWith('mcp__')) return true; // MCP工具始终允许
if (ALL_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false; // 全局禁止列表
if (!isBuiltIn && CUSTOM_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false;
if (isAsync && !ASYNC_AGENT_ALLOWED_TOOLS.has(tool.name)) return false; // 异步Agent限制
return true;
});
}这个裁剪机制确保了:
- 安全边界:子 Agent 不能执行某些高风险操作(如直接操作主 Agent 的状态)。
- 异步限制:后台运行的异步 Agent 只允许使用白名单中的工具(
ASYNC_AGENT_ALLOWED_TOOLS),因为后台 Agent 无法与用户进行交互式确认。 - MCP 工具继承:MCP(Model Context Protocol)工具对所有 Agent 开放,保证外部工具能力的可传递性。
四、本地 vs 远程:子 Agent 的执行环境选择
Claude Code 的子 Agent 可以在本地进程或远程环境中运行,这一选择由 isolation 参数和系统配置共同决定。
4.1 本地执行:LocalAgentTask
默认情况下,子 Agent 在本地 Node.js/Bun 进程中运行。对于同步子 Agent,runAgent() 函数直接在当前的 JavaScript 执行环境中被调用,通过 query() 函数与 LLM API 交互。
对于异步子 Agent,系统会将其注册到 LocalAgentTask 系统中:
// AgentTool.tsx:640-650
const agentBackgroundTask = registerAsyncAgent({
agentId: asyncAgentId,
description,
prompt,
selectedAgent,
setAppState: rootSetAppState,
toolUseId: toolUseContext.toolUseId
});registerAsyncAgent 返回一个任务对象,包含独立的 abortController,这意味着后台 Agent 不会因为用户按 ESC 取消主线程而被终止——它们必须通过专门的 chat:killAgents 命令来停止。
4.2 远程执行:RemoteAgentTask
当 isolation 设置为 'remote' 时(仅在 "external" === 'ant' 的内部分支中启用),子 Agent 会被发送到远程的 CCR(Claude Code Remote)环境中执行:
// AgentTool.tsx:375-395
if ("external" === 'ant' && effectiveIsolation === 'remote') {
const eligibility = await checkRemoteAgentEligibility();
if (!eligibility.eligible) {
throw new Error(`Cannot launch remote agent:\n${reasons}`);
}
const session = await teleportToRemote({ initialMessage: prompt, description, ... });
const { taskId, sessionId } = registerRemoteAgentTask({
remoteTaskType: 'remote-agent',
session: { id: session.id, title: session.title || description },
command: prompt,
context: toolUseContext,
toolUseId: toolUseContext.toolUseId
});
// 返回 remote_launched 状态
}远程执行的优势在于:
- 资源隔离:不占用本地机器的计算资源和上下文窗口。
- 网络访问:远程环境可能拥有不同的网络权限和工具链。
- 持久化:即使本地会话关闭,远程 Agent 仍可继续运行。
4.3 选择逻辑
执行环境的选择遵循以下优先级:
- 显式
isolation参数:用户或 Agent 定义中指定的隔离模式优先。 - Agent 定义中的
background属性:如果 Agent 定义声明了background: true,且未禁用后台任务,则强制异步运行。 - Feature Gate 强制:Coordinator 模式、Fork 子 Agent 开启、KAIROS 助手模式、Proactive 模式等都会强制子 Agent 异步运行。
run_in_background参数:用户的显式请求。
// AgentTool.tsx:555-560
const forceAsync = isForkSubagentEnabled();
const assistantForceAsync = feature('KAIROS') ? appState.kairosEnabled : false;
const shouldRunAsync = (
run_in_background === true || selectedAgent.background === true ||
isCoordinator || forceAsync || assistantForceAsync ||
(proactiveModule?.isProactiveActive() ?? false)
) && !isBackgroundTasksDisabled;五、生命周期管理:与 Task 系统的深度绑定
AgentTool 最强大的能力之一是对子 Agent 全生命周期的管理。这不仅仅是一次简单的函数调用,而是一套包含注册、执行、监控、通知、清理的完整状态机。
5.1 同步 Agent 的生命周期
同步子 Agent 的生命周期相对简单:
- 创建:
runAgent()被调用,生成新的agentId。 - 运行:进入
query()循环,与 LLM API 进行多轮对话。 - 完成:
query()返回最终消息流,runAgent()的 AsyncGenerator 结束。 - 清理:销毁 MCP 连接、注销 Perfetto Trace、清理会话存储。
// runAgent.ts:280-290
const agentId = override?.agentId ? override.agentId : createAgentId();
if (isPerfettoTracingEnabled()) {
const parentId = toolUseContext.agentId ?? getSessionId();
registerPerfettoAgent(agentId, agentDefinition.agentType, parentId);
}
// ... query 循环 ...
// 完成后 unregisterPerfettoAgent5.2 异步 Agent 的生命周期
异步 Agent 的生命周期要复杂得多,因为它需要在主 Agent 的回合结束后继续存活:
注册阶段 → 后台执行 → 进度追踪 → 完成通知 → 结果持久化 → 清理注册阶段
// AgentTool.tsx:640-650
const agentBackgroundTask = registerAsyncAgent({
agentId: asyncAgentId,
description,
prompt,
selectedAgent,
setAppState: rootSetAppState,
toolUseId: toolUseContext.toolUseId
});
// 注册名称路由(用于SendMessage)
if (name) {
rootSetAppState(prev => {
const next = new Map(prev.agentNameRegistry);
next.set(name, asAgentId(asyncAgentId));
return { ...prev, agentNameRegistry: next };
});
}后台执行与进度追踪
异步 Agent 通过 runAsyncAgentLifecycle() 函数在后台执行。这个函数包装了 runAgent() 的调用,并添加了进度追踪和状态更新机制:
// AgentTool.tsx:680-690
void runWithAgentContext(asyncAgentContext, () => wrapWithCwd(() =>
runAsyncAgentLifecycle({
taskId: agentBackgroundTask.agentId,
abortController: agentBackgroundTask.abortController!,
makeStream: onCacheSafeParams => runAgent({ ... }),
metadata,
description,
toolUseContext,
rootSetAppState,
agentIdForCleanup: asyncAgentId,
enableSummarization: isCoordinator || isForkSubagentEnabled() || getSdkAgentProgressSummariesEnabled(),
// ...
})
));runAsyncAgentLifecycle 负责:
- 启动 Agent 执行流(
makeStream)。 - 监听消息流,将进度更新写入状态。
- 支持周期性摘要(当
enableSummarization为true时),通过startAgentSummarization对长运行的后台 Agent 进行进度摘要,避免主 Agent 恢复时面对海量消息。
状态、输出与通知
异步 Agent 完成后,系统会:
- 写入输出文件:将最终结果写入磁盘文件(
getTaskOutputPath(taskId))。 - 发送通知:通过
enqueueAgentNotification向主 Agent 发送<task-notification>事件。 - 更新状态:在 AppState 中将任务标记为
completed或failed。 - 清理资源:注销
agentNameRegistry中的名称映射,清理 worktree(如果使用了隔离)。
5.3 终止机制
子 Agent 可以通过多种方式被终止:
- 自然完成:子 Agent 自行完成任务,返回结果。
- 用户取消:通过
chat:killAgents命令,调用killAsyncAgent()终止后台 Agent。 - 父 Agent 取消:同步 Agent 会继承父 Agent 的
abortController,当父 Agent 被终止时,子 Agent 也会被级联取消。 - 独立取消:异步 Agent 拥有独立的
abortController,父 Agent 的取消不会影响它(设计上如此,避免误杀后台任务)。
// AgentTool.tsx:660-670
// 异步Agent获得独立的abortController
const agentAbortController = override?.abortController
? override.abortController
: isAsync
? new AbortController() // 独立控制器
: toolUseContext.abortController; // 继承父控制器六、与相邻工具的关系:构建完整的 Agent 协作生态
AgentTool 并非孤立存在,它与 Claude Code 工具池中的多个工具形成了紧密的协作关系,共同构建了一个完整的 Agent 协作生态。
6.1 SendMessageTool:多 Agent 通信的桥梁
当 Agent Swarms 功能启用时,AgentTool 创建的队友 Agent(Teammate)之间需要双向通信机制。SendMessageTool 就是这个机制的载体。
AgentTool 在注册异步 Agent 时,会将 name 参数映射到 agentId,存入 agentNameRegistry:
// AgentTool.tsx:655-660
if (name) {
rootSetAppState(prev => {
const next = new Map(prev.agentNameRegistry);
next.set(name, asAgentId(asyncAgentId));
return { ...prev, agentNameRegistry: next };
});
}这使得 SendMessageTool 可以通过 name 作为地址,向特定的子 Agent 发送消息。这种设计让 Claude Code 从"主-从"式的子 Agent 调用,演进为"对等网络"式的多 Agent 协作。
值得注意的是,源码中有一个安全限制:队友不能创建其他队友(扁平化团队结构):
// AgentTool.tsx:290-295
if (isTeammate() && teamName && name) {
throw new Error('Teammates cannot spawn other teammates — the team roster is flat...');
}6.2 Task 系列工具
AgentTool 与 Task 系统的关系是"创建者"与"消费者"的关系:
AgentTool负责创建任务(通过registerAsyncAgent或registerRemoteAgentTask)。TaskOutputTool负责读取任务的输出文件。Task相关状态 存储在 AppState 中,供 UI 渲染任务列表。
异步 Agent 完成后,其输出文件路径会作为 async_launched 结果的一部分返回给调用者:
// AgentTool.tsx:80-90
const asyncOutputSchema = z.object({
status: z.literal('async_launched'),
agentId: z.string(),
description: z.string(),
prompt: z.string(),
outputFile: z.string(), // 输出文件路径
canReadOutputFile: z.boolean().optional()
});主 Agent 可以随后使用 TaskOutputTool 或直接的文件读取工具来检查这个结果文件,从而获取子 Agent 的最终输出。
6.3 SkillTool
SkillTool 为 Agent 提供了调用预定义技能的能力,而 AgentTool 与 SkillTool 之间存在两层交互:
第一层:Agent 定义中的技能预加载
在 runAgent.ts 中,子 Agent 启动时会预加载其 AgentDefinition 中定义的技能:
// runAgent.ts:540-550
const skillsToPreload = agentDefinition.skills ?? [];
if (skillsToPreload.length > 0) {
const allSkills = await getSkillToolCommands(getProjectRoot());
// 解析并验证技能名称,支持插件命名空间
for (const skillName of skillsToPreload) {
const resolvedName = resolveSkillName(skillName, allSkills, agentDefinition);
// ... 注册技能
}
}这意味着每个子 Agent 可以拥有自己独特的技能组合,与父 Agent 的技能集解耦。
第二层:子 Agent 调用 SkillTool
子 Agent 在其运行过程中,可以像使用任何其他工具一样使用 SkillTool。由于 SkillTool 不在 ALL_AGENT_DISALLOWED_TOOLS 列表中,它对所有子 Agent 都是可用的。
七、源码总结与关键设计思想
通过深入分析 AgentTool.tsx、runAgent.ts 和 agentToolUtils.ts 的源码,我们可以提炼出 Claude Code 子 Agent 系统的几个核心设计思想:
7.1 递归与组合
Agent 可以创建 Agent,子 Agent 又可以创建孙 Agent。这种递归性使得复杂的任务可以被无限分解。但系统通过 isInForkChild 检查和 ALL_AGENT_DISALLOWED_TOOLS 等机制防止了无限递归和滥用。
7.2 上下文隔离与继承的权衡
- Fork 路径:追求缓存一致性,继承父 Agent 的系统提示词和工具池。
- 正常路径:追求专业化,为每个子 Agent 生成独立的系统提示词和裁剪后的工具池。
7.3 同步与异步的灵活切换
AgentTool 通过 run_in_background、Agent 定义的 background 属性、Feature Gate 等多种机制,提供了从完全同步到完全异步的灵活光谱。这使得同样的任务派发接口可以适应不同的交互场景。
7.4 生命周期与资源管理的严谨性
从 agentId 的生成、Perfetto Trace 的注册、MCP 服务器的初始化和清理、worktree 的创建和销毁,到 agentNameRegistry 的维护,AgentTool 展现了工业级代码对资源管理的严谨态度。每一个被创建的对象都有对应的清理逻辑,每一个异常路径都有适当的回退处理。
结语
AgentTool 是 Claude Code 从一个"聪明的命令行助手"进化为"可扩展的 Agent 平台"的关键基石。它不仅提供了创建子 Agent 的能力,更通过精细的参数控制、灵活的生命周期管理、与 Task 系统和多 Agent 通信工具的深度集成,构建了一个完整的多 Agent 协作框架。
对于开发者而言,理解 AgentTool 的实现原理,是掌握 Claude Code 架构设计的必经之路。它展示了如何在复杂的 AI 应用中管理并发、隔离上下文、调度资源——这些正是构建生产级 Agent 系统的核心挑战。