随笔

Claude Code 深度源码解读 07:多 Agent 的核心是隔离与回灌

从 AgentTool、权限过滤、fork/background/worktree 分支和 query loop attachment drain 看多 Agent 协作。

2026-04-15 Claude Code源码解读Agent Harness技术深读

简化版入口:多 Agent / Task

机制图
机制图

我的设问与回应

设问 1:多 Agent 是不是多开几个模型?

回应答案: 不是。多 Agent 的核心是委托入口、上下文隔离、权限收缩、任务状态、结果回灌。

证据与证明路径: 证据来自第 7 期对 AgentTool.call、filter agents、normal subagent、fork subagent、background task、worktree isolation、query loop attachment drain 的拆解。

还值得继续学习或反思: 进一步要学习的是子任务结果如何结构化,避免主线程只得到一段不可审计总结。

设问 2:为什么多 Agent 首先要收束?

回应答案: 因为子任务会扩大副作用面。如果权限、上下文、输出和生命周期不收束,多 Agent 只是把错误并行化。

证据与证明路径: 证据是第 7 期对 fork guard、permission bubble、worktree isolation、background task progress/output file 的边界描述。

还值得继续学习或反思: 多 Agent 的产品价值不在数量,而在隔离与回灌协议。

源码调研主体

本组解读基于冻结源码快照进行教育、防御和架构研究。

本期主题:Harness 如何通过 AgentTool、fork subagent、background task、teammate/swarm、remote agent 和 task 通知,把复杂任务拆分为可跟踪、可恢复、可汇总的协作单元。

1. 研究问题

本期围绕一个问题展开:

Claude Code Harness 如何让一个模型会话把复杂问题拆给其他 agent,同时不丢失权限边界、进度状态和最终回灌?

普通 agent 的“多 agent”容易变成简单递归:主模型让子模型干活,子模型输出文本,主模型再读文本。这种方式的问题是:无法区分同步/异步、无法中断、无法恢复、无法把权限请求冒泡回主会话、无法持续显示进度,也无法保证子任务 transcript 可查。

源码中的协作机制不是一个单点功能,而是多条路径:

  • AgentTool:模型可调用的委托工具。
  • normal subagent:构造 agent system prompt 和独立工具池。
  • fork subagent:继承父上下文,异步执行,用 placeholder tool_result 保持轨迹。
  • background local agent task:进入 AppState tasks,写 sidechain transcript / output file。
  • teammate / swarm:以可寻址 agent name 和 team name 组织协作。
  • remote agent:在 CCR/remote session 中执行,通过 sidecar 元数据恢复。
  • task notification / pendingMessages:把异步结果重新注入主 query loop。

2. 对应总览节点

对应第 1 期总览图中的节点:

  • Tool Execution
  • AgentTool
  • Task registry
  • Query Loop
  • Transcript / Session State
  • Hooks
  • Permissions

这部分机制参考前文「多 Agent / Task 机制图」。

3. 核心流程图

路线图中的代表链路是:

AgentTool / Task tools / SendMessage / coordinator

源码中应按场景分支理解。下图用蓝色表示主 AgentTool 链路,绿色表示本地异步任务,紫色表示 teammate/swarm,橙色表示 remote,红色表示隔离/权限边界。

这部分机制参考前文「多 Agent / Task 机制图」。

4. 关键源码入口

职责文件
Agent 工具 schema、分支路由、调用入口src/tools/AgentTool/AgentTool.tsx
Agent 工具 promptsrc/tools/AgentTool/prompt.ts
agent 定义加载、MCP requirement、built-in/user agentsrc/tools/AgentTool/loadAgentsDir.ts
normal agent 运行src/tools/AgentTool/runAgent.ts
fork subagent 构造src/tools/AgentTool/forkSubagent.ts
agent 工具结果、handoff、progresssrc/tools/AgentTool/agentToolUtils.ts
task 注册表src/tasks.ts
local agent background tasksrc/tasks/LocalAgentTask/LocalAgentTask.tsx
remote agent tasksrc/tasks/RemoteAgentTask/RemoteAgentTask.tsx
teammate tasksrc/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx
task frameworksrc/utils/task/framework.ts
task output 文件src/utils/task/diskOutput.tssrc/utils/task/TaskOutput.ts
query loop 中 task notification drainsrc/query.ts
Stop / TaskCompleted / TeammateIdle hookssrc/query/stopHooks.ts
agent / teammate contextsrc/utils/agentContext.tssrc/utils/teammateContext.ts

