从0打造编码Agent 岔路 阅读 ≈ 15 分钟
A7 子 agent:一个 agent 忙不过来、或需要别人把关时,怎么分工? 岔路课

Pi 为什么不内置子 agent?

Pi 为什么不内置子 agent
Pi 在 pin 版 README 里写着「No sub-agents」,理由只有一句:做法太多,用 tmux 起几个 pi,或者自己写扩展。同一个仓库的 examples 里就有一个 1,141 行的子 agent 扩展,而权限确认扩展只有 34 行。把各家子 agent 实现的行数并排,「最小核心 + 最大扩展面」就从口号变成了可以量的工程取舍。不内置不等于反对多 agent。
❓

本课一问:这一章的每一家都有子 agent,Pi 却在 README 里写着「No sub-agents」。它是觉得子 agent 不好,还是有别的考虑?

这一课先不碰:Pi 为什么也不做沙箱(A4 岔路 B2)、不做权限弹窗(A3 岔路 B2)——它们是同一条设计立场的三个侧面。

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过

先自己设计:别家都有,Pi 也内置一个

最直接的想法:子 agent 这么有用,核心里放一个就是了。问题是放哪一种。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案核心内置一种子 agent

把第 1-4 课的对照表叠在一起:交接写什么、从什么开始、交回什么、预算按层还是共用、深度几层、报告信不信——每一格都有至少两家的答案不同,而且都能说出理由。

它原话说了什么

截至本课的 pin(31b513e3),Pi 的 coding-agent README 有一段 Philosophy,逐条列出核心不做的东西,每条给一个替代:

