sub-agents.mdx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. ---
  2. title: "子 Agent 机制 - AgentTool 的执行链路与隔离架构"
  3. description: "从源码角度解析 Claude Code 子 Agent:AgentTool.call() 的完整执行链路、Fork 子进程的 Prompt Cache 共享、Worktree 隔离、工具池独立组装、以及结果回传的数据格式。"
  4. keywords: ["子 Agent", "AgentTool", "任务委派", "forkSubagent", "子进程隔离"]
  5. ---
  6. {/* 本章目标:从源码角度揭示子 Agent 的完整执行链路、工具隔离、通信协议和生命周期管理 */}
  7. ## 执行链路总览
  8. 一条 `Agent(prompt="修复 bug")` 调用的完整路径:
  9. ```
  10. AI 生成 tool_use: { prompt: "修复 bug", subagent_type: "Explore" }
  11. AgentTool.call() ← 入口(AgentTool.tsx:239)
  12. ├── 解析 effectiveType(fork vs 命名 agent)
  13. ├── filterDeniedAgents() ← 权限过滤
  14. ├── 检查 requiredMcpServers ← MCP 依赖验证(最长等 30s)
  15. ├── assembleToolPool(workerPermissionContext) ← 独立组装工具池
  16. ├── createAgentWorktree() ← 可选 worktree 隔离
  17. runAgent() ← 核心执行(runAgent.ts:248)
  18. ├── getAgentSystemPrompt() ← 构建 agent 专属 system prompt
  19. ├── initializeAgentMcpServers() ← agent 级 MCP 服务器
  20. ├── executeSubagentStartHooks() ← Hook 注入
  21. ├── query() ← 进入标准 agentic loop
  22. │ ├── 消息流逐条 yield
  23. │ └── recordSidechainTranscript() ← JSONL 持久化
  24. finalizeAgentTool() ← 结果汇总
  25. ├── 提取文本内容 + usage 统计
  26. └── mapToolResultToToolResultBlockParam() ← 格式化为 tool_result
  27. ```
  28. ## 两种子 Agent 路径:命名 Agent vs Fork
  29. `AgentTool.call()` 根据是否提供 `subagent_type` 走两条完全不同的路径(`AgentTool.tsx:322-356`):
  30. | 维度 | 命名 Agent(`subagent_type` 指定) | Fork 子进程(`subagent_type` 省略) |
  31. |------|-------------------------------------|--------------------------------------|
  32. | **触发条件** | `subagent_type` 有值 | `isForkSubagentEnabled()` && 未指定类型 |
  33. | **System Prompt** | Agent 自身的 `getSystemPrompt()` | 继承父 Agent 的完整 System Prompt |
  34. | **工具池** | `assembleToolPool()` 独立组装 | 父 Agent 的原始工具池(`useExactTools: true`) |
  35. | **上下文** | 仅任务描述 | 父 Agent 的完整对话历史(`forkContextMessages`) |
  36. | **模型** | 可独立指定 | 继承父模型(`model: 'inherit'`) |
  37. | **权限模式** | Agent 定义的 `permissionMode` | `'bubble'`(上浮到父终端) |
  38. | **目的** | 专业任务委派 | Prompt Cache 命中率优化 |
  39. Fork 路径的设计核心是 **Prompt Cache 共享**:所有 fork 子进程共享父 Agent 的完整 `assistant` 消息(所有 `tool_use` 块),用相同的占位符 `tool_result` 填充,只有最后一个 `text` 块包含各自的指令。这使得 API 请求前缀字节完全一致,最大化缓存命中。
  40. ```typescript
  41. // forkSubagent.ts:142 — 所有 fork 子进程的占位结果
  42. const FORK_PLACEHOLDER_RESULT = 'Fork started — processing in background'
  43. // buildForkedMessages() 构建:
  44. // [assistant(全量 tool_use), user(placeholder_results..., 子进程指令)]
  45. ```
  46. ### Fork 递归防护
  47. Fork 子进程保留 Agent 工具(为了 cache-identical tool defs),但通过两道防线防止递归 fork(`AgentTool.tsx:332`):
  48. 1. **`querySource` 检查**(压缩安全):`context.options.querySource === 'agent:builtin:fork'`
  49. 2. **消息扫描**(降级兜底):检测 `<fork-boilerplate>` 标签
  50. ## 工具池的独立组装
  51. 子 Agent 不继承父 Agent 的工具限制——它的工具池完全独立组装(`AgentTool.tsx:573-577`):
  52. ```typescript
  53. const workerPermissionContext = {
  54. ...appState.toolPermissionContext,
  55. mode: selectedAgent.permissionMode ?? 'acceptEdits'
  56. }
  57. const workerTools = assembleToolPool(workerPermissionContext, appState.mcp.tools)
  58. ```
  59. 关键设计决策:
  60. - **权限模式独立**:子 Agent 使用 `selectedAgent.permissionMode`(默认 `acceptEdits`),不受父 Agent 当前模式的限制
  61. - **MCP 工具继承**:`appState.mcp.tools` 包含所有已连接的 MCP 工具,子 Agent 自动获得
  62. - **Agent 级 MCP 服务器**:`runAgent()` 中的 `initializeAgentMcpServers()` 可以为特定 Agent 额外连接专属 MCP 服务器
  63. ### 工具过滤的 resolveAgentTools
  64. `runAgent.ts:500-502` 在工具组装后进一步过滤:
  65. ```typescript
  66. const resolvedTools = useExactTools
  67. ? availableTools // Fork: 直接使用父工具
  68. : resolveAgentTools(agentDefinition, availableTools, isAsync).resolvedTools
  69. ```
  70. `resolveAgentTools()` 会根据 Agent 定义中的 `tools` 字段过滤可用工具,将 `['*']` 映射为全量工具。
  71. ## Worktree 隔离机制
  72. `isolation: "worktree"` 参数让子 Agent 在独立的 git worktree 中工作(`AgentTool.tsx:590-593`):
  73. ```typescript
  74. const slug = `agent-${earlyAgentId.slice(0, 8)}`
  75. worktreeInfo = await createAgentWorktree(slug)
  76. ```
  77. Worktree 生命周期:
  78. 1. **创建**:在 `.git/worktrees/` 下创建独立工作副本
  79. 2. **CWD 覆盖**:`runWithCwdOverride(worktreePath, fn)` 让所有文件操作在 worktree 中执行
  80. 3. **路径翻译**:Fork + worktree 时注入路径翻译通知(`buildWorktreeNotice`)
  81. 4. **清理**(`cleanupWorktreeIfNeeded`):
  82. - Hook-based worktree → 始终保留
  83. - 有变更 → 保留,返回 `worktreePath`
  84. - 无变更 → 自动删除
  85. ## 生命周期管理:同步 vs 异步
  86. ### 异步 Agent(后台运行)
  87. 当 `run_in_background=true` 或 `selectedAgent.background=true` 时,Agent 立即返回 `async_launched` 状态(`AgentTool.tsx:686-764`):
  88. ```
  89. registerAsyncAgent(agentId, ...) ← 注册到 AppState.tasks
  90. ↓ (void — 火后不管)
  91. runAsyncAgentLifecycle() ← 后台执行
  92. ├── runAgent().onCacheSafeParams ← 进度摘要初始化
  93. ├── 消息流迭代
  94. ├── completeAsyncAgent() ← 标记完成
  95. ├── classifyHandoffIfNeeded() ← 安全检查
  96. └── enqueueAgentNotification() ← 通知主 Agent
  97. ```
  98. 异步 Agent 获得独立的 `AbortController`,不与父 Agent 共享——用户按 ESC 取消主线程不会杀掉后台 Agent。
  99. ### 同步 Agent(前台运行)
  100. 同步 Agent 的关键特性是 **可后台化**(`AgentTool.tsx:818-833`):
  101. ```typescript
  102. const registration = registerAgentForeground({
  103. autoBackgroundMs: getAutoBackgroundMs() || undefined // 默认 120s
  104. })
  105. backgroundPromise = registration.backgroundSignal.then(...)
  106. ```
  107. 在 agentic loop 的每次迭代中,系统用 `Promise.race` 竞争下一条消息和后台化信号:
  108. ```typescript
  109. const raceResult = await Promise.race([
  110. nextMessagePromise.then(r => ({ type: 'message', result: r })),
  111. backgroundPromise // 超过 autoBackgroundMs 触发
  112. ])
  113. ```
  114. 后台化后,前台迭代器被终止(`agentIterator.return()`),新的 `runAgent()` 以 `isAsync: true` 重新启动,当前台的输出文件继续写入。
  115. ## 结果回传格式
  116. `mapToolResultToToolResultBlockParam()` 根据状态返回不同格式(`AgentTool.tsx:1298-1375`):
  117. | 状态 | 返回内容 |
  118. |------|---------|
  119. | `completed` | 内容 + `<usage>` 块(token/tool_calls/duration) |
  120. | `async_launched` | agentId + outputFile 路径 + 操作指引 |
  121. | `teammate_spawned` | agent_id + name + team_name |
  122. | `remote_launched` | taskId + sessionUrl + outputFile |
  123. 对于一次性内置 Agent(Explore、Plan),`<usage>` 块被省略——每周节省约 1-2 Gtok 的上下文窗口。
  124. ## MCP 依赖的等待机制
  125. 如果 Agent 声明了 `requiredMcpServers`,`call()` 会等待这些服务器连接完成(`AgentTool.tsx:371-410`):
  126. ```typescript
  127. const MAX_WAIT_MS = 30_000 // 最长等 30 秒
  128. const POLL_INTERVAL_MS = 500 // 每 500ms 轮询
  129. ```
  130. 早期退出条件:任何必需服务器进入 `failed` 状态时立即停止等待。工具可用性通过 `mcp__` 前缀工具名解析(`mcp__serverName__toolName`)判断。
  131. ## 适用场景
  132. <CardGroup cols={2}>
  133. <Card title="并行研究" icon="magnifying-glass">
  134. 多个 fork 子进程并行搜索不同方向,共享 Prompt Cache 前缀,只有指令不同
  135. </Card>
  136. <Card title="专业委派" icon="code-branch">
  137. 使用命名 Agent(Explore/Plan/verification)执行专业任务,受限工具集 + 独立权限
  138. </Card>
  139. <Card title="隔离实验" icon="flask">
  140. `isolation: "worktree"` 在独立工作副本中尝试方案,不影响主分支
  141. </Card>
  142. <Card title="后台构建" icon="layer-group">
  143. `run_in_background: true` 启动长时间构建/测试任务,主 Agent 继续工作
  144. </Card>
  145. </CardGroup>