4.1 主线与场景特殊处理分类

机制/模块分类为什么这样归类
AgentTool主线机制 + 场景增强聚合点是模型委托工作的统一工具入口;具体 normal/fork/background/team/remote 分支按 feature、输入和 agent 定义触发
filterAgentsByMcpRequirements()安全/治理横切agent prompt 暴露前先按当前 MCP server tool 可用性过滤
filterDeniedAgents() / Agent(name) deny rule安全/治理横切agent 类型也受权限规则控制,模型不能随意选择被 deny 的 agent
normal runAgent()主线机制常规 subagent 执行路径,构造 agent prompt、上下文、工具池并运行 query
worker tool pool安全/治理横切worker 通过 assembleToolPool() 独立组装工具,使用 agent permission mode,而不是直接复用父工具限制
fork subagent场景增强只在 FORK_SUBAGENT、非 coordinator、非 non-interactive 且未显式 subagent_type 时触发
fork recursion guard安全/治理横切fork child 保留 Agent tool 但调用时拒绝再 fork,防止递归扩散
background local agent task可观测/恢复支撑 + 场景增强run_in_background、agent background: true、coordinator、fork、assistant mode 等条件触发
LocalAgentTaskState.pendingMessages可观测/恢复支撑mid-turn SendMessage / prompt 被排队,在工具轮边界进入 agent 输入
task progress tracker可观测/恢复支撑记录 toolUseCount、tokenCount、recentActivities 和 summary,用于 UI/SDK/恢复
worktree isolation安全/治理横切 + 场景增强只在 explicit isolation 或 agent definition 要求时创建隔离 git worktree
remote agent场景增强 + 安全/治理横切依赖 remote eligibility、OAuth、组织策略、teleport 和 remote session API
teammate / swarm场景增强由 team_name/name、agent swarm feature、coordinator/team context 触发
stopHooks.ts 的 TaskCompleted / TeammateIdle可观测/恢复支撑teammate 完成路径上检查 in-progress task 和 idle 状态,可阻断继续

5. 机制拆解

5.1 AgentTool 的 schema 会按 feature 收缩模型可见参数

src/tools/AgentTool/AgentTool.tsx 定义 base schema:descriptionpromptsubagent_typemodelrun_in_background。full schema 再加入多 agent 参数 nameteam_namemode,以及 isolation / cwd。

源码没有简单把所有能力长期暴露给模型:

  • cwd 在非 KAIROS 分支被 omit。
  • background tasks disabled 或 fork subagent enabled 时,run_in_background 会被 omit。
  • remote isolation 在 external build 中被 dead-code 条件排除。

这说明“模型能请求的协作形态”先由 schema 层按 feature 和构建环境收缩。

5.2 agent 选择先过 MCP 和权限过滤

AgentTool 的 prompt 生成会读取当前 tools 中的 MCP server 列表,然后:

  1. filterAgentsByMcpRequirements(agents, mcpServersWithTools)
  2. filterDeniedAgents(..., toolPermissionContext, AGENT_TOOL_NAME)

调用时如果指定的 agent 被 deny,源码会查 getDenyRuleForAgent() 并抛出包含 deny rule 来源的错误。这说明 agent 类型本身也是权限对象,不是 prompt 文本中可随意选择的标签。

5.3 teammate / swarm 分支是可寻址协作,不是普通 subagent

