跳转到内容

Sub-Agent 编排

当父 agent 决定委派工作时,它会调用 Agent tool。本页详细解析完整的调用流程——从参数解析到嵌套执行循环再到结果提取——并比较 Claude Code 中全部四种 agent 派生模式。

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

AgentTool.call() 收到请求时,首先需要决定 fork 路径还是 named agent

// src/tools/AgentTool/AgentTool.tsx — agent selection logic
const 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
}

三种结果:

  1. 提供了 subagent_type → 从 activeAgents 列表中查找(内置、自定义或插件)
  2. 未提供 subagent_type + fork 已启用 → 使用合成的 FORK_AGENT 定义
  3. 未提供 subagent_type + fork 未启用 → 回退到 GENERAL_PURPOSE_AGENT

system prompt 路径在 fork 和普通 agent 之间有所不同:

// src/tools/AgentTool/runAgent.ts — system prompt resolution
const 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。

src/tools/AgentTool/runAgent.ts
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,
)

解析优先级:

  1. Agent tool 调用中的显式 model 参数(如 model: 'opus'
  2. Agent 定义的 model 字段
  3. 'inherit' → 使用父级的 mainLoopModel
  4. 配置中的默认 sub-agent 模型
// src/tools/AgentTool/agentToolUtils.ts — resolveAgentTools
const resolvedTools = useExactTools
? availableTools // Fork: identical tool set for cache hits
: resolveAgentTools(agentDefinition, availableTools, isAsync).resolvedTools

tool 解析流水线:

  1. 全局过滤filterToolsForAgent):移除对所有 agent 禁用的 tool(ALL_AGENT_DISALLOWED_TOOLS),应用自定义 agent 限制和异步 agent 允许列表
  2. 禁用的 tool:移除 agent disallowedTools 列表中的 tool
  3. 允许的 tool:如果定义了 tools(不是 ['*']),则与允许列表取交集
  4. MCP tool:始终放行(mcp__* 前缀)

核心执行发生在 runAgent() 内部,它调用 query()——与主 agent 使用的相同 agentic loop:

// src/tools/AgentTool/runAgent.ts — the nested agentic loop
for 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 参数限制循环深度。

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 定义并决定同步或异步执行:

src/tools/AgentTool/AgentTool.tsx
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() 内部,初始化按严格顺序进行:

  1. 模型解析getAgentModel() 解析最终模型 ID
  2. System promptgetAgentSystemPrompt() 或父级覆盖
  3. Tool 集合resolveAgentTools() 应用过滤
  4. MCP serverinitializeAgentMcpServers() 启动 agent 专属的 MCP server 并合并其 tool
  5. Skill — 从 agent 的 skills frontmatter 预加载 skill 命令
  6. Hook — 从 frontmatter 注册 agent 作用域的 session hook

sub-agent 通过 createSubagentContext() 获得自己的 ToolUseContext

src/tools/AgentTool/runAgent.ts
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
}

Sub-agent 可以在不同的 permission mode 下运行,控制 tool 执行的授权方式。模式通过 agent 定义的 permissionMode 字段设置。

模式行为用户提示?日志典型用途
default正常权限检查✅ 是完整交互式 sub-agent
bypassPermissions跳过所有权限检查❌ 否最少受信任的自定义 agent(如 code-reviewer)
dontAsk自动允许但仍记录日志❌ 否完整内置 agent 如 Claude Code Guide
bubble将提示浮升到父级终端✅ 是(父级终端)完整Fork 子进程

permission mode 通过自定义的 getAppState 包装器注入到 sub-agent 的 AppState 中:

// src/tools/AgentTool/runAgent.ts — permission mode override
const 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 模式是 fork 子进程独有的。当 fork 子进程需要运行某个 tool(如 Bash)的权限时,权限提示会浮升到父 agent 的终端,而不是在子进程的上下文中显示。这是因为 fork 子进程共享父级的终端会话。

// src/tools/AgentTool/forkSubagent.ts — FORK_AGENT definition
export const FORK_AGENT = {
// ...
permissionMode: 'bubble', // Permission prompts surface to parent terminal
tools: ['*'], // Full tool access (filtered at runtime)
}
场景推荐模式原因
Explore/Plan(只读)default + disallowedTools通过 tool 列表限制,而非绕过权限
受信任的 CI/CD agentbypassPermissions非交互式,预批准的操作
文档查询dontAsk低风险,但保留审计追踪
用户在旁的 Fork 子进程bubble用户可通过父级终端实时批准
后台研究default + async用户不在时自动跳过高风险 tool

Claude Code 有四种不同的子 agent 派生模式。理解何时使用每种至关重要:

方面Named Sub-AgentFork ChildCoordinator WorkerTeam 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 ModeAgent 定义(默认:acceptEditsbubble(浮升到父级)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

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 沙箱。

src/tools/AgentTool/AgentTool.tsx
// 1. Parent decides to spawn → AgentTool.call()
Agent({ prompt: "Find all usages of X", subagent_type: "Explore" })
// 2. Resolve definition → loadAgentsDir.ts
const { activeAgents } = await getAgentDefinitionsWithOverrides(cwd)
const selected = activeAgents.find(a => a.agentType === 'Explore')
// 3. Assemble tools → agentToolUtils.ts
const { resolvedTools } = resolveAgentTools(selected, availableTools, isAsync)
// 4. Execute → runAgent.ts
for await (const msg of runAgent({
agentDefinition: selected,
promptMessages: [userMessage],
toolUseContext,
availableTools: resolvedTools,
model: resolvedModel,
})) {
// 5. Messages flow back to parent
}