# [Project] 7. Orchestrator 模式 — 日耗五亿Token，我的节约自救方案

# Orchestrator 模式:日耗五亿Token,我的节约自救方案
> 日耗五百万 token,我闲庭信步;日耗五千万 token,我酣畅淋漓;日耗五亿 token,我汗流浃背。

## 背景与问题

### 单 Session 的上下文成本
LLM 的每次调用都需要将完整的对话历史传入,这意味着 context 随工具调用数线性增长。一个复杂任务执行 50 次工具调用后,第 51 次调用要携带前 50 条历史--不仅成本高,LLM 在过长的 context 中还容易失焦、漏掉关键信息。

<img src="/images/mermaid/orchestrator-zh-1.svg" alt="Figure 1" style="max-width:100%;">
**Figure 1.1 - Single session context grows linearly with every tool call**

### 无约束 Agent 的退化路径
如果不做任何限制,一个能力完整的 agent 面对复杂任务时会走向最简路径:

```
[user] → 帮我实现这个功能
[agent] → spawn_full_sub_agent(task="帮我实现这个功能")
// orchestration 能力完全失效,变成透明代理
```

read 和 write 混在一起执行,调查阶段的探索性操作和修改操作共享同一个执行流,出了问题无法定位是"看错了"还是"改错了"。

### 设计目标
本架构要同时解决三个问题:
- **context 膨胀**:主干 session 只积累决策,不积累执行细节
- **职责混乱**:强制分离"调查"和"修改",禁止全能 agent
- **模型资源浪费**:按任务复杂度分配模型,简单任务不用高价模型

## 三包架构总览
三个问题指向同一个根源:主 session 承担了不该由它承担的事--代码探索、文件修改、执行调试全部混入同一条 context 链。解法也因此收敛到同一个方向:**让主 session 只做规划,把执行外包给专门的子单元,并控制子单元的结果如何回流**。落地这个方向需要三个层次的能力--精准搜索(减少代码探索的 context 开销)、执行单元管理(强制 read/write 分离)、调度策略强制(防止主 session 退化为全能执行者)--对应三个协作的 extension 包。

### 包依赖关系
三个包形成清晰的层次:
- **pi-extension-tool-semble**:独立工具包,无依赖,提供语义代码搜索能力
- **pi-extension-session-subagent**:基础执行层,依赖 pi-coding-agent,提供 spawn 三件套
- **pi-extension-session-orchestrator**:调度增强层,依赖 subagent 包,通过 hook 强化主 session 的调度能力

<img src="/images/mermaid/orchestrator-zh-2.svg" alt="Figure 2" style="max-width:100%;">
**Figure 2.1 - Three-package dependency hierarchy**

### 各包职责定位
- **semble**:让 agent 不做盲目的全文件搜索,精准取相关代码片段
- **subagent**:把"生成一个子 LLM session 来执行任务"变成可调用的工具
- **orchestrator**:限制主 session 工具集,强制规划与执行分离,管理 session tree 分支

### 工具权限总览
| 工具 | 主 Session | read sub-agent | write sub-agent |
|:---|:---:|:---:|:---:|
| read | ✅ | ✅ | ✅ |
| semble_search | ✅ | ✅ | ✅ |
| semble_find_related | ✅ | ✅ | ✅ |
| scratchpad | ✅ | ✅ | ✅ |
| task | ✅ | ✅ | ✅ |
| git_read | ✅ | ✅ | ✅ |
| diff | ✅ | ✅ | ✅ |
| bash | ❌ | ✅(只读操作) | ✅ |
| edit | ❌ | ❌ | ✅ |
| write | ❌ | ❌ | ✅ |
| spawn_read_sub_agent | ✅ | ❌ | ❌ |
| spawn_write_sub_agent | ✅ | ❌ | ❌ |
| spawn_full_sub_agent | ❌ | ❌ | ❌ |

**设计说明:为什么 read 工具保留给主 session?**

主 session 保留了 `read`、`semble_search`、`semble_find_related` 等只读工具,而不是强制所有读取都通过 `spawn_read_sub_agent`。

原因是:有时 orchestrator 已经知道要读什么--任务描述里带着路径、或上一轮已经拿到了文件列表。这时如果还派 sub-agent 去读、原路返回、不压缩,是纯粹的 token 浪费(sub-agent 的 system prompt + context fork 开销全白付)。