README.md pi@31b513e3:packages/coding-agent/README.md:494-504需联网 MIT 最新版已变化 · 勘误表 P1
494## Philosophy
495
496Pi is aggressively extensible so it doesn't have to dictate your workflow. Features that other tools bake in can be built with [extensions](#extensions), [skills](#skills), or installed from third-party [pi packages](#pi-packages). This keeps the core minimal while letting you shape pi to fit how you work.
497
498**No MCP.** Build CLI tools with READMEs (see [Skills](#skills)), or build an extension that adds MCP support. [Why?](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/)
499
500**No sub-agents.** There's many ways to do this. Spawn pi instances via tmux, or build your own with [extensions](#extensions), or install a package that does it your way.
501
502**No permission popups.** Run in a container, or build your own confirmation flow with [extensions](#extensions) inline with your environment and security requirements.
503
504**No plan mode.** Write plans to files, or build it with [extensions](#extensions), or install a package.
👆 带下划线的行可以点:点哪行,讲哪行。

角标「最新版已变化」:2026-09-22 的提交 25cc5c7 把这个 README 从约 712 行删到 70 行,Philosophy 整段不在了(勘误表 P1)。事实层面没变:核心代码里仍然没有子 agent。本课只引 pin 上的原文。

不是做不出来:扩展里就有

Pi 的仓库 examples/extensions/ 里,恰好放着这些「不做」的功能的实现。行数用 wc -l 实测(物理行,pin 31b513e3):

扩展做什么行数
subagent/index.ts + subagent/agents.ts子 agent:单个、并行、链式三种模式1,141
plan-mode/index.ts + plan-mode/utils.tsplan mode558
todo.tstodo 工具与 /todos 命令297
confirm-destructive.ts危险命令确认59
permission-gate.ts权限确认(rm -rf、sudo 等)34

permission-gate 只要 34 行,子 agent 要 1,141 行,另带 7 份 agent 与工作流定义(scout、planner、reviewer、worker 和三条链)。一个小到不值得占核心的复杂度,一个大到每个人想要的形态都不一样——两种理由都指向「放在核心外面」 。

这个扩展怎么实现「子 agent」,和第 1 课的 s06 完全不是一个层次:它另起一个 pi 进程。

index.ts pi@31b513e3:packages/coding-agent/examples/extensions/subagent/index.ts:294-331需联网 MIT
294 const args: string[] = ["--mode", "json", "-p", "--no-session"];
295 if (agent.model) args.push("--model", agent.model);
296 if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(","));
297
298 let tmpPromptDir: string | null = null;
299 let tmpPromptPath: string | null = null;
300
301 const currentResult: SingleResult = {
302 agent: agentName,
303 agentSource: agent.source,
304 task,
305 exitCode: 0,
306 messages: [],
307 stderr: "",
308 usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
309 model: agent.model,
310 step,
311 };
312
313 const emitUpdate = () => {
314 if (onUpdate) {
315 onUpdate({
316 content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
317 details: makeDetails([currentResult]),
318 });
319 }
320 };
321
322 try {
323 if (agent.systemPrompt.trim()) {
324 const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt);
325 tmpPromptDir = tmp.dir;
326 tmpPromptPath = tmp.filePath;
327 args.push("--append-system-prompt", tmpPromptPath);
328 }
329
330 args.push(`Task: ${task}`);
331 let wasAborted = false;
👆 带下划线的行可以点:点哪行,讲哪行。

交回父 agent 的,是子进程最后一条 assistant 消息里的文本(index.ts:170-180 的 getFinalOutput),和第 1 课各家一样。扩展里没有深度闸:子进程的启动参数里没有 --no-extensions,按扩展 README 的方式装在 ~/.pi/agent/extensions/ 下时,子进程启动时同样会加载它——能不能再派一层,取决于子进程加载了哪些扩展 。

各家的子 agent 有多大

同一件事——开一个新上下文、跑完交回一段话——在各家的代码量(物理行,各自的 pin,wc -l 实测):

实现统计范围行数
learn-claude-code s06run_subagent + task 工具定义66
CoreCoder v0.7.0tools/agent.py(整个文件)72
本课项目 miniagentsubagent.py(整个文件)109
OpenCode v1.18.32tool/task.ts + agent/subagent-permissions.ts398
Pi 31b513e3(扩展,非核心)examples/extensions/subagent/index.ts + agents.ts1,141
Hermes 56dc01d9tools/delegate_tool.py(整个文件)4,356
Codex 8cabf5atools/handlers/multi_agents*(v1 + v2 工具处理器,不含测试)3,589

差出两个数量级的,主要不是「子 agent」本身,而是围着它的东西:Hermes 的委派工具管并发批量、后台运行、摘要按父窗口余量限长并落盘、凭据与 provider 选择、TUI 里的实时进度;Codex 有 v1、v2 两套协作工具。learn-claude-code 的 66 行和本课项目的 109 行,是这件事的最小形态。

⚠
常见误解:「Pi 的作者明确拒绝 multi-agent。」

调研档案里这条说法经三方核查以 1-2 被否决。能说的只有:核心不内置子 agent,可以用扩展或 tmux 实现。仓库自己就带着一个子 agent 扩展;在建的下一代 harness 设计文档里,fork 与父子会话的关联(parentSessionId)被写成子 agent 的基础,同时注明「Harness has no subagent tool」,子 agent 由应用自己实现(packages/agent/docs/harness-v2.md:3894-3895,该文档在最新版已并入 harness.md,勘误表 P6)。

harness-v2.md pi@31b513e3:packages/agent/docs/harness-v2.md:3889-3896需联网 MIT 最新版已变化 · 勘误表 P6
3889- Copy conversation entries without source lanes, including non-projecting responses, but no orchestration/classification/usage records. A copied compaction's self-contained tail preserves exact overflow omission without its link. A fork before it uses entry-local projection: omit `error`/`aborted`/`deferred`, retain `stop`/general `length`. The fork is idle with zero token/cost ledger, visible entry usage snapshots, and `messageCount` from copied messages.
3890- `scope: "branch"` creates only `main` at the fork point with a fresh config equal to snapshot source `main`. `scope: "tree"` copies every lane name/leaf and gives each its snapshot config. Atomic configured-lane creation writes new destination records, never source history or anchor-derived config. No other lane records copy, so all lanes are idle.
3891- Tree scope copies current name, labels, and custom facts. Branch scope copies name/custom facts and labels only for copied targets. Deletions stay absent; custom JSON null remains. Destination writes fresh history; facts have no ids.
3892- Any message may be the fork point. A mid-tool-batch tip remains promptable because pi-ai inserts empty results for orphaned calls at request build.
3893- Forking leaves the source untouched and copies only its coherent committed prefix, including committed open-operation entries but not its records or promised output.
3894- Linkage is `parentSessionId`, set by `fork()` and settable on `create()` — the basis for subagent parent/child tracking and export bundles.
3895- **Non-normative example.** Harness has no subagent tool. An application may derive a child session id from parent id plus provider `toolCallId`, so safe replay reopens rather than duplicates it. Core does not depend on this.
3896- Policy, restated from Part I: a platform thread that shares history with its channel is a lane; a fork is for isolation — subagents, exports, clones. A subagent can also run on a lane of its parent's session when isolation is not wanted.
👆 带下划线的行可以点:点哪行,讲哪行。

证据三问

问本课的回答
① Pi 说了什么?核心不内置子 agent,理由是做法太多,替代是 tmux、扩展或第三方包
② 为什么可能是合理的取舍?子 agent 的形态(同步 / 后台、交接写什么、预算与深度)每家都不一样;放进核心就得替所有用户选一种
③ 量得出来的是什么?行数:权限确认扩展 34 行,子 agent 扩展 1,141 行;各家实现从 66 行到 4,356 行

演进地图

这条岔路是演进图上的另一条旁支:不在「怎么做子 agent」的链上,而在「要不要做进核心」这一问上。

子 agent 的演进:每一代都被上一代的某个真数字逼出来
  1. 所有 harness 的默认形态;本课项目 T4 的「不用子 agent」两次运行
    解决了不需要交接:主 agent 亲眼看到每一行搜索结果,不会有信息在转手时丢失。
    卡在两次 grep 结果进主上下文 563 / 1,120 token,之后每轮跟着重发(A5);要判对兼容性还得读 7 个文件(内容 1,818 token),两次都没读——找到 5/6 与 5/6,兼容性判对 3/6 与 2/6。
  2. learn-claude-code s06、CoreCoder AgentTool、OpenCode task、Claude Code 子 agent、本课项目 delegate
    解决了搜索的中间结果留在子 agent 自己的窗口里;4 组对照里 3 组主上下文峰值更低。
    卡在sub-text-s2 的子 agent 在自己的 grep 里看到了 5 个真实调用点,报回的 9 个「调用点」里 9 个是 import、注释或定义行;父 agent 照抄,verifier 找到 0/6。
  3. 本课项目 with_decisions、Hermes 的 context 字段、Codex v2 的 fork_turns(默认 all)、Claude Code 的 fork
    解决了子 agent 拿到了父 agent 的全部约束;sub-dec-s1 的子 agent 报回的真实调用点行号 5/6。
    卡在两次仍然 0/6:s1 的子 agent 一个文件都没读就猜兼容性,父 agent 全程 write_file 0 次却说「已写进 CALLERS.md」;s2 的子 agent 正则写错,15 轮预算只用了 3 轮就交差。
  4. OpenCode subagent_depth、Codex agent_max_depth、Hermes max_spawn_depth(默认都是 1);Claude Code 默认 3 层;Hermes、CoreCoder、lcc、本课项目的子 agent 预算独立
    解决了递归有上界;子 agent 的轮数不挤占父 agent:sub-dec-s1 的父 agent 只需要 3 轮,父子共用一个计数时得给到 7。
    卡在预算防的是停不下来,不防过早收工:T4 的 4 个子 agent 各有 15 轮,最多只用了 4 轮。
  5. learn-claude-code s17 独立评估器、Codex Guardian(A3)、Anthropic cwc-long-running-agents 的 evaluator、Hermes 后台自省 fork
    解决了完成与否不再由干活的 agent 自己说了算;评审者没有写工具,不能顺手「修好」来掩盖问题。
    卡在T1 与 T4 里最后一句话明确声称完成 20 次,verifier 判失败 12 次;评审者也是模型,也会被说服 → 确定性的判决在 A9。
旁支 多个 agent 同时改代码:worktree 隔离工作目录,同一文件的写串行
learn-claude-code s13、Codex(0.156 起默认开 worktree)、CoreCoder FILE_MUTATION_LOCK、Pi withFileMutationQueue
本课项目的子 agent 是同步的、只读的,T4 里不会发生写冲突;并行写同一个文件时才需要它们。worktree 不是沙箱。
旁支 Pi:核心不内置子 agent,交给扩展 你在这里
Pi 31b513e3 README「No sub-agents」;examples/extensions/subagent
不是做不出来:子 agent 扩展 1,141 行,权限确认扩展 34 行,都放在核心之外。

数字口径:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit 本机录制的 T4(2026-09-26,找全 parse_date 调用点,6 次运行 + 4 条子 agent trace;单一模型、每种配置 2 个 seed,只说明现象存在,不说明比例)。token 取自服务端 usage;「内容 token」用录制模型的 tokenizer 现数。耗时不作比较(本机有其他任务同时占用)。

一句话

📌
不内置不等于不支持:Pi 把子 agent 留给扩展,核心只保留能被扩展接上的接口。

回到主线

子 agent 只拿到一句任务描述,会丢掉什么? → 第 2 课

你可能会问