teamName && name 成立时,AgentTool 走 spawnTeammate()。输入里的 name 会让 agent 变成可通过 SendMessage({to: name}) 寻址的 teammate。

源码还设置了两个边界:

  • teammate 不能再 spawn teammate,因为 roster 是扁平结构。
  • in-process teammate 不能 spawn background agents,因为生命周期绑定到 leader process。

query/stopHooks.ts 在 teammate 完成路径上会执行 TaskCompleted hooks 和 TeammateIdle hooks,并对 teammate 拥有的 in-progress tasks 做检查。这说明 teammate 是团队协作模型的一部分,不是简单的函数调用。

5.4 fork subagent 继承父上下文,但有递归 guard

forkSubagent.ts 明确写出 fork gate:

  • feature('FORK_SUBAGENT')
  • 非 coordinator mode
  • 非 non-interactive session

FORK_AGENT 是 synthetic agent definition,tools: ['*']model: 'inherit'permissionMode: 'bubble'。注释说明 fork child 接收父 exact tool pool 和父 system prompt,以保持 prompt cache prefix。

buildForkedMessages() 的关键是:

  1. 克隆父 assistant message,保留所有 tool_use blocks。
  2. 构造一个 user message,为每个 tool_use 生成相同 placeholder tool_result。
  3. 在最后追加本子任务 directive。

这保证 fork child 的 API 轨迹合法,并最大化多个 fork child 之间的 prefix cache 共享。isInForkChild() 通过 fork boilerplate tag 检测递归,AgentTool 调用时拒绝 fork child 再 fork。

5.5 normal subagent 有自己的 system prompt 和工具池

非 fork 路径会解析 selectedAgent,调用 selectedAgent.getSystemPrompt({ toolUseContext }),再通过 enhanceSystemPromptWithEnvDetails() 补充环境信息。

worker 工具池不是直接复用父会话的限制:AgentTool 构造 workerPermissionContext,把 mode 设为 selectedAgent.permissionMode ?? 'acceptEdits',再调用 assembleToolPool(workerPermissionContext, appState.mcp.tools)

这是一个重要边界:subagent 可以有不同默认权限模式和工具集,但仍通过统一工具池/权限体系组装。

5.6 background task 把异步 agent 变成可观察状态

AgentTool 会根据多个条件决定是否 async:

  • run_in_background === true
  • agent definition background: true
  • coordinator mode
  • fork subagent gate
  • assistant/KAIROS mode
  • proactive active
  • background tasks 未禁用

LocalAgentTaskState 记录:

  • agentId
  • prompt
  • selectedAgent
  • abortController
  • result / error
  • progress
  • messages
  • pendingMessages
  • retrieved
  • retain / diskLoaded / evictAfter

updateProgressFromMessage() 从 assistant message usage 和 tool_use blocks 中更新 token、工具调用数和最近活动。TaskOutput / diskOutput 负责 output file 和 sidechain 输出。这让异步 agent 不只是“后台跑了”,而是能被 UI、SDK、resume 和主循环重新观察。

5.7 task notification 在 query loop 的工具轮边界回灌

query.ts 中对 queued commands / task notifications 有明确过滤:

  • main thread 只 drain agentId === undefined
  • subagent 只 drain mode === 'task-notification' && cmd.agentId === currentAgentId
  • subagent 永远不消费 prompt stream。

这解释了多 agent 通知不会随意串线。异步 agent 完成后,通知以 attachment 形式进入后续 query iteration,由主模型解释和汇总,而不是直接改写主 assistant 输出。

5.8 worktree isolation 把文件副作用隔离到独立工作副本

AgentTool 在 effectiveIsolation === 'worktree' 时调用 createAgentWorktree(slug)。如果 fork path + worktree,还会追加 buildWorktreeNotice(parentCwd, worktreeCwd),要求子 agent 翻译路径、重读可能过期的文件,并说明改动隔离在 worktree。

这条链路的意义是防止并行 agent 直接竞争父工作区。它仍然不是默认所有 subagent 都有的隔离;必须由 isolation 参数或 agent definition 触发。