**优化原则**:已知要读什么时,直接用 read / semble 读,把信息带入主干决策,不要为"走流程"而多绕一圈。

**write 的同理优化**

同样的逻辑适用于写:当 orchestrator 已经明确知道要写什么(内容、路径、格式都清晰),直接 `spawn_write_sub_agent`,跳过 read 阶段。不必先确认现状再写,该跳过就跳过。

这两个工具的存在是为了**分离关注点**,但分离不等于必须每次都走两步--orchestrator 的核心职责是判断哪些步骤可以省。

## 树形上下文结构
三包架构的核心承诺是:无论子任务执行了多少步,主干 session 始终保持精简。这在技术上如何实现?答案在于 session 存储层的数据结构--树形 context,而不是线性 context。这不是优化手段,是整个架构得以成立的前提。

### 线性 Context 的本质缺陷
传统 LLM session 的消息历史是一条链,所有内容必须追加在同一条线上。无论执行什么工具、读了多少文件、犯了多少错、重试了多少次--所有这些都进入 context,且无法选择性隐藏。

<img src="/images/mermaid/orchestrator-zh-3.svg" alt="Figure 3" style="max-width:100%;">
**Figure 3.1 - Linear context: all noise enters the main chain**

到第 N 步时,LLM 要在一个混杂着无效操作、错误信息、重试历史的 context 里工作。

### 树形结构:每条分支独立持有自己的视角
树形 context 让每个节点只能看到"从根到自己"这条路径上的消息。兄弟分支互不干扰,父节点看不到子节点的执行细节。

<img src="/images/mermaid/orchestrator-zh-4.svg" alt="Figure 4" style="max-width:100%;">
**Figure 3.2 - Tree context: trunk only sees lean tool_result, branches hold full execution**

### Fork 点:子 Agent 从哪里开始看世界
fork 点是主干当前的 leaf ID。`forkWorkspace()` 将新 SessionManager 的 leaf 指向这个位置。sub-agent 启动时继承主干到此为止的所有消息(规划 context),但之后的所有新消息只写入自己的分支。

sub-agent 知道"我们在解决什么问题"(因为继承了主干历史),但它的探索过程不会污染主干。

### 主干只追加结论,分支承载过程
sub-agent 执行了多少步,主干完全不知道--主干只在 fork 点之后新增一条 `tool_result`(精简结论)。这是协议层面的保证:pi 的工具协议决定了工具的返回值以 `tool_result` 形式进入调用方的 context,而不是把整个子 session 的历史贴进来。

### 为什么树形才能做到主干精简
**这是架构前提,不是优化手段。**

线性 context 从设计上就无法做到主干精简--所有内容必须追加在同一条链上,你没有办法把某段历史"留在别处"。树形结构通过给每个 sub-agent 一个独立的 session 实例(指向同一文件但有独立的 leaf 指针),让分叉和隔离在存储层面成为可能。

### 并行分支:同一 Fork 点派生多个 Agent
多个 sub-agent 可以从同一个 fork 点同时创建分支,并行执行:

<img src="/images/mermaid/orchestrator-zh-5.svg" alt="Figure 5" style="max-width:100%;">
**Figure 3.3 - Parallel branches from the same fork point, trunk collects lean results**

三个 SessionManager 实例指向同一 session 文件但各自持有独立的 leaf 指针,写入时互不干扰。

### /tree 导航:过程永久可回溯
因为 sub-agent 写入持久化文件而非 inMemory,session tree 里的每一条分支都永久保留。用 `/tree` 可以随时进入任意 sub-agent 的分支,查看它读了哪些文件、执行了哪些命令、推理过程是什么--完整的审计轨迹。

## 为什么省 Token
第 3 节解释了树形结构如何让主干精简在技术上得以成立。但"精简"本身是个抽象描述--节省的究竟是哪些 token,量级是多少?这一节从四个具体机制入手,把架构优势转化为可量化的 token 成本差异。这些机制并非同质:其中三个(机制一、三、四)是**结构性保证**,不依赖 LLM 的发挥;机制二是概率性收益。第 4.6 节会展开说明这一区别。

### 问题根源:Context 随执行线性膨胀
假设一个复杂任务需要 10 个子任务,每个子任务 50 次工具调用。不做任何隔离时,第 10 个子任务的 LLM 调用要携带前 9×50=450 条历史消息,context 随任务数线性增长。

