Sub-Agent 编排
当父 agent 决定委派工作时,它会调用 Agent tool。本页详细解析完整的调用流程——从参数解析到嵌套执行循环再到结果提取——并比较 Claude Code 中全部四种 agent 派生模式。
AgentTool 完整调用流程
Section titled “AgentTool 完整调用流程”Agent tool 的入口点是 AgentTool.tsx,它将实际执行分派给 runAgent.ts。以下是完整的调用序列:
sequenceDiagram
participant P as Parent Agent
participant AT as AgentTool.call()
participant RA as runAgent()
participant Q as query() loop
participant API as Claude API
P->>AT: Agent({ prompt, subagent_type, model, ... })
AT->>AT: Resolve agent definition
AT->>AT: Check fork path vs named agent
AT->>AT: Assemble tool pool (resolveAgentTools)
AT->>AT: Create worktree (if isolation: 'worktree')
alt Async (run_in_background)
AT->>AT: registerAsyncAgent()
AT-->>P: Return task ID immediately
end
AT->>RA: runAgent({ agentDefinition, promptMessages, ... })
RA->>RA: Resolve model (getAgentModel)
RA->>RA: Build system prompt (getAgentSystemPrompt)
RA->>RA: Initialize MCP servers
RA->>RA: Preload skills
RA->>RA: Register hooks
RA->>RA: Create sub-agent ToolUseContext
RA->>Q: query({ messages, systemPrompt, tools, ... })
loop Nested agentic loop
Q->>API: Send messages
API-->>Q: Assistant response (text + tool_use)
Q->>Q: Execute tool calls
Q->>Q: Append tool results
end
Q-->>RA: Final message
RA->>RA: Cleanup (MCP, hooks, caches, Perfetto)
RA-->>AT: Yield messages
AT-->>P: Return result text
步骤 1:Agent 定义解析
Section titled “步骤 1:Agent 定义解析”当 AgentTool.call() 收到请求时,首先需要决定 fork 路径还是 named agent:
// src/tools/AgentTool/AgentTool.tsx — agent selection logicconst effectiveType = subagent_type ?? (isForkSubagentEnabled() ? undefined : GENERAL_PURPOSE_AGENT.agentType)
const isForkPath = effectiveType === undefined
if (isForkPath) { if (isInForkChild(toolUseContext.messages)) { throw new Error('Fork is not available inside a forked worker') } selectedAgent = FORK_AGENT} else { const agents = filterDeniedAgents(allAgents, ...) const found = agents.find(a => a.agentType === effectiveType) if (!found) throw new Error(...) selectedAgent = found}三种结果:
- 提供了
subagent_type→ 从activeAgents列表中查找(内置、自定义或插件) - 未提供
subagent_type+ fork 已启用 → 使用合成的FORK_AGENT定义 - 未提供
subagent_type+ fork 未启用 → 回退到GENERAL_PURPOSE_AGENT
步骤 2:System Prompt 构建
Section titled “步骤 2:System Prompt 构建”system prompt 路径在 fork 和普通 agent 之间有所不同:
// src/tools/AgentTool/runAgent.ts — system prompt resolutionconst agentSystemPrompt = override?.systemPrompt ? override.systemPrompt // Fork path: parent's rendered prompt : asSystemPrompt( await getAgentSystemPrompt( // Normal path: agent's own prompt agentDefinition, toolUseContext, resolvedAgentModel, additionalWorkingDirectories, resolvedTools, ), )对于 fork agent,父级已渲染的 system prompt 字节通过 override.systemPrompt 传递。这对 prompt cache 共享至关重要——重新调用 getSystemPrompt() 可能产生不同的字节(例如由于 GrowthBook 状态变化)。
对于普通 agent,getAgentSystemPrompt() 调用 agent 定义的 getSystemPrompt() 函数。内置 agent 接收完整的 ToolUseContext 以动态生成 prompt;自定义 agent 返回静态 markdown 正文,可选择附加 agent memory。
步骤 3:模型选择
Section titled “步骤 3:模型选择”const resolvedAgentModel = getAgentModel( agentDefinition.model, // Agent-level preference ('inherit', 'haiku', specific ID) toolUseContext.options.mainLoopModel, // Parent's model model, // Caller override from Agent() params permissionMode,)解析优先级:
- Agent tool 调用中的显式
model参数(如model: 'opus') - Agent 定义的
model字段 'inherit'→ 使用父级的mainLoopModel- 配置中的默认 sub-agent 模型
步骤 4:Tool 集合组装
Section titled “步骤 4:Tool 集合组装”// src/tools/AgentTool/agentToolUtils.ts — resolveAgentToolsconst resolvedTools = useExactTools ? availableTools // Fork: identical tool set for cache hits : resolveAgentTools(agentDefinition, availableTools, isAsync).resolvedToolstool 解析流水线:
- 全局过滤(
filterToolsForAgent):移除对所有 agent 禁用的 tool(ALL_AGENT_DISALLOWED_TOOLS),应用自定义 agent 限制和异步 agent 允许列表 - 禁用的 tool:移除 agent
disallowedTools列表中的 tool - 允许的 tool:如果定义了
tools(不是['*']),则与允许列表取交集 - MCP tool:始终放行(
mcp__*前缀)
步骤 5:嵌套 Query Loop
Section titled “步骤 5:嵌套 Query Loop”核心执行发生在 runAgent() 内部,它调用 query()——与主 agent 使用的相同 agentic loop:
// src/tools/AgentTool/runAgent.ts — the nested agentic loopfor await (const message of query({ messages: initialMessages, systemPrompt: agentSystemPrompt, userContext: resolvedUserContext, systemContext: resolvedSystemContext, canUseTool, toolUseContext: agentToolUseContext, querySource, maxTurns: maxTurns ?? agentDefinition.maxTurns,})) { // Forward API metrics to parent // Handle max_turns signal // Yield messages back to caller}sub-agent 运行自己独立的 agentic loop——它可以调用 tool、接收结果并发起进一步的 API 调用,就像主 agent 一样。maxTurns 参数限制循环深度。
Sub-Agent 生命周期
Section titled “Sub-Agent 生命周期”flowchart TD
S[Spawn] --> I[Init]
I --> C[Context Injection]
C --> L[Nested Query Loop]
L --> R[Result Extraction]
R --> CL[Cleanup]
subgraph Init
I1[Resolve model] --> I2[Build system prompt]
I2 --> I3[Assemble tool pool]
I3 --> I4[Initialize MCP servers]
I4 --> I5[Preload skills]
I5 --> I6[Register hooks]
end
subgraph Context Injection
C1[Create sub-agent ToolUseContext] --> C2[Override permission mode]
C2 --> C3[Set up agent-scoped AppState]
C3 --> C4[Initialize ReadFileState cache]
end
subgraph Nested Query Loop
L1[Send to API] --> L2[Receive response]
L2 --> L3[Execute tool calls]
L3 --> L4[Append results]
L4 -->|more turns| L1
L4 -->|done or max_turns| L5[Exit loop]
end
subgraph Cleanup
CL1[Shutdown MCP servers] --> CL2[Unregister hooks]
CL2 --> CL3[Clear prompt cache tracking]
CL3 --> CL4[Release file state cache]
CL4 --> CL5[Unregister Perfetto trace]
CL5 --> CL6[Kill background bash tasks]
CL6 --> CL7[Cleanup worktree if needed]
end
父 agent 发出 Agent() tool 调用。AgentTool.call() 解析 agent 定义并决定同步或异步执行:
if (shouldRunAsync) { const agentBackgroundTask = registerAsyncAgent({ agentId: asyncAgentId, description, prompt, selectedAgent, setAppState: rootSetAppState, }) // Background execution — parent continues immediately} else { // Synchronous — parent blocks until sub-agent completes for await (const message of runAgent(runAgentParams)) { yield message }}在 runAgent() 内部,初始化按严格顺序进行:
- 模型解析 —
getAgentModel()解析最终模型 ID - System prompt —
getAgentSystemPrompt()或父级覆盖 - Tool 集合 —
resolveAgentTools()应用过滤 - MCP server —
initializeAgentMcpServers()启动 agent 专属的 MCP server 并合并其 tool - Skill — 从 agent 的
skillsfrontmatter 预加载 skill 命令 - Hook — 从 frontmatter 注册 agent 作用域的 session hook
Context 注入
Section titled “Context 注入”sub-agent 通过 createSubagentContext() 获得自己的 ToolUseContext:
const agentToolUseContext = createSubagentContext(toolUseContext, { options: agentOptions, agentId, agentType: agentDefinition.agentType, messages: initialMessages, readFileState: agentReadFileState, // Independent file cache abortController: agentAbortController, // Independent abort getAppState: agentGetAppState, // Permission mode override shareSetAppState: !isAsync, contentReplacementState,})关键隔离特性:
- 独立的
AbortController— 中止 sub-agent 不会中止父级 - 独立的
ReadFileState— 文件读取缓存作用域限定在 sub-agent 内 - 覆盖的
getAppState— permission mode、effort level 和允许的 tool 可以不同
runAgent() 中的 finally 块确保资源的确定性释放:
// src/tools/AgentTool/runAgent.ts — cleanup (finally block)finally { await mcpCleanup() // Shutdown agent-specific MCP servers cleanupSessionHooks() // Unregister agent hooks cleanupPromptCacheTracking() // Release cache tracking releaseReadFileState(agentReadFileState) // Free file cache memory unregisterPerfettoAgent(agentId) // Remove trace entry killBackgroundBashTasks(agentId) // Stop lingering bash processes}Permission Bubbling 机制
Section titled “Permission Bubbling 机制”Sub-agent 可以在不同的 permission mode 下运行,控制 tool 执行的授权方式。模式通过 agent 定义的 permissionMode 字段设置。
四种 Permission Mode
Section titled “四种 Permission Mode”| 模式 | 行为 | 用户提示? | 日志 | 典型用途 |
|---|---|---|---|---|
default | 正常权限检查 | ✅ 是 | 完整 | 交互式 sub-agent |
bypassPermissions | 跳过所有权限检查 | ❌ 否 | 最少 | 受信任的自定义 agent(如 code-reviewer) |
dontAsk | 自动允许但仍记录日志 | ❌ 否 | 完整 | 内置 agent 如 Claude Code Guide |
bubble | 将提示浮升到父级终端 | ✅ 是(父级终端) | 完整 | Fork 子进程 |
Permission Mode 的应用方式
Section titled “Permission Mode 的应用方式”permission mode 通过自定义的 getAppState 包装器注入到 sub-agent 的 AppState 中:
// src/tools/AgentTool/runAgent.ts — permission mode overrideconst agentGetAppState = () => { const parentState = toolUseContext.getAppState() return { ...parentState, toolPermissionContext: { ...parentState.toolPermissionContext, mode: agentDefinition.permissionMode ?? parentState.toolPermissionContext.mode, }, shouldAvoidPermissionPrompts: isAsync, // Async agents can't show prompts }}bubble 模式
Section titled “bubble 模式”bubble 模式是 fork 子进程独有的。当 fork 子进程需要运行某个 tool(如 Bash)的权限时,权限提示会浮升到父 agent 的终端,而不是在子进程的上下文中显示。这是因为 fork 子进程共享父级的终端会话。
// src/tools/AgentTool/forkSubagent.ts — FORK_AGENT definitionexport const FORK_AGENT = { // ... permissionMode: 'bubble', // Permission prompts surface to parent terminal tools: ['*'], // Full tool access (filtered at runtime)}Permission Mode 选择指南
Section titled “Permission Mode 选择指南”| 场景 | 推荐模式 | 原因 |
|---|---|---|
| Explore/Plan(只读) | default + disallowedTools | 通过 tool 列表限制,而非绕过权限 |
| 受信任的 CI/CD agent | bypassPermissions | 非交互式,预批准的操作 |
| 文档查询 | dontAsk | 低风险,但保留审计追踪 |
| 用户在旁的 Fork 子进程 | bubble | 用户可通过父级终端实时批准 |
| 后台研究 | default + async | 用户不在时自动跳过高风险 tool |
四种 Agent 类型对比
Section titled “四种 Agent 类型对比”Claude Code 有四种不同的子 agent 派生模式。理解何时使用每种至关重要:
| 方面 | Named Sub-Agent | Fork Child | Coordinator Worker | Team Member |
|---|---|---|---|---|
| 触发方式 | Agent(subagent_type='X') | Agent()(无类型,fork 已启用) | Coordinator 通过 Agent 派生 | TeamCreate + Agent(team_name) |
| 上下文 | 全新——agent 自己的 system prompt | 完整的父级历史 + 父级 system prompt | 全新——worker system prompt | 全新——teammate prompt |
| Tool 集合 | Agent 定义的 tools/disallowedTools | 父级的完整 tool 集合(用于缓存) | ASYNC_AGENT_ALLOWED_TOOLS | 按 agent 定义 |
| 模型 | Agent 定义或继承 | 始终继承(用于缓存) | 继承或指定 | 按成员配置 |
| Permission Mode | Agent 定义(默认:acceptEdits) | bubble(浮升到父级) | Worker 作用域 | 按成员(通常 plan) |
| 隔离 | 可选 worktree | 可选 worktree | 无(共享进程) | 每个成员可选 worktree |
| 生命周期 | 临时——一次 query、返回、清理 | 临时——同 named | 按任务临时,可通过 SendMessage 继续 | 持久——跨任务存活 |
| 并发 | 顺序或后台 | 始终后台(并行) | 设计上并行 | 并行,独立进程 |
| 防递归 | 通过 maxTurns 限制深度 | <fork-boilerplate> 标签检测 | Coordinator 掌控所有调度 | 团队结构防止循环 |
| 用例 | 专项任务(explore、plan、verify) | 带完整上下文的并行研究 | 多阶段工作流(research → implement → verify) | 长期协作项目 |
graph TD
subgraph Named Sub-Agent
P1[Parent] -->|"Agent(subagent_type='Explore')"| C1[Explore Agent]
C1 -.->|result| P1
end
subgraph Fork Child
P2[Parent] -->|"Agent(prompt='...')"| F1[Fork 1]
P2 -->|"Agent(prompt='...')"| F2[Fork 2]
F1 -.->|result| P2
F2 -.->|result| P2
end
subgraph Coordinator Worker
CO[Coordinator] -->|Agent| W1[Worker]
CO -->|SendMessage| W1
W1 -.->|task-notification| CO
end
subgraph Team Member
L[Leader] -->|TeamCreate + Agent| T1[Member 1]
L -->|Agent| T2[Member 2]
T1 <-->|SendMessage| T2
T1 -.->|idle notification| L
end
AgentTool 内部模块架构
Section titled “AgentTool 内部模块架构”agent 系统由 src/tools/AgentTool/ 中几个紧密耦合的模块实现:
flowchart TD
AT["AgentTool.tsx<br/>(Tool definition + call entry)"]
RA["runAgent.ts<br/>(Core execution engine)"]
LA["loadAgentsDir.ts<br/>(Agent loading + parsing)"]
BA["builtInAgents.ts<br/>(Built-in registry)"]
FS["forkSubagent.ts<br/>(Fork path logic)"]
PR["prompt.ts<br/>(Prompt generation)"]
UT["agentToolUtils.ts<br/>(Tool resolution + filtering)"]
CO["constants.ts<br/>(Names + tool sets)"]
AT -->|"calls"| RA
AT -->|"resolves agents"| LA
AT -->|"checks fork"| FS
AT -->|"assembles tools"| UT
RA -->|"builds prompt"| PR
RA -->|"initializes MCP"| MCP["MCP server init"]
RA -->|"runs"| QL["query() loop"]
LA -->|"loads"| BA
LA -->|"parses"| MD["Markdown frontmatter"]
LA -->|"loads"| PL["Plugin agents"]
FS -->|"provides"| FA["FORK_AGENT definition"]
FS -->|"builds"| FM["Forked messages"]
BA -->|"registers"| BI["Built-in agents:<br/>General Purpose<br/>Explore, Plan<br/>Verification<br/>Claude Code Guide<br/>Statusline Setup"]
UT -->|"uses"| CO
PR -->|"formats"| AL["Agent list for parent prompt"]
style AT fill:#f9f,stroke:#333,stroke-width:2px
style RA fill:#bbf,stroke:#333,stroke-width:2px
style LA fill:#bfb,stroke:#333
style FS fill:#fbf,stroke:#333
AgentTool.tsx — 注册在 Claude Code tool 池中的 tool 定义。处理参数验证(inputSchema)、agent 选择(fork vs. named)、worktree 创建以及同步/异步执行决策。这是所有 sub-agent 调用的入口点。
runAgent.ts — 核心执行引擎。一个 AsyncGenerator,负责初始化 sub-agent 环境(MCP server、skill、hook),创建 sub-agent 的 ToolUseContext,运行嵌套的 query() loop,并在 finally 块中处理清理。所有资源生命周期管理都在此处。
loadAgentsDir.ts — Agent 定义的加载和解析。导出 getAgentDefinitionsWithOverrides()(带记忆化),合并内置、插件和自定义 agent,应用优先级链。还包含 parseAgentFromMarkdown() 用于解析带有 Zod 验证的 frontmatter 的 .md agent 文件。
builtInAgents.ts — 六个内置 agent 的注册中心。处理 feature flag 控制(Explore/Plan、Verification、Claude Code Guide)以及替换整个 agent 集合的 coordinator 模式切换。
forkSubagent.ts — Fork 专属逻辑:FORK_AGENT 合成定义、buildForkedMessages() 用于缓存优化的消息构建、buildChildMessage() 用于 fork 协议、isInForkChild() 防递归守卫,以及 buildWorktreeNotice() 用于 worktree 路径转换。
prompt.ts — 生成展示给父 agent 的 Agent tool 描述 prompt。格式化可用 agent 列表、fork 使用示例和使用指南。这是父级在决定派生哪个 sub-agent 时看到的内容。
agentToolUtils.ts — Tool 解析和过滤。resolveAgentTools() 应用三层过滤(全局黑名单 → agent 黑名单 → agent 白名单)。filterToolsForAgent() 处理异步 agent 限制和自定义 agent 沙箱。
// 1. Parent decides to spawn → AgentTool.call()Agent({ prompt: "Find all usages of X", subagent_type: "Explore" })
// 2. Resolve definition → loadAgentsDir.tsconst { activeAgents } = await getAgentDefinitionsWithOverrides(cwd)const selected = activeAgents.find(a => a.agentType === 'Explore')
// 3. Assemble tools → agentToolUtils.tsconst { resolvedTools } = resolveAgentTools(selected, availableTools, isAsync)
// 4. Execute → runAgent.tsfor await (const msg of runAgent({ agentDefinition: selected, promptMessages: [userMessage], toolUseContext, availableTools: resolvedTools, model: resolvedModel,})) { // 5. Messages flow back to parent}