5.9 remote agent 是另一条 session 协作路径

remote isolation 分支会先 checkRemoteAgentEligibility(),不满足时抛出 precondition error。满足后调用 teleportToRemote() 创建 remote session,再用 registerRemoteAgentTask() 写入本地任务状态和 sidecar 元数据。

RemoteAgentTask.tsx 后续通过 pollRemoteSessionEvents() 增量获取 remote session log,解析 plan/review/todo/progress,并在完成或失败时更新 task 状态。恢复路径 restoreRemoteAgentTasks() 会从 session sidecar 读取远端任务元数据,再重新 poll。

因此 remote agent 是“远端 session + 本地 task shadow + sidecar 恢复”的组合,不是普通本地子进程。

6. 对稳定解决问题能力的贡献

6.1 复杂任务可拆分,但仍受统一入口控制

AgentTool 是单一模型可见入口,schema、permission rule、MCP requirement、agent definition 一起决定模型能启动什么 agent。

6.2 异步任务可观察、可中断、可恢复

LocalAgentTask 和 RemoteAgentTask 都把 agent 执行变成 AppState task,有 progress、output、abort/kill、sidecar 或 transcript 支撑。

6.3 并行 fork 保持 API 轨迹合法

fork child 继承父 assistant tool_use 后立即用 placeholder tool_result 补齐,避免 orphan tool_use 破坏 API 轨迹。

6.4 权限请求可以冒泡

fork agent 使用 permissionMode: 'bubble',teammate / in-process agent 也通过共享任务和权限回调路径接入主会话。这比让子 agent 静默执行高风险动作更可控。

6.5 协作结果以通知形式回到主循环

task notification / pendingMessages 在 query loop 边界 drain,主模型可以在后续 iteration 统一汇总,而不是把后台输出直接拼进当前 assistant 文本。

7. 风险、边界与待验证问题

风险/边界影响防御性解读
AgentTool 分支很多容易把 fork、team、remote 写成同一路径必须按输入参数、feature gate、构建分支和 agent definition 分开描述
worker tool pool 与父工具限制不同误判可能导致对子 agent 权限过度信任worker 仍通过 assembleToolPool() 和 permission mode 组装,但默认 mode 可能与父不同
fork child 继承完整父上下文子任务可能看到超出自身 scope 的信息fork directive 强制 scope、禁止再 fork,并可用 worktree 隔离文件副作用
background task 输出异步回灌主模型可能在结果未到时继续推进pending notification 只在后续 query iteration 进入,不能假定当前 turn 已看到结果
remote agent 依赖外部 session网络、OAuth、组织策略和 remote 状态都会影响可靠性源码用 eligibility、sidecar、poll restore 和 archive/kill 支撑,但不是本地必经能力

8. 下一期衔接

第 8 期进入“安全与信任边界总复盘”。多 Agent 把风险面扩大到子 agent、worktree、remote session、MCP 依赖和权限冒泡,因此下一期将横向复盘 permissions、sandbox、MCP approval、bridge auth、managed settings、workspace trust 和 hooks 如何共同防止 agent 稳定地做错事。

机制主线收束

站在架构师视角,我会把这组源码机制收束为三个问题:为什么需要它,什么时候必须引入它,以及真正要设计的是什么。

  • why:多 Agent 解决的是复杂任务分解,但也引入上下文泄漏、权限扩大和结果不可审计风险。
  • when:任务可明确拆分,且子任务输入、权限、输出格式可以定义时使用。
  • what:显式任务边界、默认权限收缩、结构化输出、父子 transcript 关系、background task 可恢复输出。

多 Agent / Task 这组源码最值得带走的,不是某个函数或某个配置项,而是它如何把模型能力放进一组可验证、可拒绝、可恢复、可审计的工程边界里。读源码时,如果只记住名词,会很快散;如果抓住 why、when、what,就能把这组机制迁移到自己的 agent 架构判断中。