<img src="/images/mermaid/orchestrator-zh-6.svg" alt="Figure 6" style="max-width:100%;">
**Figure 4.1 - Without orchestrator: context grows by 50 messages per task**

### 机制一:主干只见结论,细节留在分支
orchestrator 模式下,10 个子任务完成后,主干 context 只新增了 10 条 tool_result。子任务内部的 450 次工具调用全在分支里。

<img src="/images/mermaid/orchestrator-zh-7.svg" alt="Figure 7" style="max-width:100%;">
**Figure 4.2 - With orchestrator: trunk context grows by 1 message per task regardless of sub-agent steps**

主干 context 大小 = O(任务数),不是 O(总工具调用数)。这是**结构性保证**,不依赖 agent 行为。

### 机制二：Context 隔离，每个 Agent 专注自身
每个 sub-agent 只有自己领域相关的 context，不会看到其他子任务的执行历史。专注的 context 减少“在 500 条历史里找关键信息”的认知负担，降低幻觉概率，减少重试。这是四个机制里唯一的概率性收益，不是结构性保证——第 4.6 节会展开这一区别。

### 机制三:Semble 拦截盲读,按需取片段
一个 10000 行的文件,`read` 全文需要传入约 10000 行的 context。semble_search 针对查询返回最相关的 5 个 chunk,每个 chunk 约 30 行,传入 context 的只有 150 行--压缩比约 67:1。

<img src="/images/mermaid/orchestrator-zh-8.svg" alt="Figure 8" style="max-width:100%;">
**Figure 4.3 - Semble reduces file content in context by ~67x**

### 机制四:模型分层,复杂任务才用高价模型
`list files in src/` 用 haiku(~$0.25/M token),`refactor auth module` 用 opus(~$15/M token)--价差 60 倍。content-driven 模型路由让每次调用都用"刚好够用"的模型。

### 省的是结构性开销,不是靠 Agent 更聪明

四个机制里有一条分界线值得点清楚。

机制一、三、四是**结构性保证**——不管 LLM 发挥得好不好,省法都在发生:

- **机制一**:工具协议 + session tree 分支决定了主干只接收 tool_result,数学事实
- **机制三**:semble 拦截 hook 在 tool_call 层面强制介入,必然触发
- **机制四**:task tier 路由在每次 spawn 时执行,不依赖 LLM 判断

就算每个 sub-agent 都很低效、出了很多错,主干 context 依然保持精简。

机制二(Context 隔离)是**概率性收益**:专注的 context → 更少幻觉 → 更少重试 → 省 token。这条链的每一步都有概率性——LLM 也可能在专注的 context 里犯错,也可能第一次就做对。工业界说"多 agent 比单 agent 更省 token",指的就是这种二阶效应。

这套架构的保守主义:先靠结构性保证建立确定性的底线,机制二的智能优势是锦上添花,不是核心保障。

从实测数据来看,引入这套架构后 token 消耗降低约 **20%**。这个数字是四个机制综合效果扣除 sub-agent 自身开销后的净值,说明结构性节省远超 spawn 的固定成本。

### 等效压缩视角:每次 Spawn 都是一次归档
以上四个机制从不同角度切入,这里提供一个统一的整体视角,把它们收拢到同一幅图景里。

**每次 spawn 本质上是对那一阶段工作的一次压缩归档。** sub-agent 执行了 50 步工具调用,产生了大量中间状态--读了哪些文件、执行了哪些命令、推理了哪些假设。这些内容对主干而言被"压缩"成一条 tool_result:Findings、Risks、Next Steps。50 条消息 → 1 条消息,压缩比约 50:1。

但这里的"压缩"和传统有损压缩不同,准确的描述是**有损侧信道的无损压缩**:

- **主干看到的是有损摘要**(tool_result)--执行细节被省略,主干 context 保持精简
- **原始数据完整保留在分支**(branch)--通过 `/tree` 随时可以取回,不是真的丢失

<img src="/images/mermaid/orchestrator-zh-9.svg" alt="Figure 9" style="max-width:100%;">
**Figure 4.4 - Each spawn compresses N steps into 1 tool_result; original preserved in branch**

