Subagent 架构的重点不是“多叫几个模型”。它的价值在于把不同职责放进隔离上下文,用不同工具权限和输出契约控制任务质量。
本课的核心模型是:主 agent 是调度者,planner 负责拆步骤,researcher 负责压缩事实,implementer 负责改代码,tester 负责验证,reviewer 负责风险审查。每个子 agent 拿到的上下文、工具和完成标准都不同,最后由主 agent 汇总结构化结果,而不是让所有子 agent 共享一段越来越混乱的对话。
前面课程里的 agent 多数是单体:读代码、计划、修改、测试、总结都在同一个上下文里完成。真实工程任务会出现这些问题:
- 研究阶段读了大量文件,污染实现阶段上下文。
- reviewer 如果能写文件,就可能把“审查”变成“顺手修改”。
- tester 只需要测试输出,不需要完整源码上下文。
- 主 agent 同时收到多个子任务结果时,需要可比较的结构,而不是几段自由发挥的回答。
本课从 /Users/ryanchen/codespace/pi-agent-course-lab/src/11-subagent-router.ts 出发,设计一个角色路由器,让不同 subagent 以不同上下文和工具执行。
Subagent 系统可以拆成五个对象:
AgentRole:角色,例如 planner、researcher、implementer、tester、reviewer。AgentSpec:角色配置,包含目标、工具、模型、上下文策略和输出格式。TaskEnvelope:主 agent 发给子 agent 的任务包,包含任务、边界、输入摘要和验收标准。AgentResult:子 agent 返回的结构化结果,包含状态、发现、变更、证据和风险。Aggregator:主 agent 汇总结果,决定下一步是继续、返工、测试、审查还是结束。
变量流是:用户任务先进入主 agent;主 agent 选择角色并构造 TaskEnvelope;router 根据角色生成 prompt、tools、contextPolicy;子 agent 独立运行;结果回到 aggregator。
1. 从角色配置开始
Section titled “1. 从角色配置开始”starter 里有四个角色:planner、implementer、tester、reviewer。课程里补上 researcher,形成更完整的五角色模型:
planner -> 拆步骤,只读researcher -> 查事实,压缩上下文,只读implementer -> 修改代码,可写tester -> 跑检查,主要 bashreviewer -> 审查 diff,读和只读 bash每个角色不是换一个名字,而是换一套工具权限、上下文策略和输出契约。
2. 上下文隔离:给子 agent 最小必要输入
Section titled “2. 上下文隔离:给子 agent 最小必要输入”contextPolicy 决定子 agent 看什么:
full-task:任务描述、关键源码、约束、计划。compressed-research:researcher 输出的文件列表、关键类型、风险点。diff-only:reviewer 只看 diff、测试证据和相关文件。test-output:tester 只看运行命令、失败输出和环境信息。
上下文隔离的目标是让每个 agent 更稳定。实现者不需要读完整探索历史;reviewer 不应该继承 implementer 的自我辩护;tester 不需要知道所有设计争论。
3. 工具隔离:角色权限默认收窄
Section titled “3. 工具隔离:角色权限默认收窄”第 19 课的权限继承规则在这里生效:
effectiveTools = parentTools ∩ roleTools ∩ taskTools即使主 agent 允许写文件,planner 和 reviewer 默认也不应该拿到 edit、write。tester 的 bash 也应限制为测试命令或只读诊断命令。
4. 子 agent 执行形态
Section titled “4. 子 agent 执行形态”Pi 示例 extension 使用单独的 pi 进程运行 subagent,并通过 --append-system-prompt 注入角色提示,通过 --tools 注入工具列表。这种形态天然提供上下文隔离:子 agent 有自己的 prompt、消息、工具和模型。
课程项目可以先不实现完整进程管理,但要保留同样的抽象:
runSubagent({ role, prompt, tools, cwd, context,});后续如果换成本地函数调用、队列任务或远程 worker,主 agent 的调度逻辑不需要大改。
5. 结果汇总不要只拼接文本
Section titled “5. 结果汇总不要只拼接文本”每个子 agent 都应该返回结构化结果:
type AgentResult = { role: AgentRole; status: "ok" | "blocked" | "failed"; summary: string; artifacts: string[]; evidence: string[]; risks: string[]; nextAction?: string;};主 agent 只根据这些字段决策:planner 输出步骤;researcher 输出事实;implementer 输出改动;tester 输出测试证据;reviewer 输出 findings。自由文本可以保留,但不应该是唯一接口。
6. 主 agent 的控制点
Section titled “6. 主 agent 的控制点”主 agent 至少有四个控制点:
- 路由:派哪个角色,是否并行。
- 权限:给哪些工具和 cwd。
- 截止条件:什么结果算完成。
- 汇总:是否进入下一轮 rework。
这些控制点不能下放给子 agent 自己决定,否则 subagent 只是另一个会漂移的单体 agent。
本课实践基于 /Users/ryanchen/codespace/pi-agent-course-lab/src/11-subagent-router.ts。starter 的 routeSubagent(role, task) 已经返回 role、goal、tools、contextPolicy 和 prompt。
把它扩展成更完整的 router:
type SubagentRequest = { role: AgentRole; task: string; parentTools: string[]; context: Record<string, string>;};
function routeSubagent(req: SubagentRequest) { const spec = specs[req.role]; const tools = intersect(req.parentTools, spec.tools);
return { role: spec.role, tools, context: selectContext(req.context, spec.contextPolicy), prompt: buildRolePrompt(spec, req.task), };}变量流:role 选中 spec;parentTools 和 spec.tools 求交集得到有效工具;contextPolicy 从父上下文中挑选最小输入;prompt 把目标、边界和输出格式固定下来。
课堂代码推演
Section titled “课堂代码推演”课堂上用一个“修复登录测试失败”的任务跑一遍五角色链:
const task = "Fix login test failure";
const research = routeSubagent({ role: "researcher", task, parentTools: ["read", "grep", "find", "ls", "edit", "bash"], context: { userRequest: task, repoSummary },});
const plan = routeSubagent({ role: "planner", task: `Use research result: ${researchSummary}`, parentTools: research.tools, context: { researchSummary },});
const impl = routeSubagent({ role: "implementer", task: planSteps, parentTools: ["read", "grep", "edit", "bash"], context: { planSteps, sourceAnchors },});这里故意让学生看到一个细节:planner 的 parentTools 如果来自 researcher,就只能读;implementer 要由主 agent 重新授权写工具,不能让 planner 自己升级权限。
再看 reviewer:
const review = routeSubagent({ role: "reviewer", task: "Review generated diff", parentTools: ["read", "grep", "find", "ls", "bash"], context: { diff, testOutput },});reviewer 的上下文是 diff-only,工具也不包含 edit。这样它的产物是审查报告,而不是混入修改行为。主 agent 收到 reviewer findings 后,再决定是否把返工任务派给 implementer。
/Users/ryanchen/codespace/pi-agent-course-lab/src/11-subagent-router.ts:本课 lab starter,展示AgentRole、AgentSpec、contextPolicy和routeSubagent()。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/README.mdat61babc2:说明 subagent 示例的隔离上下文、并行执行、链式执行、usage tracking 和安全模型。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/index.tsat61babc2:subagent tool 的主实现,包含 single、parallel、chain 三种模式、子进程启动、--tools和--append-system-prompt。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents.tsat61babc2:从用户级和项目级.pi/agents/*.md发现 agent 定义,并解析 frontmatter 中的工具和模型。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/scout.mdat61babc2:researcher/scout 类角色示例,负责压缩代码调查结果。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/planner.mdat61babc2:planner 角色示例,只读分析并输出实现计划。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/worker.mdat61babc2:implementer/worker 类角色示例,负责完成委派任务并报告改动。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/reviewer.mdat61babc2:reviewer 角色示例,使用只读审查策略输出 findings。/Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/src/core/agent-session.tsat61babc2:setActiveToolsByName()和工具 registry 刷新逻辑,是子 agent 工具隔离的底层依据。
学完本课后,你应该能做到:
- 解释 planner、researcher、implementer、tester、reviewer 五类角色的职责边界。
- 设计每个角色的工具集合、上下文策略和输出契约。
- 说明为什么 subagent 要隔离上下文,而不是共享主 agent 的完整对话。
- 写出一个 router,把
role + task + parentTools + contextPolicy转成可执行子 agent 请求。 - 设计主 agent 的结果汇总逻辑,能根据子 agent 状态决定继续、返工、测试或结束。
- 实践题:扩展
/Users/ryanchen/codespace/pi-agent-course-lab/src/11-subagent-router.ts,增加researcher角色和AgentResult类型,并让routeSubagent()返回有效工具交集和结构化输出要求。 - 思考题:为什么 reviewer 子 agent 默认不应该拥有
edit和write?如果 reviewer 发现一个很小的问题,应该由谁来决定是否修改?