子代理 / Task / coordinator
前四天(Day 11-14)都在讲"给主 Agent 加料";今天是第 3 周收官——主 Agent 怎么派生"子 Agent"分身并行干活。贴真实代码看一个惊人简单的真相:子代理就是递归调用 query()。这是"统一抽象"主线的最后一块拼图。
为什么要子代理
Day 08 见过 Agent 工具(别名 Task)。当主 Agent 遇到一个"值得独立探索"的子任务时——比如"在整个代码库里搜集所有跟认证相关的代码"——它可以派一个子代理去干。好处:
- 上下文隔离:子代理搜集过程产生的一大堆中间信息(读了 50 个文件)不污染主对话,主 Agent 只拿到最终结论。
- 并行:可以同时派多个子代理(Day 08 讲 Agent 工具 isConcurrencySafe=true)。
- 专注:子代理有自己的 system prompt,可专门为某类任务优化。
子代理 = 递归调用 query()(真实代码)
核心洞察极简:子代理就是再跑一遍 Day 06 讲的那个 query() 循环,只是换一套输入。真实代码(packages/builtin-tools/src/tools/AgentTool/runAgent.ts:776):
for await (const message of query({
messages: initialMessages, // 子代理自己的初始消息
systemPrompt: agentSystemPrompt, // 子代理专属 system prompt
userContext: resolvedUserContext,
canUseTool,
toolUseContext: agentToolUseContext, // ★ 带独立 agentId
querySource,
maxTurns: maxTurns ?? agentDefinition.maxTurns,
})) {
onQueryProgress?.()
// ... 消费子代理吐出的消息
}
query()(Day 06 那个循环层入口),只是传进去的是"子代理自己的 messages / systemPrompt / agentId"。主 Agent 跑 query,query 里模型调 Agent 工具,Agent 工具内部又跑一个 query——就像函数递归调用自己。initialMessages = 只有一句"调研认证相关代码"、systemPrompt = 子代理专属、agentId = 新生成 → 同一个 query() 就跑起一个隔离的子会话,跑完只把最后一句话交回主循环。独立上下文:互不干扰
独立 messages
独立 messages
每个子代理有自己独立的一套:messages、systemPrompt、agentId、abortController(就是 L02 代码里传的那些参数)。它跑它的循环,主 Agent 看不到它的中间过程。
因为 toolUseContext.agentId 存在,query.ts 里很多"面向主 UI"的分支会跳过——子代理不生成工具调用摘要、不发 profiler、不跑 Stop hook 的后台任务,因为它不直接面对你的界面。
<sessionId>/subagents/agent-<agentId>.jsonl)。结果怎么汇回主代理
子代理跑完,怎么把结论给主 Agent?finalizeAgentTool(agentToolUtils.ts:277)取子代理最后一条 assistant 消息的文本作为结果。
content 变成 Agent 工具的 tool_result,按 Day 03 的回填机制进入主 query() 的下一轮消息——于是主 Agent"看到"了子代理的报告,继续往下推理。所以子代理对主 Agent 而言,就是一个"输入任务描述、输出一段报告"的普通工具调用——只不过这个工具内部跑了一整个 Agent。这正好也解释了 Day 08 为什么 Agent 工具标 isReadOnly:true(它对主流程的直接效果就是返回一段文本)。工具裁剪:子代理能用啥
子代理可用的工具会被裁剪——有个禁用清单 ALL_AGENT_DISALLOWED_TOOLS(src/constants/tools.ts),如 ExitPlanMode、AskUserQuestion、TaskStop、LocalMemoryRecall 等。
AskUserQuestion(问用户问题)——子代理是后台干活的,问了谁答?ExitPlanMode(退出规划模式)——那是主流程的状态。把这些从子代理的工具池去掉,防止它调用没意义/会捣乱的工具。这跟 gov-agents 教程里 subagent 只能用受限工具集是同一种"按角色限权"思想。子代理调用它被允许的工具(如 Read/Grep/Bash)时,权限检查照常生效(Day 09)——回扣 Day 08:"派子代理不弹权限,子代理干的活逐个受权限管"(权限下放)。
Task:后台任务的上层抽象
src/Task.ts 是比"同步子代理"更上层的后台任务抽象。它定义了几种 TaskType:local_bash(后台跑命令)、local_agent(后台跑子代理)、remote_agent、in_process_teammate(coordinator 用)、local_workflow(Day 19)。
run_in_background: true 的 Agent 就走 Task 路径。这让 Agent 能"边干主线边挂几个后台任务",像你开几个终端标签页并行跑东西。coordinator:一个协调者 + 一群 worker
coordinator 模式(feature('COORDINATOR_MODE'))是更成体系的多代理编排——回扣 gov-agents 教程 Day 04 的 supervisor 模式:
只派活、不亲自干
coordinatorMode.ts:getCoordinatorSystemPrompt()给协调者一段"你是协调者,用 Agent 工具派 worker"的 prompt。workerAgent.ts:定义WORKER_AGENT,worker 的工具是"异步代理允许工具集"减去编排工具。- 机制:协调者是主 query 循环里的模型,通过调 Agent 工具(
subagent_type: "worker")派发;worker 结果以<task-notification>消息回到协调者。
第 3 周收官 + 动手
🧠 第 3 周(扩展能力)自测
- 为什么要子代理?(上下文隔离、并行、专注)
- 子代理本质是什么?(递归跑 query(),用独立参数)
- 子代理中间过程为什么不污染主对话?结果怎么汇回?(只取最后一条 assistant 文本)
- 为什么裁剪子代理工具?同步子代理 vs Task 的区别?
- coordinator vs 普通子代理 vs workflow?
回顾第 3 周(都印证同一主题——统一抽象):Day 11 一切是 Command → Day 12 MCP 工具是 CoreTool → Day 13 skill 是 prompt Command → Day 14 hooks 挂生命周期 → Day 15 多代理是递归 query。Claude Code 的可扩展性 = 少数几个统一原语 + 各种来源都实现它。
✋ 动手:对着真实代码读一遍
# 1. 子代理 = 递归 query(L02,本页精华)
sed -n '773,795p' packages/builtin-tools/src/tools/AgentTool/runAgent.ts
# 2. 结果汇回(L04)
sed -n '277,320p' packages/builtin-tools/src/tools/AgentTool/agentToolUtils.ts
# 3. 子代理工具裁剪清单(L05)
grep -n "ALL_AGENT_DISALLOWED_TOOLS" src/constants/tools.ts
# 4. Task 类型 + coordinator(L06/L07)
sed -n '6,20p' src/Task.ts
grep -n "getCoordinatorSystemPrompt\|WORKER_AGENT" src/coordinator/*.ts
# 5. 实操:让主 Agent 派子代理
bun run dev # 输入:用 3 个子代理分别调研 A/B/C,汇总给我