传统压缩是破坏性的--原始数据可能丢失。这里的"压缩"是归档性的--原始数据换了个地方存放,主干不再看到它,但它仍然完整存在。如果主干需要某个执行细节,可以通过 /tree 进入 branch 取回。

把这个视角与机制一、三、四对应:机制一是这个压缩归档的直接体现;机制三(semble 精准取片段)是 sub-agent 内部在做同样的事,只不过粒度更细--不读整个文件,只取相关 chunk;机制四(模型分层)是在决定每次归档操作用多贵的"处理器"。三个机制在同一个逻辑下统一:减少进入任何一层 context 的冗余信息。

### 实际效果

引入这套架构后,两个现象值得记录。

一是 **session 压缩几乎不再需要**。过去单 session 跑复杂任务,context 会膨胀到需要手动触发压缩(或被自动压缩截断);现在主干 context 始终保持在任务数量级,长任务跑完主干依然轻量,压缩这件事基本从日常操作里消失了。

二是 **token 消耗降低约 20%**。这个数字是四个机制综合效果扣除 sub-agent 自身开销(每次 spawn 有固定的系统提示成本)后的净值。净节省 20% 说明结构性节省远超 spawn 的开销成本。

## 基础层:pi-extension-session-subagent
整个架构的基础能力是"把一个 LLM session 变成可调用的工具"。没有这一层,orchestrator 就没有可以委派的执行单元。subagent 包做的正是这件事:把 spawn 操作封装成三个工具,让调用方可以像调用函数一样启动一个有完整推理能力的子 session。

### 三种 spawn 工具
subagent 包注册了三种工具,对应三种执行角色:
- **spawn_read_sub_agent**:只读 agent,只能观察和汇报,不能修改任何文件或状态
- **spawn_write_sub_agent**:读写 agent,可以执行文件修改、命令运行等操作
- **spawn_full_sub_agent**:完整工具集,orchestrator 包会将其从主 session 工具列表中移除

### 工具能力矩阵
三种 agent 的工具集来自 `tools-config.ts` 中的 `DEFAULT_TOOLS_CONFIG`:

```typescript
common:    ["read", "scratchpad", "task", "git_read", "diff",
            "semble_search", "semble_find_related"]
readExtra: ["bash"]        // bash 受 READ_AGENT_CONSTRAINTS 提示约束
writeExtra:["bash", "edit", "write"]
```

read agent = common + readExtra,write agent = common + writeExtra。

### 四种 context mode

- **fork(默认)**:知识传递模式。继承父 session 的消息历史,但经过蒸馏--只保留 user 消息和 assistant 的 text 块,剥除 thinking 块和所有 toolCall/tool_use 块。

  剥除行为信号是刻意的:assistant 的 thinking 和工具调用历史会通过 in-context learning 影响 sub-agent 的操作方式,使其模仿 orchestrator 的行为模式,即使系统提示已经把它定义为 read/write worker。过滤之后,sub-agent 知道"我们在解决什么问题"(来自 user 消息和 assistant 的分析文字),但不会被"父 session 怎么操作"所污染。

- **fresh**:空白 context,只有系统提示。适合真正自包含的任务,比如"检查这个目录下有没有文件"--不依赖父 session 的任何规划信息。

- **fork_full**:完整 session 克隆,原始历史原样传入,不做任何过滤。仅用于 sub-agent 与父 session 扮演同一角色的延续场景(极少见)。

- **auto**:无条件路由到 fork。如果需要 fresh,必须显式传 `mode="fresh"`--不要依赖 auto 来触发 fresh。

### Session 生命周期
sub-agent 是按需创建、执行完即释放的 session:

<img src="/images/mermaid/orchestrator-zh-10.svg" alt="Figure 10" style="max-width:100%;">
**Figure 5.1 - Sub-agent spawn, execute, and return sequence**

### 结构化输出格式
所有 sub-agent 的系统提示都注入了 `SUB_AGENT_OUTPUT_GUIDELINES`,要求最终回复包含五个固定 section:

```
## Conclusion   - 核心结论
## Findings     - 关键发现要点
## Risks        - 风险和注意事项
## Open Questions - 未解决的问题
## Next Steps   - 具体的后续行动建议
```

orchestrator 消费 tool_result 时按优先级处理:先看 Risks 和 Open Questions(有无阻塞项),再看 Findings(建立事实基础),最后看 Next Steps(决策建议)。

