跳转到内容

第 21 课:Subagent 架构

设计 planner、researcher、implementer、reviewer、tester 的上下文隔离、工具隔离和结果汇总。

Subagent 架构的重点不是“多叫几个模型”。它的价值在于把不同职责放进隔离上下文,用不同工具权限和输出契约控制任务质量。

本课的核心模型是:主 agent 是调度者,planner 负责拆步骤,researcher 负责压缩事实,implementer 负责改代码,tester 负责验证,reviewer 负责风险审查。每个子 agent 拿到的上下文、工具和完成标准都不同,最后由主 agent 汇总结构化结果,而不是让所有子 agent 共享一段越来越混乱的对话。

前面课程里的 agent 多数是单体:读代码、计划、修改、测试、总结都在同一个上下文里完成。真实工程任务会出现这些问题:

  1. 研究阶段读了大量文件,污染实现阶段上下文。
  2. reviewer 如果能写文件,就可能把“审查”变成“顺手修改”。
  3. tester 只需要测试输出,不需要完整源码上下文。
  4. 主 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。

starter 里有四个角色:plannerimplementertesterreviewer。课程里补上 researcher,形成更完整的五角色模型:

planner -> 拆步骤,只读
researcher -> 查事实,压缩上下文,只读
implementer -> 修改代码,可写
tester -> 跑检查,主要 bash
reviewer -> 审查 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 默认也不应该拿到 editwrite。tester 的 bash 也应限制为测试命令或只读诊断命令。

Pi 示例 extension 使用单独的 pi 进程运行 subagent,并通过 --append-system-prompt 注入角色提示,通过 --tools 注入工具列表。这种形态天然提供上下文隔离:子 agent 有自己的 prompt、消息、工具和模型。

课程项目可以先不实现完整进程管理,但要保留同样的抽象:

runSubagent({
role,
prompt,
tools,
cwd,
context,
});

后续如果换成本地函数调用、队列任务或远程 worker,主 agent 的调度逻辑不需要大改。

每个子 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。自由文本可以保留,但不应该是唯一接口。

主 agent 至少有四个控制点:

  1. 路由:派哪个角色,是否并行。
  2. 权限:给哪些工具和 cwd。
  3. 截止条件:什么结果算完成。
  4. 汇总:是否进入下一轮 rework。

这些控制点不能下放给子 agent 自己决定,否则 subagent 只是另一个会漂移的单体 agent。

本课实践基于 /Users/ryanchen/codespace/pi-agent-course-lab/src/11-subagent-router.ts。starter 的 routeSubagent(role, task) 已经返回 rolegoaltoolscontextPolicy 和 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;parentToolsspec.tools 求交集得到有效工具;contextPolicy 从父上下文中挑选最小输入;prompt 把目标、边界和输出格式固定下来。

课堂上用一个“修复登录测试失败”的任务跑一遍五角色链:

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,展示 AgentRoleAgentSpeccontextPolicyrouteSubagent()
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/README.md at 61babc2:说明 subagent 示例的隔离上下文、并行执行、链式执行、usage tracking 和安全模型。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/index.ts at 61babc2:subagent tool 的主实现,包含 single、parallel、chain 三种模式、子进程启动、--tools--append-system-prompt
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents.ts at 61babc2:从用户级和项目级 .pi/agents/*.md 发现 agent 定义,并解析 frontmatter 中的工具和模型。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/scout.md at 61babc2:researcher/scout 类角色示例,负责压缩代码调查结果。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/planner.md at 61babc2:planner 角色示例,只读分析并输出实现计划。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/worker.md at 61babc2:implementer/worker 类角色示例,负责完成委派任务并报告改动。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/subagent/agents/reviewer.md at 61babc2:reviewer 角色示例,使用只读审查策略输出 findings。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/src/core/agent-session.ts at 61babc2setActiveToolsByName() 和工具 registry 刷新逻辑,是子 agent 工具隔离的底层依据。

学完本课后,你应该能做到:

  • 解释 planner、researcher、implementer、tester、reviewer 五类角色的职责边界。
  • 设计每个角色的工具集合、上下文策略和输出契约。
  • 说明为什么 subagent 要隔离上下文,而不是共享主 agent 的完整对话。
  • 写出一个 router,把 role + task + parentTools + contextPolicy 转成可执行子 agent 请求。
  • 设计主 agent 的结果汇总逻辑,能根据子 agent 状态决定继续、返工、测试或结束。
  1. 实践题:扩展 /Users/ryanchen/codespace/pi-agent-course-lab/src/11-subagent-router.ts,增加 researcher 角色和 AgentResult 类型,并让 routeSubagent() 返回有效工具交集和结构化输出要求。
  2. 思考题:为什么 reviewer 子 agent 默认不应该拥有 editwrite?如果 reviewer 发现一个很小的问题,应该由谁来决定是否修改?