### Read-only 约束注入原理
read agent 的约束不依赖工具层面的沙箱,而是通过系统提示注入 `READ_AGENT_CONSTRAINTS`,明确列出允许和禁止的 bash 操作:
- 允许:ls、find、grep、git log、git diff 等只读操作
- 禁止:任何写文件(`>`、`>>`)、`sed -i`、`rm`、`npm install`、`git commit` 等修改操作

当 read agent 发现需要修改的内容时,应在 Next Steps 中描述,由 orchestrator 派发 write agent 执行。

## 调度层:pi-extension-session-orchestrator
有了 subagent 提供的执行单元,下一个问题是:谁来决定什么时候派哪种 agent,以及如何防止主 session 自己去执行任务。orchestrator 包在 subagent 之上加了一层调度策略,通过三个 hook 把主 session 强制塑造成一个只规划不执行的角色。

### 核心哲学:规划与执行分离
orchestrator 的主 session 只做三件事:**理解请求、分解任务、汇总结论**。它从不直接执行 bash 命令、读写文件或调用 API。所有执行都委托给 sub-agent。

这不是靠提示词约束的--bash/edit/write 工具在 `session_start` 时就从主 session 的工具列表中物理移除,想退化都做不到。

### 三个 Hook 的拦截点
orchestrator 通过三个 hook 实现全部能力,不注册任何工具:

<img src="/images/mermaid/orchestrator-zh-11.svg" alt="Figure 11" style="max-width:100%;">
**Figure 6.1 - Three hook interception points in orchestrator lifecycle**

### 工具集限制(session_start)
`session_start` hook 调用 `pi.setActiveTools()` 将主 session 的工具限制为:

```
common tools + mainExtra(spawn_read_sub_agent, spawn_write_sub_agent)
```

spawn_full_sub_agent 被完全排除在外。如果 subagent 包未安装(找不到 spawn 工具),hook 会向 UI 发送错误通知并提前退出。

### Orchestrator 提示注入(before_agent_start)
`before_agent_start` hook 在系统提示末尾追加 `[ORCHESTRATOR MODE]` 段落,明确告知 agent:
- 你是调度器,只规划和委派,不直接执行
- 有 read-only 直接访问(read、semble_search、semble_find_related)
- 没有 bash/edit/write 工具
- 工作流:理解 → task(plan) → spawn_read(仅当不确定时)→ spawn_write → 汇总

hook 同时重新断言工具集(防止其他 extension 的 hook 在中间恢复了被移除的工具)。

### Workspace 分支与主干精简(tool_call)
`tool_call` hook 拦截所有 spawn 调用,做两件事:
1. **模型选择**:分析 task 描述内容,选择匹配复杂度的模型
2. **workspace 注入**:调用 `forkWorkspace()` 创建分支 SessionManager,注入到 spawn 参数的 `_workspaceSessionManager` 字段

sub-agent 使用这个 workspace SM 而不是 inMemory SM,其所有消息写入 session tree 的分支,主干只追加一条 tool_result。

### forkWorkspace 实现原理
实现非常简洁,只用了两个 SessionManager API:

```typescript
const ws = SessionManager.open(sessionFile);  // 同一个文件,独立实例
ws.branch(trunkLeafId);                       // leaf 指向主干当前位置
// 下次 sub-agent 写入时,自动在此 fork 点创建新分支
```

<img src="/images/mermaid/orchestrator-zh-12.svg" alt="Figure 12" style="max-width:100%;">
**Figure 6.2 - Trunk only appends tool_result; branch holds full execution**

### 内容驱动模型分层(Task Tier)
模型选择根据 task 描述的内容复杂度,而不是 agent 类型(read/write)决定:

```typescript
COMPLEX_TASK = /architect|refactor|migrat|implement|debug|fix|rewrite/i  → high tier
SIMPLE_TASK  = /list|find|grep|count|search|summarize|check if/i          → low tier
其他                                                                        → medium tier
```

Tier 对应的实际模型来自 `~/.pi/agent/model-routing.json`,或自动按 cost + 名称模式(opus → high、sonnet → medium、haiku → low)检测。主 session 始终使用用户当前选中的模型,不做覆盖。

### /tree 可见性
因为 sub-agent 写入的是持久化的 session tree 分支(而不是 inMemory),用户可以在任务完成后通过 `/tree` 导航到任意 sub-agent 的分支,查看其完整的执行过程--每一次读文件、每一条 bash 命令、每一次推理都保留在那里。

## 搜索层:pi-extension-tool-semble
规划与执行分离解决了"由谁做"的问题,但还有一个隐性开销没有被覆盖:代码探索。无论是 orchestrator 还是 sub-agent,处理代码任务时都需要定位相关文件和函数。如果每次都靠全文件读取或穷举 grep,省下来的主干 context 开销会被代码读取开销抵消。semble 包解决的正是这个问题。

### semble_search / semble_find_related
两个工具封装了 `semble` CLI:
- **semble_search**:自然语言或符号名语义搜索,返回最相关的代码 chunk(默认 top 5)
- **semble_find_related**:给定 `file:line`,找项目中的相似实现,用于横向探索

两者比 grep/read 更 token 高效:semble 只返回相关片段,而不是整个文件。

### bash grep 自动重写
`tool_call` hook 拦截 bash 调用,用正则匹配以下两种模式并自动替换命令:

```bash
# 被拦截并重写为 semble search
grep -r "pattern" ./src
rg "pattern" ./src

# 重写后
semble search "pattern" "/abs/path/to/src"
```

复合命令(含 `|`、`&`、`;`)不做重写,避免破坏管道逻辑。

### 大文件盲读拦截
`tool_call` hook 同时拦截 `read` 工具调用。判断条件:
- 调用没有指定 `offset` 或 `limit`(说明是全文读取)
- 文件估计行数 > 300(`LINE_THRESHOLD`,可通过环境变量覆盖)
- 该文件所在目录未曾被 semble 搜索过(`SearchTracker.hasSearched()`)

满足条件则 `block: true`,返回提示:先用 semble 定位,确认是目标文件后再读片段。

### SearchTracker 状态管理
`SearchTracker` 记录每次 semble_search 搜索过的目录:

<img src="/images/mermaid/orchestrator-zh-13.svg" alt="Figure 13" style="max-width:100%;">
**Figure 7.1 - SearchTracker gates blind file reads within a turn**

每个 turn 开始时清空记录,确保跨 turn 不会误放行。

## 协作工作流
理解了各个组件的作用和省 token 的原理,接下来看它们如何在实际任务中协同运转。一个典型的 orchestrator 任务会经历:规划 → 侦察 → 实施 → 验证这几个阶段,每个阶段对应不同的工具调用组合。

### 标准执行序列
完整的 orchestrator 工作流:

<img src="/images/mermaid/orchestrator-zh-14.svg" alt="Figure 14" style="max-width:100%;">
**Figure 8.1 - Full orchestrator execution sequence**

### Skip-read 优化路径
如果 orchestrator 已经从之前的 context 中知道目标文件和修改内容,直接派发 write sub-agent,跳过 read 阶段:

```
❌ 浪费:spawn_read → 读到已知信息 → spawn_write
✅ 正确:直接 spawn_write(已知信息就在主干 context 里)
```

orchestrator 系统提示明确强调:"skip the read phase when you already have enough context"。

### Context 流向
- **用户消息** → 进入主干,orchestrator 可见
- **主干工具调用**(spawn)→ fork 点创建,记录在主干历史
- **sub-agent 执行**(50 步)→ 全部写入分支,主干不可见
- **tool_result** → 写入主干,orchestrator 可见(精简结论)
- **scratchpad** → 存在 tool_result details 里,branch 切换后自动重建,跨 spawn 持久

### 工具配置定制
`DEFAULT_TOOLS_CONFIG` 定义默认工具白名单,支持两级覆盖:
1. `~/.pi/agent/tools.json`:全局用户配置
2. `<cwd>/.pi/agent/tools.json`:项目级配置(优先级更高)

后者覆盖前者,都覆盖默认值。可以给特定项目添加额外工具(如 `db_query`)而不影响其他项目。

## 模式辨析
描述完架构细节,最后回到一个定性问题:这套架构到底是什么模式?它和几个相似的概念--路由模式、多 agent 系统--有什么本质区别?厘清边界有助于在更广的技术语境里定位它,也有助于判断哪些场景适合用它、哪些场景不需要它。

### 与路由模式(Router Pattern)的区别
路由模式的核心是**单次分发**:收到请求 → 判断类型 → 转发给对应处理器 → 返回结果。路由器本身无状态,不持有任务上下文,不汇总结论,不决策下一步。

orchestrator 有本质不同:
- **有全局状态**:scratchpad 和 task 在多轮 spawn 之间持久化
- **多轮迭代**:根据每轮结果决定下一步,不是一次性分发
- **主动汇总**:把多个 sub-agent 的结论综合成最终答案
- **决策循环**:spawn_read → 评估 → spawn_write → 验证 → 汇总

<img src="/images/mermaid/orchestrator-zh-15.svg" alt="Figure 15" style="max-width:100%;">
**Figure 9.1 - Router (stateless dispatch) vs Orchestrator (stateful multi-round delegation)**

### 与真正多 Agent 系统(MAS)的区别
经典 MAS(Multi-Agent System)的核心要素:

| 要素 | 经典 MAS | 本架构 |
|:---|:---|:---|
| 自主性 | agent 自主决定何时行动 | sub-agent 被动等待 spawn |
| 对等通信 | agent 间可直接发消息 | 严格单向:orchestrator ↔ sub-agent |
| 持久存在 | agent 持续运行,有自己的目标 | sub-agent 执行完即销毁 |
| 共享环境感知 | 多 agent 主动感知同一环境变化 | sub-agent 只感知 orchestrator 注入的任务描述 |

sub-agent 本质上是**会推理的函数调用**--接受输入(task 描述 + fork context),产生输出(结论),然后消失。它没有自己的目标函数,没有对环境的主动感知,没有与兄弟 agent 通信的能力。

真正的 MAS 需要:持久化 agent loop、共享可读写环境(黑板/消息总线)、agent 基于感知自主决策行动。工业界把这种 orchestrator 架构称为 "multi-agent" 是宽泛用法。

### 这套架构的本质定位
准确的定位是三层复合:

<img src="/images/mermaid/orchestrator-zh-16.svg" alt="Figure 16" style="max-width:100%;">
**Figure 9.2 - Orchestrator architecture as composition of three patterns**

- **Hierarchical Agent**:单一决策中心(orchestrator),执行工具碰巧也是 LLM
- **CQRS**(Command Query Responsibility Segregation):read/write 强制分离,通过物理移除工具而不是靠约定
- **树形 Context 管理**:分支隔离 + 主干精简 + 永久审计轨迹

它不是 MAS,不是 Router,是一个通过能力边界强制来防止职责退化的层次委派模式。

## 适用边界:节省的是不确定性的代价

走完整个架构的细节,可以回头看一个更本质的问题:这套机制到底在省什么?答案是--**探索过程的不确定性产生的 context**。

主 session 没有 bash 权限,根本原因不是不信任它,而是因为探索过程天然不确定--要读多少文件?会遇到几次错误?需要重试几轮?这些无法提前预知的步骤一旦发生在主干,就全部沉积在 context 里。把探索外包给 sub-agent,实际上是把这种不确定性"装进容器":

- **read sub-agent** 是探索读的容器。乱读、误读、重试,都在分支里。主干只看到结论
- **write sub-agent** 是探索写的容器。试改、测试失败、再改,过程全在分支。主干不知道

**这意味着 sub-agent 的收益正比于任务的不确定性。**不确定性越高--不知道从哪里下手、文件结构复杂、需要大量探索--spawn 的收益越显著。

**反过来,已经确定的事情不必 spawn。**如果 orchestrator 已经知道要读哪个文件、要改什么内容,直接用 `read` 或 `edit/write` 工具完成反而更高效--每次 spawn 有固定的系统提示成本和 context fork 开销,为"确定的事"支付这个成本是纯浪费。

极端情况:如果整个任务从头到尾都是确定的(路径已知、改动内容明确),主 session 直接处理完全合理。这不是架构的"退化",而是正确识别了 spawn 的适用边界--它解决的始终是**不确定性产生的 context 膏胀**,确定性任务本来就不是它的目标对象。

```
不确定性高 → spawn sub-agent(把探索隔离在分支里)
不确定性低 → 主 session 直接做(省去 spawn 的固定成本)
```

这也解释了一个表面上奇怪的设计:为什么同样是"读文件",有时候直接 `read`,有时候要 `spawn_read_sub_agent`?区别不在操作类型,而在于**不知道要读什么**时才开始往 sub-agent 里装。知道了,直接读就行。


