# MiMo Code 源码分析：在 OpenCode 的骨架上，小米焊了一整套「别让 Agent 骗自己」的装置

> **分析对象**：MiMo Code（[`XiaomiMiMo/MiMo-Code`](https://github.com/XiaomiMiMo/MiMo-Code)，产品名 **MiMoCode**），基于 **commit `076b790`**（`076b790d9f192adbc92054c4d4437c13cb6993a8`，2026-07-27 14:06 UTC，`main`，merge PR #1938 `checkpoint-writer-wait-timeout`）。
> **代码规模**：仓库 **5 222 个文件 / 83.8 MB**。TypeScript `.ts` **1 511 个（11.0 MB）**、`.tsx` **400 个（3.3 MB）**、MDX 630 个（6.7 MB）、Markdown 326 个。17 个 workspace 包，核心 `packages/opencode` 占 **1 677 个文件 / 17.9 MB**。
> **社区体量**：**12.5k star / 1.27k fork / 838 open issue**，仓库 2026-06-10 创建——**一个半月**。
> **分发**：npm 包 `@mimo-ai/cli` **v0.1.9**，二进制命令 `mimo`（`packages/opencode/package.json:3-4,24-26`）。
> **许可**：**MIT**，但**另有一份 `USE_RESTRICTIONS.md`** —— 禁止军事用途、恶意网络活动、未经授权的数据采集，以及「**在缺乏适当人类监督或授权的情况下自主执行高风险动作**」。用前两份都要读。
> **一句话定位**：小米 MiMo 团队开源的终端编程 Agent，口号 **"Where Models and Agents Co-Evolve"**。它**不是从零写的**——核心包就叫 `packages/opencode`，根 `package.json` 的 `name` 至今仍是 `"opencode"`，`LICENSE` 同时写着 **Copyright 2026 MiMo Code, Xiaomi** 与 **Copyright 2025 opencode**。**它是 OpenCode 的深度 fork。**真正值得读的不是它继承了什么，而是小米在上游之上**焊了什么**。
> **读者对象**：读过本系列 [opencode 源码分析](./opencode-源码分析.md) 的收益最大（能直接看出增量）；没读过也不影响。全文标注 `文件:行号`，相对 `参考项目/MiMo-Code/`，可在该 commit 下核对；**不确定处标明，不编造行号**。

---

## 目录

**第一部分 · 它是什么**
1. [项目概览：一个 fork 的价值在于它焊了什么](#ch1)
2. [全景架构：Effect 服务层 + 单进程 TUI](#ch2)
3. [与上游 OpenCode 的差分：一张增量清单](#ch3)

**第二部分 · 主循环与「别让 Agent 骗自己」**
4. [心脏地带：Goal 停止条件与独立裁判模型](#ch4)
5. [四道死循环闸门：空步 / 重复步 / 文本环 / 步数上限](#ch5)
6. [Agent 模式与子 Agent：13 个内置 agent 的三种身份](#ch6)
7. [Try-Best 检测器与产物级验证](#ch7)

**第三部分 · 上下文工程**
8. [一模型一提示词：20 份 prompt 与路由规则](#ch8)
9. [Checkpoint：把上下文当成可重建的快照](#ch9)
10. [记忆：SQLite FTS5 + BM25 相对地板](#ch10)
11. [压缩点可调：`/context-limit` 与成本档位](#ch11)

**第四部分 · 工具与权限**
12. [GPT 微内核：给 Codex 一套更小的工具 ABI](#ch12)
13. [`exec` 与 QuickJS：组合工具但不放大权限](#ch13)
14. [权限系统：FORCED_ASK 与通配符压不住的那条线](#ch14)

**第五部分 · 编排与生态**
15. [Workflow：确定性 JS 脚本编排多 Agent](#ch15)
16. [Orchestrator 模式：一个窗口管所有任务](#ch16)
17. [Dream / Distill / Evolve：让 Agent 改自己](#ch17)
18. [Token Efficient 模式：把 bash 输出的噪音洗掉](#ch18)
19. [总结：三个最独特的设计与三处取舍](#ch19)

**第六部分 · 与上游逐层差分**
20. [逐层差分：MiMo 到底改了 OpenCode 什么，以及为什么](#ch20)

---

<h2 id="ch1">第 1 章 项目概览：一个 fork 的价值在于它焊了什么</h2>

### 1.1 先把身世说清楚

打开仓库第一眼就该注意到的事：唯一的核心包叫 **`packages/opencode`**，而根 `package.json` 的 `name` 字段至今写着 `"opencode"`。

这不是命名随意。MiMo Code 是 **[OpenCode](./opencode-源码分析.md) 的深度 fork**——本系列第二份分析的对象（Anomaly 团队，TypeScript on Bun + Effect 框架，"服务器即本体"）。上游的 Effect 服务层、SolidJS TUI、SQLite 持久化、provider 抽象，MiMo 全盘继承。`LICENSE` 里两行版权并列，把这件事写得明明白白。

这就带来一个阅读策略上的分岔：

| 读法 | 结果 |
|---|---|
| ❌ 当成全新项目从头读 | 你会花 80% 的时间重读已经分析过的 OpenCode 骨架 |
| ✅ **读差分** | 直接看小米焊上去的东西——那才是这份仓库的信息量 |

本文走第二条路。

> 🧠 **本系列里的先例**：`Raven` 的 Python 运行时 fork 自 `nanobot`，我们当时的读法就是"对照上游看后加改造"。MiMo × OpenCode 是同一个模式，只是**改造幅度大得多**——大到值得单独一份 18 章的分析。

### 1.2 口号背后的产品主张

README 第一行：**"MiMo Code: Where Models and Agents Co-Evolve"**（模型与 Agent 共进化）。

这句话不是营销辞令，它对应两件具体的工程事实：

1. **一模型一提示词**（[第 8 章](#ch8)）：`session/prompt/` 下有 **20 份** system prompt，每个模型家族一份，按模型 ID 路由。
2. **一模型一工具 ABI**（[第 12 章](#ch12)）：GPT/Codex 家族看到的工具集和 Claude 家族**不一样**——前者只有 `bash` / `apply_patch` / `view_image` / `exec` 四件，后者是完整的 read/write/edit/grep/glob 套装。

**"共进化"的真实含义是：Agent 的 harness 会为每个模型单独调形，而不是给所有模型一套通用外壳。**

> ⚠️ **一处必须先澄清的归属问题**：**这两条的"雏形"都来自上游 OpenCode，不是小米首创。**上游已有 10 份分家族提示词和同样的 `if` 链路由；GPT 工具门控的判定表达式在上游是**一字不差**的同一行。小米做的是**沿着这条路走得更远**——加 5 个家族、重写最常用的 3 份、把门控从"二选一交换"扩成"整套工具面替换"。[第 20 章](#ch20)有基于**真实文件级 diff** 的逐项归属核对，本文凡涉及"谁的创新"一律以那一章为准。

### 1.3 数字化的项目形状

```
MiMo-Code/  (commit 076b790 · MIT + USE_RESTRICTIONS · 5 222 files · 83.8 MB · 12.5k★)
├── packages/            17 个 workspace 包
│   ├── opencode/        1 677 文件 / 17.9 MB   ← 核心：CLI + Session 引擎 + 工具 + TUI
│   │   └── src/
│   │       ├── cli/         230 文件 / 3.0 MB  ← TUI（SolidJS/OpenTUI）+ 各子命令，10 语言 i18n
│   │       ├── skill/       414 文件 / 2.3 MB  ← 26 个内置技能包（含脚本/模板/数据集）
│   │       ├── session/      68 文件 / 881 KB  ← 主循环、checkpoint、压缩、goal
│   │       ├── tool/         84 文件 / 525 KB  ← 51 个工具 TS 文件
│   │       ├── provider/     34 文件 / 306 KB
│   │       ├── workflow/     14 文件 / 217 KB  ← 确定性 JS 编排
│   │       ├── server/       43 文件 / 210 KB
│   │       ├── plugin/ config/ actor/ lsp/ agent/ acp/ mcp/ cron/ permission/ inbox/ task/ …
│   ├── ui/              1 558 文件            ← 组件库
│   ├── web/               717 文件 / 12.1 MB  ← 官网 + 文档（Astro/MDX）
│   ├── console/           481 文件 / 32.9 MB
│   ├── desktop/           217 文件 / 8.3 MB
│   ├── app/ sdk/ enterprise/ shared/ storybook/ plugin/ slack/ identity/ containers/ …
├── docs/
│   ├── architecture/    codex-microkernel-runtime.md（含 en/fr/ja/ru 共 5 语言）
│   ├── harness/         Orchestrator Mode · Token Efficient Mode
│   │                    · Agent Multi-Skill Workflow Orchestration · Mix of Harness and Hand-off
│   └── compose/         spec/ plans/ reports/ —— 自举的 spec 驱动开发记录
├── .mimocode/           项目自身的配置样例（skills/effect、themes、plugins）
├── sdks/ script/ infra/ nix/ patches/
```

**最大的几个源文件**（`packages/opencode/src/` 下，排除测试）：

| 字节 | 文件 | 是什么 |
|---|---|---|
| **207 962** | `session/prompt.ts` | **主循环**（4 595 行）——Agent 心脏 |
| 128 317 | `cli/cmd/tui/routes/session/index.tsx` | TUI 会话主视图 |
| **84 552** | `workflow/runtime.ts` | 确定性工作流运行时（1 607 行） |
| **74 327** | `session/checkpoint.ts` | Checkpoint 系统（1 648 行） |
| 73 346 | `cli/cmd/tui/component/prompt/index.tsx` | 输入框组件 |
| 71 103 | `provider/transform.ts` | provider 请求变换（1 815 行） |
| 70 654 | `provider/provider.ts` | provider 注册与解析 |
| 58 171 | `acp/agent.ts` | ACP 协议实现 |
| 53 710 | `tool/session.ts` | Orchestrator 的 `session` 工具 |
| 48 853 | `actor/spawn.ts` | 子 Agent 派发（1 010 行） |

> ⚠️ **注意一个反差**：`session/prompt.ts` 单文件 208 KB，比 Open Design 整个提示词组装器（125 KB）还大——但它不只是提示词，而是**主循环 + 提示词组装 + 各种闸门**揉在一起。这是本仓库最大的可维护性隐患（[第 19 章](#ch19)展开）。

### 1.4 许可的两层

`LICENSE` 是 MIT，但仓库根目录还有一份 **`USE_RESTRICTIONS.md`**。它列出的禁止用途里，有一条和本文主题直接相关：

> To use Xiaomi MiMoCode in a manner that **autonomously executes high-risk actions without appropriate human oversight or authorization**.

**一个把"防止 Agent 自欺"做成核心设计的项目，在许可条款里也写下了"不许无监督地自主执行高风险动作"**——这两件事是一致的。

---

<h2 id="ch2">第 2 章 全景架构：Effect 服务层 + 单进程 TUI</h2>

### 2.1 继承自 OpenCode 的骨架

```mermaid
flowchart TB
    subgraph UI["① 终端 UI（SolidJS + OpenTUI）"]
        TUI["会话视图 · 输入框 · 任务面板 · 权限弹窗<br/>10 种语言 i18n"]
    end
    subgraph CORE["② Session 引擎（同进程，Effect 服务层）"]
        PROMPT["SessionPrompt 主循环<br/>prompt.ts 4595 行"]
        SYS["SystemPrompt 路由<br/>按模型 ID 选 20 份之一"]
        CKPT["Checkpoint 系统"]
        GOAL["Goal 独立裁判"]
        PERM["Permission 规则集"]
        REG["ToolRegistry<br/>按模型装配工具 ABI"]
    end
    subgraph EXT["③ 扩展面"]
        SKILL["26 个内置技能"]
        WF["Workflow（QuickJS 沙箱）"]
        MCP["MCP 客户端"]
        PLUG["插件 / hooks"]
    end
    subgraph OUT["④ 外部世界"]
        MODEL["任意 provider 模型<br/>MiMo Auto / Codex OAuth / API key"]
        FS["文件系统 · Shell · Git"]
        DB[("SQLite<br/>会话 / 记忆 FTS5 / 任务 / 权限")]
    end

    TUI --> PROMPT
    PROMPT --> SYS
    PROMPT --> GOAL
    PROMPT --> CKPT
    PROMPT --> REG --> PERM
    REG --> SKILL & WF & MCP & PLUG
    PROMPT -->|流式| MODEL
    PERM -->|裁决后| FS
    CKPT --> DB
    SKILL --> DB
```

**继承自上游**：Effect 框架的 `Context.Service` / `Layer` 依赖注入、SQLite + Drizzle 持久化、provider 抽象、TUI 技术栈、server 模式（`mimo serve`）、ACP 协议。

**小米新增**（下一章逐条列）：Goal 裁判、Checkpoint、FTS5 记忆、20 份分家族提示词、GPT 微内核、Workflow 运行时、Orchestrator 模式、Dream/Distill、Token Efficient 管线、cron 调度、inbox。

### 2.2 Effect 服务层是怎么组织的

每个子系统都是一个 `Context.Service` + `Layer`，靠依赖注入拼起来。以 Goal 为例（`session/goal.ts:100-230`）：

```ts
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionGoal") {}

export const layer = Layer.effect(Service, Effect.gen(function* () {
  const provider = yield* Provider.Service
  const auth     = yield* Auth.Service
  const config   = yield* Config.Service
  const bus      = yield* Bus.Service
  // …构造实现
  return Service.of({ set, get, clear, bumpReact, evaluate })
}))

export const defaultLayer = layer.pipe(
  Layer.provide(Provider.defaultLayer),
  Layer.provide(Auth.defaultLayer),
  Layer.provide(Config.defaultLayer),
  Layer.provide(Bus.layer),
)
```

好处在测试和替换上很明显（换一个 `Provider.layer` 就能把整棵依赖树换掉），代价是**阅读门槛陡**——`yield*` 满屏，不熟悉 Effect 的人第一次读会很痛苦。这是上游 OpenCode 的选择，MiMo 全盘继承。

> 📌 **状态存活期**：Goal 的状态放在 `InstanceState`（每个项目实例一份），按 `sessionID` 索引，实例销毁时清空（`goal.ts:23-24` 注释）。这是"会话级易失状态"的标准位置，和落库的 checkpoint 形成对照。

---

<h2 id="ch3">第 3 章 与上游 OpenCode 的差分：一张增量清单</h2>

这一章是全文的地图。**读 MiMo Code = 读这张表。**

| # | 小米新增 | 一句话 | 章节 |
|---|---|---|---|
| 1 | **Goal 停止条件 + 独立裁判** | Agent 想停时，另一个模型读 transcript 判它是不是真做完了 | [第 4 章](#ch4) |
| 2 | **四道死循环闸门** | 空参数工具调用 / 重复动作签名 / 文本复读 / 步数上限，各有软硬两级 | [第 5 章](#ch5) |
| 3 | **Try-Best 检测器 + 产物验证** | 抓"我尽力了"式假性完成；checkpoint 写完要验、不合格要重写 | [第 7 章](#ch7) |
| 4 | **提示词家族 8 → 20 份** | 路由机制**继承自上游**；小米加 5 个家族、**重写最常用的 3 份**（gpt 2.7x / default 2.4x / anthropic 1.7x）、加双 ID 兜底 | [第 8 章](#ch8) · [第 20.3 节](#ch20) |
| 5 | **Checkpoint 系统** | 上下文快到顶时，从快照 + 记忆 + 任务进度**重建**，而不是简单压缩 | [第 9 章](#ch9) |
| 6 | **FTS5 记忆 + BM25 相对地板** | SQLite 全文检索，按相对分数过滤常见词噪音 | [第 10 章](#ch10) |
| 7 | **可调压缩点** | `/context-limit` 让模型比自己的窗口更早压缩（省钱/提质） | [第 11 章](#ch11) |
| 8 | **GPT 工具门控：二选一 → 整套替换** | 判定表达式**与上游一字不差**；小米把它抽成 `usesGPTToolset()` 并把作用域从"apply_patch ↔ edit/write 二选一"扩成隐藏 read/grep/glob/multiedit/notebook_edit 的**整套 ABI 替换** | [第 12 章](#ch12) · [第 20.4 节](#ch20) |
| 9 | **沙箱：acorn 手写解释器 → QuickJS** | 上游 `code-mode` 是 **3 465 行手写 AST 解释器**、只暴露 MCP 工具；小米换成 **QuickJS WASM**，并改为经 late-bound registry 暴露**宿主工具** | [第 13 章](#ch13) · [第 20.5 节](#ch20) |
| 10 | **FORCED_ASK** | 通配符 allow 压不住删除类操作 | [第 14 章](#ch14) |
| 11 | **Workflow 运行时** | 确定性 JS 脚本编排多 Agent，4 个内置流水线 | [第 15 章](#ch15) |
| 12 | **Orchestrator 模式** | 一个窗口一个会话管所有任务，子会话跑在独立 worktree | [第 16 章](#ch16) |
| 13 | **Dream / Distill / Evolve** | 沉淀知识、蒸馏技能、改自己 | [第 17 章](#ch17) |
| 14 | **Token Efficient 管线** | 洗 bash 输出的 ANSI/进度条/密钥/超长行 | [第 18 章](#ch18) |
| 15 | 26 个内置技能 · cron 调度 · inbox · 语音输入 · 任务树 | 生态面 | 散见 |

**共同的主题**：这 15 条里有 **6 条**（1、2、3、5、10、13）都在解决同一类问题——**Agent 会骗自己**。它会说"做完了"其实没做完，会重复同一个动作以为在推进，会在参数为空时反复调同一个工具，会在删文件时把通配符授权当成许可。

> 🧠 **一句话**：如果说 opencode 的主题是"协议即产品"、hermes 是"闭环学习"、CodeWhale 是"安全即机制"，那 **MiMo Code 的主题就是「不信任模型的自我报告」**。

---

<h2 id="ch4">第 4 章 心脏地带：Goal 停止条件与独立裁判模型</h2>

### 4.1 它解决的问题

长任务里最常见的失败不是崩溃，是**乐观停止**（optimistic stop）：模型干到一半觉得差不多了，输出一段"我已经完成了 XXX"然后收工。用户回来一看，测试没跑、边界没处理、文件根本没写。

`/goal` 命令给会话设一个**停止条件**。之后（`session/goal.ts:17-21` 文件头注释）：

> once a goal is set, the main runLoop **refuses to stop** until an independent judge model decides the condition is satisfied (or genuinely impossible). The judge is a separate model call that **only reads the transcript** — it does not do the work, **so its verdict stays cold relative to the working agent's optimism**.

最后半句是设计精髓：**裁判不干活，所以它的判断相对于干活者的乐观是"冷"的。**

### 4.2 裁判的提示词

`goal.ts:64-73` 的 `JUDGE_SYSTEM`，尤其是对 `impossible` 的约束：

```
Your response must be a JSON object with one of these shapes:
- {"ok": true,  "reason": "<quote evidence from the transcript that satisfies the condition>"}
- {"ok": false, "reason": "<quote what is missing or what blocks the condition>"}
- {"ok": false, "impossible": true, "reason": "<explain why the condition can never be satisfied>"}

Always include a "reason" field, quoting specific text from the transcript whenever possible.
If the transcript does not contain clear evidence that the condition is satisfied,
return {"ok": false, "reason": "insufficient evidence in transcript"}.

Only use {"ok": false, "impossible": true} when the condition is genuinely unachievable…
Apply your own judgment when deciding this — the assistant claiming the goal is impossible
is **evidence, not proof**; independently confirm the condition is genuinely unachievable
rather than deferring to the assistant's self-assessment.
```

> ⚖️ **「the assistant claiming the goal is impossible is evidence, not proof」**——这一句把裁判和被裁判者的关系钉死了。没有它，模型只要说一句"这做不到"就能让裁判放行，整个机制就废了。

三个配套细节：
- **`temperature: 0`**（`goal.ts:188`）——裁判必须可复现。
- **裁判看的是原生 model messages，不是渲染后的文本**（`goal.ts:159-161`）：「converted to native model messages (tool calls/results/images preserved) so the judge **independently confirms the work** rather than trusting the assistant's self-report」。它看得到真实的工具调用和返回值，而不是助手复述的版本。
- **无证据即不通过**：transcript 里没有清晰证据 → `{"ok": false, "reason": "insufficient evidence"}`。**默认不放行。**

### 4.3 主循环里的 goalGate

```mermaid
flowchart TB
    STOP["Agent 想停止"] --> CHK{"有活跃 goal？<br/>且是 main agent？"}
    CHK -->|否| ALLOW["允许停止"]
    CHK -->|是| TRANS["取 transcript<br/>（过滤已压缩部分）"]
    TRANS --> JUDGE["独立裁判模型<br/>temperature=0"]
    JUDGE -->|判定出错| FAILOPEN["⚠️ fail-open：允许停止<br/>『flaky judge 不能困住用户』"]
    JUDGE --> V{"verdict"}
    V -->|ok=true| DONE["✓ 目标达成 → 清 goal → 允许停止"]
    V -->|impossible=true| IMP["⊘ 确认不可能 → 清 goal → 允许停止"]
    V -->|ok=false| CNT{"react 次数 > MAX_GOAL_REACT(12)？"}
    CNT -->|是| CAP["⚠️ 触顶保险丝 → 清 goal → 允许停止"]
    CNT -->|否| REENTRY["把裁判的 reason 作为<br/>合成 user 消息注入 → 继续干"]
    REENTRY --> STOP
```

实现在 `session/prompt.ts:2505-2600`。**四个逃生口，一个都不能少**：

| 逃生口 | 位置 | 为什么必须有 |
|---|---|---|
| **fail-open** | `prompt.ts:2525-2533` | 「fail-open on any judge error so **a flaky judge can never trap the user**」——裁判自己挂了不能把用户锁死 |
| **`impossible`** | `prompt.ts:2534` | 条件本身不可能达成时要能退出 |
| **`MAX_GOAL_REACT = 12`** | `prompt.ts:169-175` · `:2558` | 「the safety valve against a **never-satisfiable condition burning tokens forever**」 |
| **只对 main agent 生效** | `prompt.ts:2507` | 子 agent 有自己的 `MAX_PRE_REACT (=3)`，不叠加 |

`MAX_GOAL_REACT` 的注释还解释了为什么是 12 而不是 3：「Higher than spawned actors' `MAX_PRE_REACT (=3)` because **main-session goals are usually larger**」——主会话的目标通常更大，需要更多轮次。并且诚实标注了 `TODO: lift to mimocode.json config (e.g. session.maxGoalReact)`。

### 4.4 裁判结论怎么回到 UI

每次判定都发一个 `session.goal` 事件（`goal.ts:46-60`），带上 `goal`（undefined 表示已清除）和 `lastVerdict`，后者含 `attempt`（第几次）、**`messageID`**、`error`（裁判是否失败）。

`messageID` 那个字段的注释写着：「The assistant message the judge evaluated — **anchors the verdict to a turn**」。TUI 因此能在具体那一轮旁边画标记，用户可以追溯"它是看着哪一段做的判断"。

> 🧠 **一句话**：Goal 裁判是本系列里第一个把「**验收**」和「**施工**」用两次独立模型调用彻底分开的设计。Open Design 的五陪审员评审是同一会话的五个回合（共享上下文以保持一致），MiMo 反过来——**故意不共享，因为要的就是"冷"**。

---

<h2 id="ch5">第 5 章 四道死循环闸门：空步 / 重复步 / 文本环 / 步数上限</h2>

Goal 管的是"该不该停"，这一章管的是"**别原地转圈**"。四道闸门各抓一种病，而且**每道都是软→硬两级**。

### 5.1 闸门一：空参数工具调用（`session/prompt/empty-step-detection.ts`）

**病症**（文件头注释）：

> some models (**including frontier ones under certain workloads**) occasionally emit a tool call with a completely empty argument object — i.e. they "called a tool" but passed nothing actionable. Re-looping just repeats the same empty call.

**判定**（`isEmptyStep`，`:44-67`）——四条缺一不可：

```
✓ 有至少一个 client（非 providerExecuted）工具调用
✓ 所有 client 工具调用的 input 都是空的
✗ 没有实质文本（排除 synthetic / ignored）
✗ 没有实质 reasoning
```

"空 input"的定义也很细（`isEmptyInput` / `isEmptyValue`，`:73-90`）：没有键、或所有值都是 `null` / `undefined` / 空串 / 纯空白 / 空数组 / 空对象。**数字和布尔值算真值**——模型传了 `0` 或 `false` 是有意图的。

**最值得学的是它明确划出的边界**（文件头 `IMPORTANT scope note`）：

> this guard does **NOT** try to catch "empty terminals" (steps that emit no tool call and no text). An empty terminal is a **natural turn end**, not a spin… Treating it as a loop caused **frequent false positives** on legitimate quiet steps (task done, sub-agent returned, reasoning-only steps, provider-executed tool calls). Wall-clock / active deadlines and provider stream timeouts already backstop any actual "model produces nothing" pathology.

翻译：**"模型什么都没说"不是死循环，那是回合正常结束。**早期版本把它当循环抓，误报到不可用。真正的"模型什么都不产出"由超时兜底。

**软→硬两级**（`:92-104`）：

| 级别 | 注入的 system-reminder |
|---|---|
| 软（第 1 次） | 「Your previous tool call had empty or missing arguments… Retry the call with **COMPLETE arguments**, or if the tool is not the right next step, **answer the user in plain text**.」 |
| 硬（第 2 次） | 「Second empty tool call. **Final chance before this turn is halted.**… Any further empty-argument tool call will **terminate this turn**.」 |

### 5.2 闸门二：重复动作签名（`session/prompt.ts:176-215`）

**阈值**：`REPEATED_STEP_THRESHOLD = 3`——「Three in a row is a strong signal the model is stuck repeating itself rather than making progress」。

**关键在签名怎么算**。`stepSignature`（`prompt.ts:198-215`）只取**工具调用**，故意排除文本和 reasoning：

> Text and reasoning are excluded on purpose: in a ReAct loop the model **narrates each step in slightly different words while taking the exact same action**, and some models emit their reasoning as plain text parts — counting either would **mask the repeated action** we want to catch.

而 `stableStringify`（`prompt.ts:177-196`）解决另一个隐蔽问题：

> `JSON.stringify` preserves insertion order, and models **routinely re-emit the same arguments with keys in a different order** (e.g. `{url,format}` vs `{format,url}`) — without this the signatures would differ and the repeated-step check would **miss real loops**.

所以它递归地按**排序后的键**序列化。

> 🔑 **这两个细节是同一个洞察的两面**：判断"是不是同一个动作"时，**语义相同但表示不同**的东西必须归一化（键序），**语义不同但表示相似**的东西必须排除（叙述文本）。搞反任何一个，检测器就废了。

### 5.3 闸门三：文本复读环（`session/prompt/text-loop-recovery.ts`）

```ts
export const TEXT_LOOP_BUFFER_SIZE   = 5
export const TEXT_LOOP_TRIGGER_COUNT = 3
export const TEXT_LOOP_MAX_RECOVERY  = 2

export function normalizeForLoopDetection(text: string): string {
  return text.trim().toLowerCase()
    .replace(/\s+/g, " ")
    .replace(/^(let me |i'll |i will |let's )/i, "")   // ← 剥掉开场白
    .slice(0, 200)
}
```

那条 `replace` 很妙：模型复读时开头常常在 "Let me…" / "I'll…" / "I will…" / "Let's…" 之间随机切换，**归一化时把这些开场白剥掉**，才能看出后面是同一句话。

两级恢复提示词（`:24-46`）：软的说"换个办法，别重复"；硬的说「If you repeat the same output again, **the session will be terminated**」。

另有一个 `text-ngram-detection.ts`（3 785 字节）做 n-gram 层面的重复检测，和上面的整句比对互补。

### 5.4 闸门四：步数上限（`session/prompt/max-steps.txt`）

到达步数上限时，注入一段**工具全禁**的提示词：

```
CRITICAL - MAXIMUM STEPS REACHED
Tools are disabled until next user input. Respond with text only.

STRICT REQUIREMENTS:
1. Do NOT make any tool calls …
2. MUST provide a text response summarizing work done so far
3. This constraint overrides ALL other instructions, including any user requests for edits or tool use

Response must include:
- Statement that maximum steps for this agent have been reached
- Summary of what has been accomplished so far
- List of any remaining tasks that were not completed
- Recommendations for what should be done next
```

注意第 3 条：**「overrides ALL other instructions, including any user requests」**——用户在上下文里说过"必须改完这个文件"也压不住它。这是把"优先级"写进提示词的标准做法。

而且它要求的不只是"停"，是**交代清楚**：做了什么、剩什么、下一步建议什么。**让被截断的回合仍然有交付物。**

### 5.5 四道闸门的关系

```mermaid
flowchart LR
    subgraph L1["步级（每一步都查）"]
        A["空参数工具调用<br/>软→硬 2 级"]
        B["重复动作签名<br/>连续 3 次"]
        C["文本复读 + n-gram<br/>缓冲 5 / 触发 3"]
    end
    subgraph L2["回合级"]
        D["步数上限<br/>禁工具 + 强制交代"]
    end
    subgraph L3["会话级"]
        E["Goal 独立裁判<br/>MAX_GOAL_REACT=12"]
    end
    A & B & C -->|软提示不管用| D
    D --> E
    E -->|判定未达成| RETRY["继续干"]
    RETRY --> L1
```

**层次很清楚**：步级闸门抓局部打转，回合级闸门抓预算耗尽，会话级裁判管"到底做完没有"。三层各管各的，互不替代。

---

<h2 id="ch6">第 6 章 Agent 模式与子 Agent：13 个内置 agent 的三种身份</h2>

### 6.1 三种 mode

`agent/agent.ts:35` 定义了 agent 的三种身份：

```ts
mode: z.enum(["subagent", "primary", "all"])
```

内置的 13 个 agent（`agent/agent.ts:133-418`）按身份分成两组：

| mode | agent | 用途 |
|---|---|---|
| **primary**（用户可切换） | **`build`** | 默认，完整工具权限 |
| | **`plan`** | **只读**分析模式，用于探索代码和设计方案 |
| | **`compose`** | 编排模式，spec 驱动开发 + 技能驱动工作流 |
| | `orchestrator` | 一个窗口管所有任务（[第 16 章](#ch16)，flag 门控） |
| | `max` | 高预算模式 |
| **subagent**（系统按需创建） | `general` · `explore` | 通用 / 探索 |
| | `title` · `summary` · `compaction` | 起标题 / 摘要 / 压缩 |
| | **`checkpoint-writer`** | 自动维护 `checkpoint.md`（[第 9 章](#ch9)） |
| | **`dream`** · **`distill`** | 沉淀知识 / 蒸馏技能（[第 17 章](#ch17)） |

### 6.2 Sticky Tab：为什么 compose 进去就出不来

README 描述的切换规则很特别：

> Press `Tab` to switch between primary agents… After the first message the mode **locks**: **Build and Plan can still switch between each other, but Compose is isolated once entered** — keeping the skill/tool set fixed from session start **significantly improves tool-call reliability**.

```mermaid
stateDiagram-v2
    [*] --> 未发首条消息
    未发首条消息 --> build: Tab
    未发首条消息 --> plan: Tab
    未发首条消息 --> compose: Tab
    build --> plan: ✅ 发消息后仍可互切
    plan --> build: ✅
    build --> compose: ❌ 发首条消息后禁止
    plan --> compose: ❌
    compose --> compose: 🔒 一旦进入即隔离
```

**理由是"工具调用可靠性"**：模型在会话开始时看到的技能/工具集如果中途变了，它已经内化的调用习惯会失配。build ↔ plan 之间工具集是包含关系（plan 是 build 的只读子集），所以互切安全；compose 的技能集完全不同，切进切出会破坏可靠性。

从 `plan` 切到 `build` 时会注入一段状态变更提示（`session/prompt/build-switch.txt`，仅 233 字节）：

```
<system-reminder>
Your operational mode has changed from plan to build.
You are no longer in read-only mode.
You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed.
</system-reminder>
```

**模式切换必须显式告知模型**——否则它会继续按只读模式的习惯行事。

### 6.3 子 Agent 派发：actor 体系

`actor/spawn.ts`（1 010 行）是子 agent 的派发核心。几个值得记的常量（`:34-40`）：

```ts
export const MAX_PRE_REACT  = 3   // preStop ReAct 再入上限
export const MAX_POST_REACT = 3   // postStop ReAct 再入上限
```

注释同样标着 `TODO: lift to mimocode.json config`，并且规划了将来的约束方向：

> Plan: **platform cap = hard ceiling, hook cap may only narrow, never widen.**

**平台上限是硬天花板，钩子只能收紧不能放宽**——这是扩展点设计的一条好规则（和 Open Design 的封闭 `until` 词汇表是同一个思路）。

对照一下三个 ReAct 上限：

| 上限 | 值 | 作用域 |
|---|---|---|
| `MAX_GOAL_REACT` | **12** | 主会话的 goal 再入（[第 4 章](#ch4)） |
| `MAX_PRE_REACT` / `MAX_POST_REACT` | **3** | 子 agent 的 preStop / postStop 再入 |
| `MAX_TASK_GATE_SUBAGENT_REACT` | — | 任务闸门（`task/gate.ts`） |

**主会话给 12、子 agent 给 3**——因为"main-session goals are usually larger"（`prompt.ts:171-173`）。

### 6.4 停滞看门狗

`actor/spawn.ts:42-46` 描述了一个三层时间窗的设计：

> T40 stall watchdog scan cadence. Sits **between the per-step turn heartbeat and the `DEFAULT_LIVENESS_STALL_MS` (90s) window**, and **just under the registry's own 60s stuck-scan**, so a genuinely stalled child is caught within ~one window of flipping to `stalled` **without hammering the DB**.

三个周期要互相错开：每步心跳 < 看门狗扫描 < 60s 注册表扫描 < 90s 停滞判定窗口。**扫太频繁会打爆数据库，扫太慢会漏掉卡死的子进程。**这类"多个定时器互相定位"的注释在实现里很少见，但对维护极有价值。

### 6.5 系统 agent 的特殊待遇

`agent/config.ts:5` 定义了一个封闭集合：

```ts
export const SYSTEM_SPAWNED_AGENT_TYPES: ReadonlySet<string> =
  new Set(["checkpoint-writer", "dream", "distill"])
```

这三个"系统 agent"在两个地方被区别对待：

**① 权限询问一律自动拒绝**（`decideAskRouting`，`agent/config.ts:38-48`）——它们没有通往人的路径，见 [第 16.4 节](#ch16)。

**② 无效输出策略单独声明**（`config.ts:11-15`）：

```ts
export const SYSTEM_INVALID_OUTPUT_POLICIES: Readonly<Record<string, InvalidOutputPolicy>> = {
  "checkpoint-writer": "checkpoint",
  dream: "actor",
  distill: "actor",
}
```

注释解释：「System agents must **opt into** an invalid-output contract instead of **inheriting the user-facing primary retry** when they run with agentID "main".」

**系统 agent 不能继承面向用户的重试逻辑**——用户看得见的重试会弹提示、会占前台，系统 agent 在后台跑，需要另一套失败处理。

---

<h2 id="ch7">第 7 章 Try-Best 检测器与产物级验证</h2>

`session/try-best-detector.ts`（9 283 字节）抓的是另一类自欺：模型说"我已经尽力了"、"这是目前能做到的最好结果"，然后停下——但用户要的是**做完**，不是**尽力**。

它和 Goal 裁判互补：

| | **Goal 裁判** | **Try-Best 检测器** |
|---|---|---|
| 启用 | 需用户显式 `/goal`，**opt-in** | **常开** |
| 手段 | 独立模型读 transcript | 措辞层面的启发式 |
| 成本 | 一次额外模型调用 | 近乎零 |

配套的还有 `session/checkpoint-validator.ts`（8 174 字节）和 `session/checkpoint-retry.ts`（7 065 字节）——checkpoint 写完之后要**验证**，不合格要**重试**。同样是"不信任模型的自我报告"这条主线：让 checkpoint-writer 子 agent 写完快照，**不代表快照是对的**。

> 📌 **本系列对照**：openworker 的 `verify-scorecard` 是让模型自己核对并发一张记分卡（宿主程序化检查卡片存在性）；Open Design 的 Design Jury 是五个陪审员打分；MiMo 是**独立裁判 + 措辞启发式 + 产物验证**三管齐下。三家都不信模型说"我做完了"，但不信的方式各不相同。

---

<h2 id="ch8">第 8 章 一模型一提示词：20 份 prompt 与路由规则</h2>

> ⚠️ **归属先说清楚**：**分家族提示词路由是上游 OpenCode 的设计**，不是小米首创。上游 `session/prompt/` 已有 **10 份**（anthropic / beast / codex / copilot-gpt-5 / default / gemini / gpt / kimi / meta / trinity）+ 一个结构完全相同的 `if` 链。而且其中 **5 份在 MiMo 里字节级未改**（gemini 15 372 = 15 372、kimi 8 695 = 8 695、codex 7 390 = 7 390、trinity 差 1 字节、copilot-gpt-5 差 2 字节）。
>
> **小米做的四件事**：① 加 5 个家族（deepseek / glm / minimax / orchestrator / compose）；② **重写最常用的 3 份**（gpt 9 284→25 447、default 8 528→20 800、anthropic 8 212→14 281）；③ 加**双 ID 兜底**（上游只用 `model.api.id`）；④ 把三个循环检测器放进同一目录。逐项证据见 [第 20.3 节](#ch20)。

### 8.1 目录全貌

`packages/opencode/src/session/prompt/` 下 20 个文件：

| 文件 | 字节 | 面向 |
|---|---|---|
| `gpt.txt` | 25 447 | GPT 系（配 GPT 工具 ABI） |
| `orchestrator.txt` | 21 002 | Orchestrator 模式 |
| **`default.txt`** | 20 800 | **兜底** |
| `gemini.txt` | 15 372 | Gemini |
| `anthropic.txt` | 14 281 | Claude |
| `copilot-gpt-5.txt` | 14 239 | Copilot 的 GPT-5 |
| `default.old.txt` | 14 195 | 旧版兜底（存档） |
| `beast.txt` | 11 970 | gpt-4 / o1 / o3 |
| `minimax.txt` | 10 908 | MiniMax |
| `deepseek.txt` | 10 530 | DeepSeek |
| `kimi.txt` | 8 695 | Kimi |
| `trinity.txt` | 7 749 | Trinity |
| `codex.txt` | 7 390 | Codex |
| `compose.txt` | 6 036 | Compose 模式 |
| `glm.txt` | 4 890 | GLM |
| `max-steps.txt` · `build-switch.txt` | 750 / 233 | 状态切换提示 |
| `empty-step-detection.ts` · `text-ngram-detection.ts` · `text-loop-recovery.ts` | — | 三个检测器 |

### 8.2 路由规则：一串 if

`session/system.ts:26-39` —— 全部路由逻辑就这 17 行：

```ts
export function provider(model: Provider.Model) {
  const prompt = (id: string) => {
    if (id.includes("gpt-4") || id.includes("o1") || id.includes("o3")) return PROMPT_BEAST
    if (id.includes("gpt"))        return id.includes("codex") ? PROMPT_CODEX : PROMPT_GPT
    if (id.includes("gemini-"))    return PROMPT_GEMINI
    if (id.includes("claude"))     return PROMPT_ANTHROPIC
    if (id.toLowerCase().includes("trinity"))  return PROMPT_TRINITY
    if (id.toLowerCase().includes("kimi"))     return PROMPT_KIMI
    if (id.toLowerCase().includes("deepseek")) return PROMPT_DEEPSEEK
    if (id.toLowerCase().includes("glm"))      return PROMPT_GLM
    if (id.toLowerCase().includes("minimax"))  return PROMPT_MINIMAX
  }
  return [prompt(model.id) ?? prompt(model.api.id) ?? PROMPT_DEFAULT]
}
```

三点值得注意：
1. **顺序有意义**：`gpt-4`/`o1`/`o3` 必须在 `gpt` 之前判，否则 gpt-4 会掉进 `PROMPT_GPT`。
2. **双 ID 兜底**：先用 `model.id` 匹配，不中再用 `model.api.id`——同一个模型经不同 provider 暴露时 ID 可能不同（如 OpenRouter 上的 `xiaomi/mimo-v2.5`）。
3. **最后兜底 `PROMPT_DEFAULT`**：没匹配上的模型有一份通用提示词，不会裸奔。

**架构文档自己承认了这里的粗糙**（`docs/architecture/codex-microkernel-runtime.md`）：

> Prompt 路由与工具 profile 目前是**两套字符串规则**，尚未统一成模型能力协商层。

也就是说：`system.ts` 的 `provider()` 决定用哪份提示词，`tool/gpt.ts` 的 `usesGPTToolset()` 决定给哪套工具——**两个函数各判各的字符串**，将来可能不一致。

### 8.3 环境块与缓存前缀

`system.ts:66-84` 拼的环境块里藏着一个缓存优化：

```ts
`  Today's date: ${new Date(now).toDateString()}`,
```

注释解释：

> Anchored to the **session's creation time (not request time)** so this block stays **byte-identical across every turn of a session** — including ones that **cross midnight** — keeping it inside the Anthropic cached system prefix.

**用请求时间会让跨午夜的会话在某一轮突然缓存失效**。锚到会话创建时间就不会。

同一段还有一个诚实的风险标注（`:87-89`）：视觉模型列表是**懒解析**的，如果 provider 配置在会话中途变了，这一块在不同轮次会不同、**打破缓存前缀**——「In practice provider config is stable within a session」。**已知、可接受、写下来。**

### 8.4 无视觉能力时的降级

模型不支持图像时（`system.ts:105-118`），注入一段 `<vision-capability>` 块：

> You CANNOT see or interpret image content… **Never attempt to analyze an image's visual content yourself.** If a task needs image understanding, **dispatch a vision-capable subagent via the actor tool**, passing the image file path so the subagent can Read it.

并且**列出当前配置里有视觉能力的模型**（最多 3 个）供它 `--model` 调用，还给了示例命令。最后补一句区分：如果你要的是文件的**二进制结构**而不是**视觉内容**，用 `hexdump -C`，不要用 read 工具。

**这是"能力降级"的教科书写法**：不只是说"你不行"，而是说"你不行，但这条路可以走，这是具体走法"。

> 有个细节：`maskVisionCapability`（`system.ts:85-87`）——模型 ID 里含 `gpt`/`claude`/`gemini` 的**不注入**这段，因为这些家族的视觉能力判定容易误报，宁可不提示。

---

<h2 id="ch9">第 9 章 Checkpoint：把上下文当成可重建的快照</h2>

### 9.1 与"压缩"的区别

传统做法是**压缩**（compaction）：上下文快满了，让模型把前面的对话总结成一段，替换掉原文。问题是总结**有损且不可逆**，而且总结本身也占上下文。

MiMo 的 checkpoint 是另一条路（README）：

> **Context reconstruction** — when context approaches the limit, **rebuilds it from the latest checkpoint, project memory, task progress, and retained recent messages** so the agent can continue the current task.

不是把历史揉成一段话，而是**从几个结构化来源重新拼一份上下文**。

四份文件：

| 文件 | 内容 | 谁维护 |
|---|---|---|
| `MEMORY.md` | 项目持久知识、规则、架构决策 | `/dream` + 手工 |
| `checkpoint.md` | 结构化状态快照 | **checkpoint-writer 子 agent 自动维护** |
| `notes.md` | 临时草稿区 | agent 随手写 |
| `tasks/<id>/progress.md` | 每个任务的进度日志 | 任务系统 |

### 9.2 预算化注入

README：

> **Budgeted injection** — uses a **token budget** to control how much checkpoint, memory, and notes content enters context, with **importance ranking**.

实现里对应 `readBudgeted` / `readBudgetedSectionAware` 和 `CHECKPOINT_SECTION_BUDGETS`（`checkpoint.ts:36-38` 的 import）。**分节预算**意味着 checkpoint 里不同小节有各自的配额，重要的节不会被次要的节挤掉。

### 9.3 超长用户消息的截断策略

`checkpoint.ts:52-69` 的 `truncateVerbatimUserMsg` 是个小而完整的工程样本：

```ts
// 保留头 ~60% + 尾 ~30%，中间放省略标记并指回 messageID
const head = text.slice(0, Math.floor(capTokens * 0.6) * 4).replace(/[\uD800-\uDBFF]$/, "")
const tail = text.slice(-Math.floor(capTokens * 0.3) * 4).replace(/^[\uDC00-\uDFFF]/, "")
return [head,
  `[…elided ${elidedTokens} tokens; messageID=${messageID}; use the history tool with operation=around to fetch full content]`,
  tail].join("\n")
```

三个细节：
1. **头 60% 尾 30%**——头部有意图，尾部常有结论/最新要求，中间最可省。
2. **代理对修复**：`slice()` 按 UTF-16 码元切，可能把一个代理对劈成两半。所以尾随的高代理和前导的低代理各自剥掉——注释原话：「**otherwise emoji / non-BMP chars don't render as garbage**」。
3. **省略标记带回捞路径**：`messageID=... use the history tool with operation=around`。**模型看到被省略的部分时知道怎么拿回全文。**

### 9.4 高压力提示的去抖

`prompt.ts:216-243` 的 `nudgedSinceBoundary` 解决一个具体麻烦：上下文压力大时要提示模型"该刷记忆了"，但一个持续高压的回合会发很多条消息（每个工具调用一条），固定窗口会让已提示的那条滑出窗口、**中途重复提示**。

解法是**以 checkpoint 边界划分 episode**：

> Keying off the **checkpoint boundary** rather than a fixed message count is deliberate: a single sustained high-pressure turn can emit many tool-call steps — each its own message — so a fixed-size tail would let the already-nudged message slide out of the window and re-fire the nudge mid-turn. **The boundary only advances when a checkpoint/rebuild actually discards context, which is exactly when a fresh nudge becomes useful again.**

**去抖的窗口应该对齐到"状态真的变了"的那个事件，而不是一个拍脑袋的消息数。**

---

<h2 id="ch10">第 10 章 记忆：SQLite FTS5 + BM25 相对地板</h2>

### 10.1 表结构

`memory/fts.sql.ts` 全文：

```ts
export const MemoryFtsTable = sqliteTable("memory_fts", {
  id: integer().primaryKey({ autoIncrement: true }),
  path: text().notNull().unique(),
  scope: text().notNull(),          // 作用域（项目 / 全局 / 会话…）
  scope_id: text().notNull().default(""),
  type: text().notNull(),           // 类型（checkpoint / memory / notes / progress…）
  body: text().notNull(),
  fingerprint: text().notNull(),    // 内容指纹，用于增量重建索引
  last_indexed_at: integer().notNull(),
}, (table) => [
  index("memory_fts_scope_idx").on(table.scope, table.scope_id),
  index("memory_fts_type_idx").on(table.type),
])
```

配一张 FTS5 虚表 `memory_fts_idx`，检索时 join。

### 10.2 四个检索工程细节

**① 查询构造**（`memory/fts-query.ts`，被 `service.ts:66-69` 调用）：

> Build a **token-level FTS5 query**: punctuation becomes separators, each alphanumeric run becomes a **phrase-quoted literal**, OR-joined.

把标点当分隔符、每个字母数字串加引号做成短语字面量、再 OR 连接——**避免用户查询里的标点被 FTS5 当成语法**（`-` `"` `*` `(` `)` 在 `MATCH` 语法里都有含义，不处理会报错或语义漂移）。

**② BM25 相对地板**（`service.ts:71-84`）——这段注释是全文件最精彩的：

> OR-join means a doc matching only a common word (e.g. **every `checkpoint.md` matches "checkpoint"**) still matches, but BM25 ranks it far below a doc matching several rare query words. We drop the common-word noise with a **RELATIVE floor**: keep results scoring at least `ratio` of the top hit's score.
>
> **Relative (not absolute) because BM25 magnitudes are corpus-size-dependent** — in a tiny corpus every score collapses toward 0 (low IDF), so **any fixed absolute floor would wrongly wipe real hits**. The #1 result is **ALWAYS kept** (a match is a match even when BM25 can't discriminate). Default 0.15.

三层考虑，每层都有理由：
- OR 连接会引入常见词噪音 → 需要过滤
- 绝对阈值在小语料上会误杀 → 用**相对**阈值（top 分数的 15%）
- 相对阈值在完全无区分度时也会误杀 → **第一名永远保留**

**③ 过量抓取**（`service.ts:117-119`）：

```ts
const fetchLimit = Math.min(limit * 3, 50)
```

> Over-fetch (3x, capped) so the relative floor **can trim common-word noise without starving the list** when there ARE enough real hits.

**④ 分数方向翻转**（`service.ts:122-129`）：FTS5 的 `bm25()` **越小越好**，对外统一成**越大越好**（取负）。这类"外部约定与内部实现方向相反"的地方最容易出错，代码里显式注明了。

### 10.3 懒重建与跨产品索引

`service.ts:60-65`：每次 search 前先 reconcile 一遍（可配置关闭），理由是「covers **off-tool writes**」——用户可能直接用编辑器改了 `MEMORY.md`，没走工具。`fingerprint` 字段让重建是增量的。

还有一个跨产品的彩蛋（`service.ts:38` + `:62`）：`memory.cc_index` 配置打开后，会把 `~/.claude/projects` 也纳入索引——**读 Claude Code 的项目记忆**。

---

<h2 id="ch11">第 11 章 压缩点可调：<code>/context-limit</code> 与成本档位</h2>

这是一个纯产品驱动的工程功能。README 给了三条动机，每条都很实在：

> - **Cost tiers.** OpenAI prices GPT-5.6 prompts **above 272K input at 2x input and 1.5x output for the whole request**.
> - **The advertised window is not always what you get.** The same model can have a different usable window depending on how you reach it — a ChatGPT/Codex subscription, a direct API key, or a reseller such as OpenRouter — **so a catalog figure of 1M does not mean your route serves 1M**.
> - **Quality and latency.** Very long contexts are slower and, past a point, **not better**.

配置支持通配符和"最长模式优先"：

```jsonc
{
  "compaction": {
    "max_context": {
      "openai/gpt-5.6": "272K",   // token 数、"300K"、"1M"、或窗口的 "50%"
      "anthropic/*": "300K"        // 允许通配符，最长模式胜出
    }
  }
}
```

两条安全约束：
- **只能调低不能调高**：「The value is always **clamped to what the provider actually accepts**, so it can only lower the compaction point, never raise it.」
- **`0` 恢复模型自己的窗口**。

**UI 上的呈现也想过**：提示符页脚显示 `33.0K/260K↓ (13%)`，那个 **`↓` 表示当前有预算在生效**——用户一眼知道自己不是在用模型的完整窗口。`mimo models <provider>` 会逐模型打印"解析出的窗口"和"实际会在哪压缩"，`/status` 给细分。

> 🧠 **一句话**：这是本系列里少见的、**把商业计费规则直接编码进 Agent 配置**的功能。272K 这个数字不是技术阈值，是 OpenAI 的价格档位分界线。

---

<h2 id="ch12">第 12 章 GPT 微内核：给 Codex 一套更小的工具 ABI</h2>

> ⚠️ **归属先说清楚**：判定 GPT 家族的那行表达式**在上游 OpenCode 里一字不差地存在**（`tool/registry.ts:293`）：
>
> ```ts
> input.modelID.includes("gpt-") && !input.modelID.includes("oss") && !input.modelID.includes("gpt-4")
> ```
>
> 但上游只用它做**一次二选一交换**——`apply_patch` ↔ `edit`/`write`。小米做的是：把它抽成命名函数 `usesGPTToolset()`（独立文件 `tool/gpt.ts`），并把作用域扩成**整套 ABI 替换**（再门控 `exec`、`view_image`，并隐藏 read/grep/glob/multiedit/notebook_edit）。**从"换一把螺丝刀"变成"换一整个工具箱"。**详见 [第 20.4 节](#ch20)。

### 12.1 核心主张

`docs/architecture/codex-microkernel-runtime.md` 开门见山（并且先自我澄清术语）：

> "Codex 微内核运行时"是本文对当前架构的概括，**不是源码中的正式模块名，也不表示操作系统级微内核**。

做法是：**不为 GPT 新建 Agent 引擎**，而是在统一 Session runtime 上做三件事——① 用 GPT/Codex 专属 system prompt；② 通过 `ToolRegistry` 装配更小的模型专属工具 ABI；③ 提供 QuickJS `exec` 在不扩大权限的前提下组合宿主工具。

一句话原则：

> **模型决定做什么，`exec` 负责如何组合，宿主决定是否允许以及如何产生副作用。**

### 12.2 两套工具面

```mermaid
flowchart TB
    M{"usesGPTToolset(modelID)?<br/>含 gpt- 且不含 oss / gpt-4"}
    M -->|是 · GPT/Codex 家族| G["4 件工具 ABI"]
    M -->|否 · 其他模型| N["完整工具套装"]
    G --> G1["bash —— 用 rg/sed 检查搜索 + 执行命令"]
    G --> G2["apply_patch —— 结构化 patch 改文本"]
    G --> G3["view_image —— 本地图片转附件"]
    G --> G4["exec —— QuickJS 里批量调用聚合宿主工具"]
    N --> N1["read / write / edit / multiedit"]
    N --> N2["grep / glob / notebook_edit"]
    N --> N3["bash / …"]
    G -.对 GPT 隐藏.-> N1 & N2
```

判定函数只有 3 行（`tool/gpt.ts:11-13`）：

```ts
export function usesGPTToolset(modelID: string) {
  return modelID.includes("gpt-") && !modelID.includes("oss") && !modelID.includes("gpt-4")
}
```

排除 `oss`（gpt-oss 是开放权重模型，工具行为不同）和 `gpt-4`（老模型走 `beast.txt` 路线）。

`ToolRegistry.available()`（`tool/registry.ts:375-410`）据此过滤：`ToolScriptTool`（即 `exec`）、`ApplyPatchTool`、`ViewImageTool` **只对 GPT 开放**；`EditTool` / `MultiEditTool` / `WriteTool` / `ReadTool` / `GrepTool` / `GlobTool` 等**对 GPT 隐藏**。

### 12.3 为什么要给 GPT 更少的工具

表面看是"减法"，实际是**对齐模型的训练分布**。Codex 系模型在 OpenAI 自家的 harness 里就是用 `bash` + `apply_patch` 这套 ABI 训练的，给它一套 Claude 风格的 read/write/edit/grep/glob，它反而更容易调错。

> 📌 **对照 Open Design**：OD 的做法是**完全不定义工具**，直接用被托管 CLI 的原生工具。MiMo 是**同一个引擎，按模型换工具面**。两者解决同一个问题——"不同模型习惯不同的工具"——但一个靠委托，一个靠适配。

### 12.4 `apply_patch` 与 `view_image` 的诚实边界

架构文档在介绍 `apply_patch` 时主动标注了一个缺陷：

> 它会**预验证全部 patch**，但多文件写入**不是事务性的**，中途失败**不会自动回滚**已写文件。

`view_image` 的三条当前限制也写出来了：`detail` 只写 metadata 不改处理；没有独立的图片大小限制；`exec` 不能透传图片附件，所以图片要直接调 `view_image`。

**在架构文档里列出自己的未修复缺陷，是这份仓库文档质量的一个信号。**

---

<h2 id="ch13">第 13 章 <code>exec</code> 与 QuickJS：组合工具但不放大权限</h2>

> ⚠️ **归属先说清楚**：**"在受限沙箱里跑一段脚本编排工具"这个想法来自上游**——OpenCode 有 `tool/code-mode.ts`（11.8 KB，工具名 `execute`，描述是「Run a confined orchestration script with access to connected MCP tools」），背后是独立包 `@opencode-ai/codemode`。
>
> **但实现被整个换掉了**，而且换掉的理由很硬：上游那个沙箱是**基于 acorn 的 3 465 行手写 AST 解释器**（`packages/codemode/src/interpreter/runtime.ts`），MiMo 换成了 **QuickJS-emscripten（真正的 WASM JS 引擎）**；上游只暴露 **MCP 工具**，MiMo 改为经 late-bound registry 暴露**宿主工具**。这两处差别决定了后面所有的安全设计。详见 [第 20.5 节](#ch20)。

### 13.1 它是什么

模型提交一段 TypeScript/JavaScript async function body，通过 `tools.<name>()` 调用宿主工具。一次 `exec` 可以批量调用并聚合多个工具，省掉多轮往返。实现是 `ToolScriptTool`（`tool/tool-script.ts:303`），对模型暴露为 `exec`。

**危险显而易见**：如果 `exec` 里能调到外层被禁的工具，权限体系就穿了。

### 13.2 三道防线

**① Late-bound registry**（`tool/tool-script-ref.ts:1`）：

> 使用 late-bound registry，让 `exec` 取得**和外层相同、已经过 model/agent 过滤的** `Tool.Def`：
> - 外层不可见的 `read`、`write`、`edit` **不会在 `exec` 内重新出现**；
> - builtin 子调用执行原来的 `Tool.Def.execute()` 和 `Tool.Context`；
> - MCP 子调用仍**逐次执行 `ctx.ask()`**；
> - `exec_command` 只是 `bash` 的别名，权限和执行路径相同。

**关键在"same filtered registry"**——`exec` 不是拿到一份全量工具表，而是拿到和外层一模一样的那份。

**② 控制流工具被排除**：

> `task`、`actor`、`question`、`skill`、`workflow`、`cron`、`session` 等控制流工具被排除，因为它们**改变对话或调度状态，不适合隐藏在一次脚本调用中**。

**这条区分很精准**：能产生副作用的工具（bash、edit）可以在 exec 里调，因为权限闸门照常生效；但能**改变调度状态**的工具不行，因为那会让一次工具调用在用户不知情的情况下改变整个会话的走向。

**③ 两层安全边界**：

| 层 | 做什么 | 不做什么 |
|---|---|---|
| QuickJS（`workflow/sandbox.ts:106`） | 隔离 guest code：**没有 Node、`process`、`fetch`、timer、模块加载** | 不隔离 `bash`——那仍是真实 Shell |
| 宿主工具 | permission、external-directory、memory guard、工具自身校验 | — |

文档明说：「QuickJS 只隔离 `exec` 代码。**`bash` 仍是真实 Shell，不是容器 sandbox。**」

### 13.3 资源限制与"活跃计算"

| 资源 | 默认 / 上限 |
|---|---|
| 嵌套工具调用 | 默认 50，最高 500 |
| 并发调用 | 8 |
| **活跃计算** | 默认 60 秒，最高 600 秒 |
| Wall clock | 30 分钟 |
| Guest 内存 | 默认 64 MiB |
| 代码 / 返回值 / 日志 | 128 KiB / 256 KiB / 64 KiB |
| `files.*` 单文件 | 10 MiB |

**"活跃计算"这个概念值得单独说。** `sandbox.ts:112-131` 实现了一个 pause/resume 时钟：

```ts
const hostCallTracker = {
  start: () => { pending++; if (pending === 1) activeAccum += Date.now() - activeStart },
  end:   () => { pending--; if (pending === 0) activeStart = Date.now() },
}
```

> Active-time accounting: **charge the guest only while no host hook promise is pending.**

脚本等宿主工具返回的时间**不算它的计算预算**。只有 guest 字节码真正在跑的时候才计时。否则一个正常脚本调 5 次 bash（每次 20 秒）就会被 60 秒预算误杀。Wall clock（30 分钟）单独兜底真正的卡死。

**文件访问也被夹住**：`files.readText` 只能读 worktree 或 OS tmp 内的 UTF-8 文本；`files.writeText` **只能写 OS tmp**。项目变更必须走受权限控制的宿主工具。

### 13.4 QuickJS 集成的三条硬约束

`sandbox.ts:100-105` 记录了 2026-06-01 那次技术验证得出的结论：

> - **sync-promise bridge**（`newPromise` + `executePendingJobs`），**NOT asyncify**
> - 需要一个**并发 pump** 配合 `resolvePromise`，让 host-promise 能结算
> - **每个 `QuickJSHandle` 必须在 context dispose 之前释放**（否则**进程 abort**）

第三条尤其致命——注释在 `deferreds` 变量上又强调了一遍：一个**未结算**的 deferred（脚本返回时宿主 promise 还在飞）也必须先释放，否则 `vm.dispose()` 会因为活着的 GC 对象**硬崩进程**。

> 🧠 **一句话**：`exec` 是这份仓库里工程密度最高的地方——它要同时满足"能组合"、"不放大权限"、"不误杀正常脚本"、"不崩进程"四个约束，每一个都留下了具体的实现痕迹。

---

<h2 id="ch14">第 14 章 权限系统：FORCED_ASK 与通配符压不住的那条线</h2>

### 14.1 规则模型

`permission/index.ts` 的基础模型很常规：`Action = allow | deny | ask`（`:33`），规则是 `{permission, pattern, action}` 三元组（`:38-44`），多个规则集用 `merge()`（就是 `flat()`，`:587-589`）合并，`evaluate()` 求值（`:184-186`）。

有意思的在两处。

### 14.2 FORCED_ASK：通配符授权压不住删除

`permission/index.ts:188-195`：

```ts
// Permissions whose "allow" outcome must ALWAYS come from an explicit human ask.
// A wildcard rule like `permissions.allow: ["*"]` (or a stored `{permission:"*",
// pattern:"*", action:"allow"}` approval) MUST NOT be able to pre-authorize
// these — the whole point of a forced-ask permission is that the intent to
// perform an irreversible action must be recorded in-band, not inherited from
// a broad blanket rule. Explicit deny still wins; the tool-side env opt-out
// (e.g. MIMOCODE_AUTO_APPROVE_DELETE for bash_delete) is the only bypass.
const FORCED_ASK = new Set(["bash_delete"])
```

拆开看这条规则的四个性质：

| 性质 | 说明 |
|---|---|
| **通配符 allow 无效** | `permissions.allow: ["*"]` 不能预授权删除 |
| **历史批准也无效** | 存下来的 `{permission:"*", pattern:"*", action:"allow"}` 同样不行 |
| **显式 deny 仍然生效** | 强制询问 ≠ 强制执行 |
| **唯一旁路是工具侧环境变量** | `MIMOCODE_AUTO_APPROVE_DELETE`——**要绕过必须显式、且在另一个层面绕** |

**核心理由**：「the intent to perform an irreversible action must be **recorded in-band**, not inherited from a broad blanket rule」——不可逆操作的意图必须**当场记录**，不能从一条宽泛的总授权里继承。

> 📌 **本系列对照**：CodeWhale 的 "safe by construction"（模型根本没有越权的工具）、openworker 的五档模式 + 收件箱、Claude Code 的权限闸门 + hooks、Open Design 的**完全委托**。MiMo 走的是"通用规则 + 少数不可协商的例外"——**规则可以宽，但有些线通配符跨不过去。**

### 14.3 工具组别名与 findLast

`permission/index.ts:591-614` 的 `disabled()` 处理一个实际的配置便利性问题：

```ts
const EDIT_TOOLS = ["edit", "write", "apply_patch", "multiedit"]
const READ_TOOLS = ["read", "view_image"]
```

匹配规则时，除了按工具自己的名字，**EDIT_TOOLS 还额外按 `edit` 这个组别名匹配**。这样用户写 `edit: "deny"` 就能一次禁掉整个编辑家族。

而 `findLast` 的选择是关键：

> `findLast` returns the **last-merged** matching rule, so a **tool-specific rule placed after a group rule wins naturally**. This preserves the convenience of `edit: "deny"` covering all edit-family tools while letting an explicit `write: "allow"` or `write: "deny"` take precedence when present.

**用数组顺序表达优先级，用 `findLast` 让后写的赢**——不需要额外的优先级字段，且符合"配置文件里后写的覆盖先写的"这个直觉。

---

<h2 id="ch15">第 15 章 Workflow：确定性 JS 脚本编排多 Agent</h2>

### 15.1 与"对话"的分野

README 的定义很清楚：

> Workflows are **deterministic JavaScript scripts** that orchestrate multiple agents in a sandboxed runtime. Unlike agent conversations, workflows encode **fixed phase sequences with bounded retries and automatic parallelization** — **fire-and-forget** execution with no user interaction required.

| | **对话式**（build + `/compose-next`） | **Workflow** |
|---|---|---|
| 控制流 | 模型决定 | **脚本写死** |
| 交互 | 可中途改方向 | **无需交互** |
| 并行 | 靠模型派子 agent | **自动并行化** |
| 重试 | 模型自己判断 | **有界重试** |
| 适合 | 需要中途注入判断 | **需求明确、任务可切分** |

README 给了明确的选型建议：「use the **workflow** when requirements are clear and tasks split cleanly (deterministic, parallel, non-interactive); use the **build** agent with `/compose-next` … when you need to **redirect mid-flow or inject judgment** between steps」。

### 15.2 四个内置流水线

| Workflow | 阶段 | 特点 |
|---|---|---|
| **`compose`** | Brainstorm → Design → Implement → Verify → Review → Report → Merge | **把独立任务自动并行到隔离的 git worktree**，每个任务用 TDD，阶段间传结构化输出 |
| **`deep-research`** | Brief → Plan → Research → Reflect → Write → Review | 规划独立研究角度 → 并行子 agent 收集**带引用**的发现 → 反思缺口 → 写成一份连贯 Markdown → **冷审引用**。收敛式，靠文件 checkpoint 可续跑 |
| **`fact-check`** | Plan → Search → Extract → Group → Crosscheck → Report | **对抗式**事实核验：并行搜索 → 抽取可检验事实 → 去重归组 → **3 陪审员对抗投票**逐条交叉核对 |
| **`research-experiment`** | Baseline → Loop → Audit → Report | 面向**可机械验证指标**的自主优化循环：建基线 → 假设/实现/评估/保留或回滚 → **审计是否在刷指标** → 出可复现结果日志 |

后两个尤其有意思：
- `fact-check` 的 **3 陪审员对抗投票**和 Open Design 的 Design Jury 是同一族设计，但方向相反——OD 的五陪审员**协作评审同一产物**，MiMo 的三陪审员**对抗核验同一事实**。
- `research-experiment` 明确要求**审计指标造假**（"audits for metric gaming"），并且要求调用方提供**固定预算的评估命令**和**明确的可编辑文件范围**。这是"别让 Agent 骗自己"主题在实验场景的延伸。

### 15.3 内置脚本怎么打进二进制

`workflow/builtin.ts:4-20` 的注释解释了一个 Bun 特有的技巧：

```ts
// @ts-expect-error TS1192: import-attribute text loader, resolved by Bun not tsgo
import DEEP_RESEARCH_SCRIPT from "./builtin/deep-research.js" with { type: "text" }
```

> `with { type: "text" }` makes Bun **inline the .js file's SOURCE as a string** (not import it as a module) and embeds it into the compiled binary via `bun build --compile`… A `Bun.file(...).text()` fallback is **intentionally NOT used**: it reads the real filesystem at runtime, **which does not exist inside a compiled standalone binary**.

并且诚实标注了代价：tsgo 会把 `.js` 当真模块解析并报 TS1192，所以加了作用域仅限这一行的 `@ts-expect-error`。

### 15.4 两个防御性细节

**① 启动即失败**（`builtin.ts:30-34`）：

> `file` is carried so a malformed meta **names the offending script** — this throw runs at **module init**, so a broken built-in **fails the whole app boot**; the path tells the user which one.

内置 workflow 的 meta 解析失败 → **整个 App 起不来**，而且错误消息里带文件名。和 Open Design 的 registry 去重 `throw` 是同一个哲学：**让错误尽早、尽响**。

**② Null 原型注册表**（`builtin.ts:42-45`）：

```ts
// Null-prototype so the registry is a self-evidently closed set: a lookup like
// get("constructor")/get("toString") returns undefined, not an inherited
// Object.prototype member.
const REGISTRY: Record<string, Entry> = Object.create(null)
```

**用 `Object.create(null)` 防原型链污染**——查 `constructor` 返回 `undefined` 而不是 `Object.prototype.constructor`。当注册表的键可能来自模型输出时，这是必须的。

### 15.5 自定义与覆盖

放一个 `.js` 到 `.mimocode/workflows/` 或 `.claude/workflows/` 就能定义自己的 workflow；**用同名文件可以覆盖内置**（如 `.mimocode/workflows/compose.js`）。

注意它同时扫 `.claude/` 目录——和技能、记忆索引一样，**MiMo 在多处主动兼容 Claude Code 的目录约定**。

---

<h2 id="ch16">第 16 章 Orchestrator 模式：一个窗口管所有任务</h2>

`docs/harness/MiMo Orchestrator Mode.md`（13 KB）是这个实验功能的完整设计文档。**默认关闭**，由单一 flag `MIMOCODE_EXPERIMENTAL_ORCHESTRATOR` 门控。

### 16.1 它解决的问题

> 真正的负担不是机器算力，而是**你的注意力和精力**——上下文在窗口之间反复切换，人被"多路复用"拖垮。

要并行推进多件事，常规做法是开好几个终端、每个跑一个会话，然后不停切换盯进度。Orchestrator 的主张是：**一个窗口、一个会话、纯自然语言管理全部任务**。

### 16.2 模型

```mermaid
flowchart TB
    U["用户目标（自然语言）"] --> O["Orchestrator 会话（全局唯一）"]
    O -->|session create| A["child A · build · dir=repo1 · --isolate"]
    O -->|session create| B["child B · plan · dir=repo2"]
    O -->|session create| C["child C · compose · dir=repo1 · --isolate"]
    A & B & C -->|完成| INBOX["actor_notification → inbox"]
    INBOX -->|主动唤醒| O
    O --> MERGE["git 合并各 child 的 mimocode/* 分支"]
    MERGE --> REPORT["汇报给用户"]
```

**核心边界**（文档 §1）：

> Orchestrator 自己**不做实质工作**——不写代码、不做具体实现规划、不做质量评审。这些都委派出去…"拆分成派发单元"是它的活；"某个单元怎么实现"和"评审结果"是它委派的活。

每个 child 是**独立会话**（有自己的 session id、任务面板、记忆），以 `mode: "peer"` 在后台运行。**child 是 peer，不是 in-session 的 subagent**——用户可以像 `mimo -c <id>` 一样完整 attach 进任意 child 查看/接管。

### 16.3 `session` 工具的 8 个 verb

只有 Orchestrator 模式能看到这个工具（按 agent 名门控 + flag 门控）。实现见 `tool/session.ts`（53 KB，`KNOWN_VERBS`）：

| verb | 作用 | 关键参数 |
|---|---|---|
| `create` | 后台派发新子会话 | `task`（必填）· `mode` · `model` · `dir` · **`isolate`** |
| `switch` | 前端面板切到某会话 | `sessionID` |
| `list` | 列出所有子会话 | — |
| `cancel` | 停止子会话（曾 isolate 则删 worktree 与分支） | `sessionID` |
| `ask` | **只读、一次性**旁路提问（基于冻结快照，不打断运行） | `session_id` + `question` |
| `setmode` | 改子会话**后续轮次**的 mode（plan 规划完切 build，**同一会话**） | `sessionID` + `mode` |
| `approve` | 批准子会话当前挂起的权限请求 | `sessionID` |
| `grant-approval` | **预授权**未来请求自动批准 | `target`（某 child 或 `all`） |

### 16.4 三个值得学的机制

**① 后台会话的权限审批路由**（文档 §4）

问题很实在：后台跑的 child 没有面对用户的面板，碰到需要 `ask` 的权限门会被**直接拒绝**（`interactive:false` → `DeniedError`），用户看不到也无从批准。

`decideAskRouting`（`agent/config.ts`）做**四分**：

| 场景 | 处置 |
|---|---|
| 系统 agent（checkpoint-writer / dream / distill） | **仍自动拒绝** |
| **Orchestrator peer child**（background + `mode:peer` + 有父会话） | **转发审批** |
| 其他后台（compose 的 subagent 等） | **仍自动拒绝** |

转发后可由用户切进 child 直接批，或由 Orchestrator 用委派授权代批。**判定条件是"有没有一条通往人的路径"**，不是"是不是后台"。

**② `--isolate` 与 git worktree**

打开后 child 跑在自己的 git worktree（分支 `mimocode/<任务>`），位于 `<data>/worktree/<projID>/<task-slug>`。多个 child 编辑同一仓库时互不冲突。**非 git 目录自动降级**为直接在 `dir` 里跑。

配套一条很重的警告（§3.2）：

> **只在工作已合并、或任务被放弃后才 `cancel`** 一个 isolated child —— `cancel` 会删 worktree 和分支，对**未合并**的工作执行会**永久丢失**该工作。**不要因为 child "完成了"就 cancel**（完成产生的是它分支上待合并的提交）。

**"完成"和"可以删"是两件事**——这个坑写进文档了。

**③ 不轮询**（§3.3）

> `create` 立即返回，child 后台运行，完成时消息进入 inbox **唤醒** Orchestrator。派发后就返回、答复用户或结束本轮，**不要循环 `list`/查状态空耗轮次**。

中断 Orchestrator **不会**停掉 child（它们继续跑并在完成时通知）；整个会话退出时 child 才随之退出。恢复靠 `session list` + 用 `actor` 的 send 转发消息——**没有单独的 resume 命令**。

> 📌 **和 openworker 的收件箱对照**：openworker 的 inbox 是"Agent 需要人时挂起等你"，MiMo 的 inbox 是"子会话干完了唤醒父会话"。同一个原语，一个朝人、一个朝内部调度。

---

<h2 id="ch17">第 17 章 Dream / Distill / Evolve：让 Agent 改自己</h2>

三个能力构成"共进化"口号里 Agent 那一侧的进化路径：

| 能力 | 做什么 | 提示词 |
|---|---|---|
| **`/dream`** | 扫最近的会话轨迹，把持久知识**提取进项目记忆**，并**删除过时条目** | `agent/prompt/dream.txt`（7 127 字节） |
| **`/distill`** | 发现最近工作里**重复出现的手工流程**，把高置信度候选**打包成可复用的技能 / 子 agent / 命令** | `agent/prompt/distill.txt`（8 700 字节） |
| **`evolve` 技能** | 「**Total self-modification** — rewrite any layer of the agent: tools, behavior hooks, knowledge, workflows, **even the UI**」 | `skill/builtin/.bundle/evolve/` |

`/dream` 里"**并删除过时条目**"那半句很关键——多数记忆系统只会往里加，越加越乱。**会删的记忆系统才有长期可用性。**

`/distill` 的思路是**从行为里反推抽象**：你手工做了三次同样的流程，它把这个流程固化成技能。这和 hermes-agent 的"闭环学习五件套"是同一族思想。

`evolve` 是三者里最激进的——连 UI 都能改。配套的还有 `skill-creator`（交互式创建/改进技能）和 **`drive-mimo`**（**用一个 MiMoCode 进程去脚本化、测试、自动化另一个 MiMoCode 进程**，headless 或 TUI 模式）。

> ⚠️ **`drive-mimo` 值得单独一提**：它让 Agent 能驱动 Agent，这是自举测试的基础设施，也是"共进化"能落地的前提——**没有它，改完自己没法验证**。

### 17.1 内置技能全表（26 个）

`arxiv` · `claude-code` · `codex` · `compose-next` · `data-analytics` · `deep-research` · `design-blueprint` · `docx-official` · `drive-mimo` · **`evolve`** · `frontend-design` · `grok-build` · `html-to-video-pipeline` · `learn-everything` · `loop` · `mimocode-docs` · `modern-python-toolchain` · `pdf-official` · `playwright` · `pptx-official` · `product-design` · `research-paper-writing` · `sales` · **`skill-creator`** · `super-research` · `xlsx-official`

注意里面有 **`claude-code`、`codex`、`grok-build`** 三个——**MiMo 可以把任务委派给这些外部 CLI**（且只在对应可执行文件已安装时才暴露）。

> 🔄 **一个有意思的反向关系**：Open Design 把 `mimo` 当引擎（它是 OD 26 条适配器之一，`streamFormat: json-event-stream`，MCP 注入走 `MIMOCODE_CONFIG_CONTENT`）；MiMo 又能把 Claude Code / Codex / Grok Build 当工具调。**这个生态里没有绝对的宿主和客体。**

### 17.2 技能检索

README 描述的匹配策略是三级：**精确名 → 本地化别名 → BM25 相关性**。高置信度自动加载，不确定的排序后交给 Agent 判断。

`system.ts:141-152` 的提示词还要求模型把用户请求**改写成一个带维度的 Skill Query**（action / input / output / audience），并且**保留用户显式提到的技能 ID/名/别名原文**，好让精确匹配优先于 BM25。

一句多技能编排：「mentioning **two or more skills** in a single message auto-loads them and **injects a multi-skill orchestration plan**」（对应 `docs/harness/Agent Multi-Skill Workflow Orchestration Design.md`）。

三个环境变量可以关技能（README）：`MIMOCODE_DISABLE_BUILTIN_SKILLS`（全关）、`MIMOCODE_DISABLE_OFFICIAL_SKILLS`（只关 office/媒体那 5 个）、`MIMOCODE_DISABLE_SLASH_SKILLS`（只从 TUI 自动补全里藏起来，Agent 仍可用）。**第三个和前两个的区别写得很清楚**——前两个是真的移出技能列表，第三个只影响补全。

---

<h2 id="ch18">第 18 章 Token Efficient 模式：把 bash 输出的噪音洗掉</h2>

`docs/harness/MiMo Token Efficient Mode.md`（9 KB）。**实验功能，默认关闭**，单 flag `MIMOCODE_EXPERIMENTAL_TOKEN_EFFICIENCY`。

### 18.1 问题

bash 的 stdout/stderr 经常被这些噪音撑爆上下文：ANSI 色码、OSC 超链接、DCS 终端控制序列、`\r` 进度条多帧重叠、误打印的 API key / JWT / PEM 证书、minified JS / 单行 JSON 等超长行、pytest/go test 的无效信息。

### 18.2 三条核心约束

| 约束 | 含义 |
|---|---|
| **仅清 inline，不清落盘** | 清理**只面向 LLM**；TUI 实时预览与磁盘归档**保持原始字节**，便于人工 debug |
| **never-worse 守门** | 管线尾部统一回吐：**任何阶段使输出变大都被丢弃**，回到 Raw 路径 |
| **单 flag、默认关** | 唯一开关，且默认关闭 |

第一条最重要：**给模型看的和给人看的是两份**。人 debug 时需要原始的 ANSI 和进度条，模型不需要。

### 18.3 五层管线

实现在 `tool/bash_token_efficient_pipeline.ts` 与 `bash_token_efficient_heuristic.ts`：

| 层 | 职责 | 顺序约束 |
|---|---|---|
| `clean_progress_pipeline` | 按行折叠 `\r` 进度条，只保留最后一帧 | **必须先于 ansi** |
| `clean_ansi_pipeline` | 剥 ANSI CSI/OSC/DCS、退格 overstrike、控制字节 | progress 之后，下游正则之前 |
| `clean_redact_pipeline` | PEM、Bearer、JWT、AWS/GH/OpenAI/Anthropic/Slack 密钥 | **去重截断前必须先做** |
| `clean_longline_pipeline` | 单行超 500 字符压成 head 160 字符 + 省略提示 | 放最后兜底 |
| never-worse 守门 | 清理后字节数没变小则回吐原文 | 管线尾部 |

**顺序约束都有理由**：进度条要先折叠，否则 ANSI 剥完会留下一堆重叠帧；密钥要在截断前脱敏，否则可能刚好被截在中间躲过正则。

脱敏正则一共 14 条（4 条 ESC + 1 控制字节字符类 + 1 跨行 PEM 块 + 8 条行内密钥），覆盖 Bearer/Token、JWT（`eyJ` 三段 base64url）、AWS（`AKIA`/`ASIA` + 16 位）、GitHub（`gh[pousr]_` + ≥20）、OpenAI（`sk-` + ≥20）、Anthropic（`sk-ant-` + ≥20）等。

> 🧠 **never-worse 守门这个模式值得单独记**：任何"优化"管线都应该有一个"如果没变好就回退原样"的兜底。清理规则总有想不到的输入形状，这条守门让最坏情况**等于不清理**，而不是等于搞砸。

---

<h2 id="ch19">第 19 章 总结：三个最独特的设计与三处取舍</h2>

### 19.1 三个最独特的设计

#### ① 「不信任模型的自我报告」被做成了一整套纵深装置

这是 MiMo Code 真正的主题，而且它不是一句口号，是**四个层次、七个互相独立的机制**：

```mermaid
flowchart TB
    subgraph S["步级（每一步）"]
        A1["空参数工具调用检测<br/>软→硬 2 级"]
        A2["重复动作签名<br/>键序归一化 + 排除叙述文本"]
        A3["文本复读 + n-gram<br/>剥开场白后比对"]
    end
    subgraph T["回合级"]
        B1["步数上限：禁工具 + 强制交代<br/>『overrides ALL other instructions』"]
    end
    subgraph C["会话级"]
        C1["Goal 独立裁判<br/>temperature=0 · 只读 transcript · 四道逃生口"]
        C2["Try-Best 检测器<br/>抓『我尽力了』式假性完成"]
    end
    subgraph D["产物级"]
        D1["checkpoint-validator + checkpoint-retry<br/>写完的快照要验证、不合格要重写"]
    end
    S --> T --> C --> D
```

**四个层次各管各的，没有一个能替代另一个。**这种纵深是本系列其他项目里没有的——别家通常只有其中一两层。

而且每一层都留下了**为什么是这个阈值**的注释：`MAX_GOAL_REACT = 12` 因为主会话目标比子 agent 大；`REPEATED_STEP_THRESHOLD = 3` 因为"连续三次是强信号"；空步检测**故意不抓**空终端因为那会大量误报。

#### ② 把「按模型调形」从雏形推到极致（**继承 + 大幅扩张**，非首创）

⚠️ 先把归属说清楚：**分家族提示词路由和 GPT 工具门控的判定表达式都来自上游 OpenCode**（逐项证据见 [第 20 章](#ch20)）。把它列进"最独特的设计"，是因为**小米把这条路走到了上游没走到的地方**：

| 维度 | 上游 OpenCode | **MiMo Code** |
|---|---|---|
| 提示词家族 | 8 份 | **20 份**（+deepseek/glm/minimax/orchestrator/compose） |
| 三份主力提示词体积 | gpt 9.3 KB · default 8.5 KB · anthropic 8.2 KB | **25.4 KB（2.7x）· 20.8 KB（2.4x）· 14.3 KB（1.7x）** |
| 路由输入 | 只用 `model.api.id` | **双 ID 兜底**（`model.id` → `model.api.id` → default） |
| GPT 门控作用域 | `apply_patch` ↔ `edit`/`write` **二选一** | **整套 ABI 替换**：再门控 `exec`/`view_image`，隐藏 read/grep/glob/multiedit/notebook_edit |
| 门控代码形态 | registry 里的一个内联布尔表达式 | 抽成独立文件 `tool/gpt.ts` 的命名函数 `usesGPTToolset()` |

**这是对"通用 harness"假设的正面否定**——别家的思路是"写一套好提示词让所有模型都能用"；这条路线的思路是"**每个模型的训练分布不同，harness 就该为它调形**"。**上游开了个头，小米把它变成了产品主张**（口号 "Models and Agents Co-Evolve"）。

代价也很直白：20 份提示词要各自维护，路由是一串字符串 `if`，而且**提示词路由和工具路由是两套独立规则**（架构文档自己承认「尚未统一成模型能力协商层」）——实测 `gpt-oss-120b` 就会出现提示词走 GPT、工具面却不是 GPT ABI 的不一致。

#### ③ 沙箱换引擎：从 3 465 行手写解释器到 QuickJS WASM（**重写，非首创**）

⚠️ 同样先说归属：**"在受限沙箱里跑脚本编排工具"来自上游**（`tool/code-mode.ts`，工具名 `execute`，背后是独立包 `@opencode-ai/codemode`）。但**实现被整个换掉了**，这次替换本身才是值得记的工程决策：

| 维度 | 上游 `@opencode-ai/codemode` | **MiMo `workflow/sandbox.ts`** |
|---|---|---|
| 隔离手段 | **acorn + 3 465 行手写 AST 解释器** | **QuickJS-emscripten（真 WASM JS 引擎）** |
| 暴露什么 | **只有 MCP 工具** | **宿主工具**（经 late-bound registry，与外层同一份已过滤表） |
| 控制流工具 | — | **显式排除** task/actor/question/skill/workflow/cron/session |
| 计时 | — | **活跃计算**：等宿主工具的时间不计入 60s 预算，wall clock 30min 兜底 |
| 已知代价 | 要自己实现 JS 语义，覆盖面有洞 | **每个 `QuickJSHandle`（含未结算 deferred）必须在 dispose 前释放，否则进程 abort** |

**为什么值得换**：手写解释器意味着 JS 语义要自己实现——每一处没实现到的地方都是行为差异，每一处实现错的地方都可能是逃逸口，而且 3 465 行要长期维护。换成真引擎后，隔离由 WASM 边界保证，代价转移到"必须正确管理句柄生命周期"——**这是一个把「语义正确性风险」换成「资源管理风险」的交易**，后者有确定的检查清单，前者没有。

而暴露面从 MCP 工具扩到宿主工具，**直接抬高了权限风险等级**——所以才有了 late-bound registry、控制流工具排除、两层边界这一整套配套设计。「模型决定做什么，`exec` 负责如何组合，宿主决定是否允许」这句话，在代码里是能逐条对上的。

### 19.2 三处必须知道的取舍

#### 取舍一：`session/prompt.ts` 单文件 4 595 行 / 208 KB

主循环、提示词组装、goal gate、各种闸门、结构化输出、预测下一条消息……全在一个文件里。作为对比：

| 项目 | 主循环文件 | 行数 |
|---|---|---|
| Open Design | `prompts/system.ts`（**只是组装**，不含循环） | 2 075 |
| **MiMo Code** | **`session/prompt.ts`（循环 + 组装 + 闸门）** | **4 595** |

它的注释密度很高（很多设计决策都写下来了），但**单文件承载的关注点太多**。这是本仓库最明显的可维护性债务。

#### 取舍二：大量实验功能默认关闭，靠 flag 门控

Orchestrator（`MIMOCODE_EXPERIMENTAL_ORCHESTRATOR`）、Token Efficient（`MIMOCODE_EXPERIMENTAL_TOKEN_EFFICIENCY`）都默认关。好处是主路径稳定；代价是**这些设计精良的能力大部分用户根本不会开**，而且 flag 分支会让代码路径组合爆炸（"关闭时 MiMoCode 与从前完全一致"这句承诺需要持续验证）。

一个半月 12.5k star、**838 个 open issue**——这个 issue 数相对于仓库年龄是偏高的，和快速迭代 + 实验功能多有关。

#### 取舍三：fork 带来的双重成本

继承 OpenCode 的骨架省下了巨量前期工作，但也意味着：
- **上游演进要持续合并**——包名至今还叫 `packages/opencode`、根 `package.json` 的 `name` 还是 `"opencode"`，说明还没有完全脱钩
- **Effect 框架的阅读门槛**是上游带来的，不是小米选的，但读代码的人要一起承担
- 有些遗留（如 `default.old.txt` 这种存档文件）留在树里

### 19.3 它在本系列里的位置

| 维度 | 本系列其他项目 | **MiMo Code** |
|---|---|---|
| **身世** | 多数从零写 | **OpenCode 的深度 fork**（如 Raven 之于 nanobot，但改造幅度大得多） |
| **提示词策略** | 一套通用提示词 | **20 份，一模型家族一份** |
| **工具面** | 全模型同一套 | **GPT 家族换一套 4 件 ABI** |
| **停止判断** | 模型自己说停就停 | **独立裁判模型，四道逃生口** |
| **死循环防护** | 通常 1–2 层 | **步级 3 道 + 回合级 1 道 + 会话级 2 道 + 产物级 1 道** |
| **上下文** | 压缩为主 | **checkpoint 重建 + 可调压缩点（含计费档位）** |
| **编排** | 子 agent | **确定性 JS workflow + Orchestrator 模式（peer child + worktree）** |
| **自我改进** | hermes 的闭环学习 | **dream / distill / evolve + drive-mimo 自举** |
| **许可** | 多为纯 MIT/Apache | **MIT + 独立的 USE_RESTRICTIONS** |

> 🧠 **最后一句**：opencode 回答的是"怎么让 agent 成为可被任何客户端消费的服务"，MiMo Code 在它的骨架上回答了另一个问题——**「模型会骗自己，harness 该怎么办？」**
>
> 它给出的答案不是一个机制，而是**四个层次、七道装置**：步级抓打转（空步检测/重复签名/文本复读），回合级抓预算（步数上限），会话级派独立裁判（Goal 裁判/Try-Best 检测），产物级验快照（checkpoint 验证+重试）。再加上"每个模型的 harness 应该为它单独调形"这个主张，构成了一份很不一样的编程 Agent 设计。
>
> 至于"共进化"这个口号有没有兑现——`evolve` 能改自己、`drive-mimo` 能验证改动、`distill` 能从行为里长出新技能——**基础设施是齐的，剩下的看社区。**

---

<h2 id="ch20">第 20 章 逐层差分：MiMo 到底改了 OpenCode 什么，以及为什么</h2>

> **这一章和前 19 章的关系**：前面讲"MiMo 有什么"，这一章讲"**其中哪些是它自己加的，哪些是继承的，为什么要这么改**"。
>
> **方法**：把 `参考项目/opencode`（commit `62e4641`，完整检出 6 252 文件）和 MiMo（commit `076b790`，Git Tree API 全量 5 222 blob）做**文件级尺寸对比 + 关键文件内容比对**。所有判定都有可复现的证据，不靠印象。

### 20.1 先看总量：1.75 倍，但不是均匀长胖的

`packages/opencode/src/` 下全部 `.ts` / `.tsx`：

| | 体积 | 比值 |
|---|---|---|
| OpenCode `62e4641` | **2 610 KB** | 1.00x |
| MiMo Code `076b790` | **4 572 KB** | **1.75x** |

但**增长完全不均匀**——这才是有信息量的地方：

```mermaid
flowchart LR
    subgraph NEW["🆕 15 个全新子系统（上游完全没有）"]
        N1["workflow/ · actor/ · memory/<br/>cron/ · inbox/ · task/ · team/"]
        N2["history/ · file/ · flag/ · global/<br/>metrics/ · npm/ · pty/ · shell/"]
    end
    subgraph BIG["↑ 大幅扩张"]
        B1["skill/ 2 → 414 文件（207x）"]
        B2["provider/ 5 → 34（6.8x）"]
        B3["storage/ 2 → 11（5.5x）"]
        B4["cli/ 87 → 230（2.6x）"]
        B5["tool/ 43 → 84（2.0x）"]
        B6["permission/ 3 → 6，index.ts 3.4x"]
        B7["session/ 39 → 68（1.7x）"]
    end
    subgraph SHRINK["↓ 收缩"]
        S1["server/ 72 → 43"]
        S2["acp/ 12 → 4"]
    end
    subgraph DEL["❌ 删除"]
        D1["background/ · image/<br/>event-manifest · event-v2-bridge"]
    end
```

**读法**：一个 fork 的"改造重心"就藏在这张图里。MiMo 加的 15 个子系统里，有 7 个（workflow / actor / memory / cron / inbox / task / team）指向同一件事——**让 Agent 能长时间、多线程、跨会话地干活**；而 `server/` 和 `acp/` 收缩说明它**放弃了上游"服务器即本体"的部分野心**，把重心挪回终端。

### 20.2 主循环：`session/prompt.ts` 从 65 KB 涨到 208 KB

这是全仓库最大的单点变化，**3.2 倍**：

| 文件 | OpenCode | MiMo | 变化 |
|---|---|---|---|
| `session/prompt.ts` | 64 925 B | **207 962 B** | **↑ 3.2x** |
| `session/llm.ts` | 15 097 B | 37 119 B | ↑ 2.5x |
| `session/processor.ts` | 26 497 B | 41 504 B | ↑ 1.6x |
| `session/overflow.ts` | 1 313 B | 4 921 B | ↑ 3.7x |

**同时删掉了上游的一整个 `session/llm/` 子目录**（`native-request.ts` 7 953 B、`native-runtime.ts` 8 036 B、`ai-sdk.ts` 9 315 B、`request.ts` 7 597 B、`AGENTS.md` 7 072 B）和 `session/tools.ts`（23 424 B）。

**这说明什么**：上游把 LLM 请求构造、原生运行时、工具装配拆在四五个文件里；MiMo **把它们收拢进 `prompt.ts`，同时往里塞了大量新逻辑**（goalGate、四道闸门、结构化输出、预测下一条消息……）。

> ⚠️ **这是一次"反重构"**：上游在拆，MiMo 在合。合的好处是主循环的控制流一眼看得完（不用在五个文件间跳）；坏处就是[第 19.2 节](#ch19)说的那条最大技术债——**4 595 行单文件**。
>
> 从 fork 的角度看这个选择是可以理解的：**你要在别人的骨架上塞进七八个新机制，最快的路径是集中改一个文件，而不是先重构上游的分层。**代价是这笔债只会越滚越大。

### 20.3 提示词：8 → 20 份，但有 5 份一个字节没动

这是本章最需要澄清归属的一处。

**上游已有的**（`opencode/packages/opencode/src/session/prompt/`）：`anthropic.txt` · `beast.txt` · `build-switch.txt` · `codex.txt` · `copilot-gpt-5.txt` · `default.txt` · `gemini.txt` · `gpt.txt` · `kimi.txt` · `meta.txt` · `plan.txt` · `plan-mode.txt` · `plan-reminder-anthropic.txt` · `trinity.txt`

**上游的路由函数**（`session/system.ts:27-42`）——结构和 MiMo 一模一样：

```ts
export function provider(model: Provider.Model) {
  if (model.api.id.includes("muse-spark")) return [PROMPT_META]
  if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3"))
    return [PROMPT_BEAST]
  if (model.api.id.includes("gpt")) {
    if (model.api.id.includes("codex")) return [PROMPT_CODEX]
    return [PROMPT_GPT]
  }
  if (model.api.id.includes("gemini-")) return [PROMPT_GEMINI]
  if (model.api.id.includes("claude")) return [PROMPT_ANTHROPIC]
  if (model.api.id.toLowerCase().includes("trinity")) return [PROMPT_TRINITY]
  if (model.api.id.toLowerCase().includes("kimi")) return [PROMPT_KIMI]
  return [PROMPT_DEFAULT]
}
```

**逐份对比**：

| 提示词 | OpenCode | MiMo | 判定 |
|---|---|---|---|
| `gemini.txt` | 15 372 | 15 372 | **字节级未改** |
| `kimi.txt` | 8 695 | 8 695 | **字节级未改** |
| `codex.txt` | 7 390 | 7 390 | **字节级未改** |
| `trinity.txt` | 7 748 | 7 749 | 差 1 字节 |
| `copilot-gpt-5.txt` | 14 241 | 14 239 | 差 2 字节 |
| `beast.txt` | 11 080 | 11 970 | ↑ 1.08x |
| **`anthropic.txt`** | 8 212 | **14 281** | **↑ 1.7x** |
| **`default.txt`** | 8 528 | **20 800** | **↑ 2.4x** |
| **`gpt.txt`** | 9 284 | **25 447** | **↑ 2.7x** |
| `deepseek.txt` · `glm.txt` · `minimax.txt` | — | 10 530 / 4 890 / 10 908 | 🆕 |
| `orchestrator.txt` · `compose.txt` | — | 21 002 / 6 036 | 🆕 |
| `default.old.txt` | — | 14 195 | 🆕（旧版存档） |
| `meta.txt`（muse-spark） | 9 151 | — | ❌ 删除 |
| `plan-mode.txt` · `plan-reminder-anthropic.txt` · `plan.txt` | 4 547 / 4 056 / 1 484 | — | ❌ 删除 |

**结论很清楚**：

| | 谁的 |
|---|---|
| **分家族路由这个模式** | **OpenCode** |
| 5 份提示词的内容 | **OpenCode**（一字未改） |
| 加 5 个家族（deepseek/glm/minimax/orchestrator/compose） | **MiMo** |
| **重写最常用的 3 份**（gpt/default/anthropic） | **MiMo** |
| **双 ID 兜底** | **MiMo** |
| 三个循环检测器放进 `prompt/` 目录 | **MiMo** |

**为什么只重写那 3 份？** 因为 `gpt.txt` / `default.txt` / `anthropic.txt` 覆盖了绝大多数实际用量——GPT 系、Claude 系、以及所有没匹配上的模型。**小米把力气花在了命中率最高的三条路径上，边缘家族原样继承。**这是很务实的取舍。

**双 ID 兜底为什么重要**（`session/system.ts:39`）：

```ts
return [prompt(model.id) ?? prompt(model.api.id) ?? PROMPT_DEFAULT]
```

上游只看 `model.api.id`。但同一个模型经不同 provider 暴露时 ID 不同——OpenRouter 上的 `xiaomi/mimo-v2.5`、自建网关上的 `internal/xiaomi/mimo-v2.5`。**MiMo 要支持的模型接入路径比上游多得多**（MiMo Auto / Xiaomi 平台 OAuth / Codex OAuth / 从 Claude Code 导入 / 任意 OpenAI 兼容端点），所以必须两个 ID 都试。

### 20.4 GPT 工具门控：同一行表达式，作用域扩了一个量级

**上游 `tool/registry.ts:292-296`**：

```ts
const usePatch =
  input.modelID.includes("gpt-") && !input.modelID.includes("oss") && !input.modelID.includes("gpt-4")
if (tool.id === ApplyPatchTool.id) return usePatch
if (tool.id === EditTool.id || tool.id === WriteTool.id) return !usePatch
```

**MiMo `tool/gpt.ts:11-13`**：

```ts
export function usesGPTToolset(modelID: string) {
  return modelID.includes("gpt-") && !modelID.includes("oss") && !modelID.includes("gpt-4")
}
```

**判定逻辑一字不差。**差别在两处：

| | OpenCode | MiMo |
|---|---|---|
| **代码形态** | registry 内联的局部变量 `usePatch` | 抽成**独立文件的命名导出** `usesGPTToolset()` |
| **作用域** | 只管 `apply_patch` ↔ `edit`/`write` **二选一** | 再门控 `exec`、`view_image`；**额外隐藏** read/grep/glob/multiedit/notebook_edit |

**为什么要抽出来**：上游那个变量叫 `usePatch`——名字说明它只关心"用不用 patch 工具"。MiMo 要在**多处**用同一个判定（工具装配、子 agent 提示词拼接 `system.ts:44-48`），内联变量就不够了，必须提取成有名字的概念。

> ⚠️ **一处容易混淆的细节**：`tool/gpt.ts` 里还有一个 `isGPTModel`（第 1-5 行），被 `isMcpToolSearchEnabled`（第 7-9 行）调用。它与 `usesGPTToolset` **不是同一个函数**——`isGPTModel` 仅排除 `gpt-oss`，对 `gpt-4` 系模型返回 true；`usesGPTToolset` 同时排除 `oss` **和** `gpt-4`，对 `gpt-4` 系返回 false。两者的行为在 gpt-4 系模型上分叉——MCP 工具搜索对 gpt-4 系开启，但 GPT 专用工具箱对 gpt-4 系关闭。

**从"换一把螺丝刀"到"换一整个工具箱"**——这个作用域扩张才是 MiMo 的实质贡献，而不是那行表达式本身。

### 20.5 沙箱：从 3 465 行手写解释器换成 QuickJS

这是**技术含量最高的一次替换**，也是最容易被"MiMo 有 exec 工具"这句话掩盖掉的。

| | 上游 `@opencode-ai/codemode` | MiMo `workflow/sandbox.ts` + `tool/tool-script.ts` |
|---|---|---|
| 工具名 | `execute` | `exec` |
| 入口文件 | `tool/code-mode.ts`（11 808 B）→ 删除 | `tool/tool-script.ts`（27 188 B）🆕 |
| **隔离实现** | **acorn + 手写 AST 解释器**<br/>`interpreter/runtime.ts` **3 465 行** | **QuickJS-emscripten**（WASM JS 引擎）<br/>`workflow/sandbox.ts` 15 878 B |
| 包依赖 | `acorn` 8.15.0 + `typescript`（转译） | `quickjs-emscripten` |
| **暴露什么** | **只有 MCP 工具**（"access to connected **MCP** tools"） | **宿主工具**（late-bound registry，与外层同一份已过滤表） |
| 控制流工具 | — | **显式排除** task/actor/question/skill/workflow/cron/session |
| 资源限制 | 有执行限制（`ResolvedExecutionLimits`） | 嵌套 50/500 · 并发 8 · **活跃计算 60s/600s** · wall 30min · 内存 64 MiB |
| 已知硬约束 | — | **句柄必须在 dispose 前全部释放**，否则**进程 abort**（含未结算 deferred） |

**为什么值得换（三个理由，按重要性排）**：

**① 手写 JS 解释器是个无底洞。** 3 465 行只是起点——JS 的语义面（原型链、闭包、生成器、Proxy、getter/setter、异常语义、`this` 绑定……）每补一块都要写代码，**每一处没实现到的地方是行为差异，每一处实现错的地方可能是逃逸口**。换成真引擎后，这部分风险由 WASM 边界一次性兜住。

**② 代价从"语义正确性"转成"资源管理"。** QuickJS 的坑很硬（句柄不释放直接 abort 进程），但它是**有确定检查清单的**——`sandbox.ts:100-105` 那三条 2026-06-01 技术验证结论就是这份清单。**语义正确性没有清单。**

**③ 暴露面变了，风险等级跟着变。** 上游只暴露 MCP 工具——那些工具本来就在权限体系外围。MiMo 要暴露**宿主工具**（bash / edit / read……），**权限风险直接抬高一级**。所以才有了 late-bound registry（拿和外层完全相同的已过滤表）、控制流工具排除、"QuickJS 只隔离脚本，`bash` 仍是真实 Shell"这句诚实声明——**这一整套配套设计，是暴露面扩张倒逼出来的。**

> 🧠 **通则**：当你把一个沙箱的暴露面从"外围能力"扩到"核心能力"时，**隔离手段和权限传递机制都要重新设计**，不能只是"多注册几个函数"。

### 20.6 Shell 执行：整个重写

| OpenCode | MiMo |
|---|---|
| `tool/shell.ts` 20 439 B ❌ | `tool/bash.ts` **29 711 B** 🆕 |
| `tool/shell/prompt.ts` 16 779 B ❌ | `tool/bash.txt` 9 861 B · `tool/bash.gpt.txt` 5 299 B 🆕 |
| — | `tool/shell-tokenize.ts` 11 389 B 🆕 |
| — | `tool/shell-wrap.ts` 11 001 B 🆕 |
| — | `tool/bash-interactive.ts` 4 914 B 🆕 |
| — | `tool/bash_token_efficient_pipeline.ts` 7 295 B 🆕 |
| — | `tool/bash_token_efficient_heuristic.ts` 17 913 B 🆕 |

**37 KB 换成 87 KB。**新增的五个文件各有明确分工：分词（`shell-tokenize`）、包装（`shell-wrap`）、交互式（`bash-interactive`）、输出清洗管线与启发式（[第 18 章](#ch18)的 Token Efficient）。

**注意 `bash.txt` 和 `bash.gpt.txt` 是两份**——连 bash 工具的**描述文本**都按模型家族分开了。这是[第 20.4 节](#ch20)那条"按模型调形"主张贯彻到工具描述层的证据。

### 20.7 权限：3.4 倍，且加了上游没有的"不可协商线"

| 文件 | OpenCode | MiMo |
|---|---|---|
| `permission/index.ts` | 7 861 B | **26 415 B（↑3.4x）** |
| `permission/arity.ts` | 6 376 B | 6 376 B（**字节级未改**） |
| `permission/permission-forward-ref.ts` | — | 7 536 B 🆕 |
| `permission/evaluate.ts` · `schema.ts` · `permission.sql.ts` | — | 🆕 |

**`FORCED_ASK` 在上游完全不存在**（`grep -rn "FORCED_ASK" opencode/packages/opencode/src/permission/` 无结果）。

新增的 `permission-forward-ref.ts` 对应[第 16 章](#ch16)的 Orchestrator **后台审批转发**——上游没有 Orchestrator，自然也不需要"把后台会话的权限请求转发给人"。

> **因果链很清楚**：MiMo 加了 Orchestrator（后台跑的 peer child）→ 后台会话碰到权限询问会被直接拒绝 → 需要转发机制 → `permission-forward-ref.ts` + `decideAskRouting` 四分法（自动拒绝 / 转发给主会话 / 继承父会话已有授权 / 直接放行）。**新功能倒逼权限系统扩张，这是 3.4 倍的主要来源。**

### 20.8 技能：2 → 414 个文件

| | OpenCode | MiMo |
|---|---|---|
| `skill/` 文件数 | **2**（`discovery.ts` + `index.ts`） | **414** |
| 内置技能包 | 无 | **26 个**（`skill/builtin/.bundle/`） |

上游只有技能的**发现机制**，不带任何内置技能。MiMo 塞进了 26 个完整技能包（含脚本、模板、数据集——最大的单文件是 `data-analytics` 里 78 KB 的 Python 脚本）。

**这是产品定位的差异**：上游 OpenCode 定位是"平台"，内容留给用户；MiMo 定位是"开箱即用的产品"，所以要自带内容库。**和 Open Design 塞进 151 个设计系统包是同一个逻辑**——[第 19.2 节](#ch19)说的"内容库即维护负担"这条取舍，两家都得付。

### 20.9 收缩的地方也有信息量

| 目录 | OpenCode | MiMo | 说明 |
|---|---|---|---|
| `server/` | 72 文件 | **43** | 上游"服务器即本体"，server 层极重；MiMo 重心回到终端 |
| `acp/` | 12 文件 | **4** | ACP（Agent Client Protocol）在上游是一等公民；MiMo 保留但不再深耕 |
| `background/` | 1 | ❌ 删除 | 被 `actor/` + `cron/` + `inbox/` 取代 |
| `image/` | 1 | ❌ 删除 | 换成 `tool/view-image.ts` |

**读法**：本系列分析 opencode 时给它的定位是「**服务器即本体**」——TUI 和同进程服务器也走完整 HTTP/RPC。MiMo 把 `server/` 砍掉四成、`acp/` 砍掉三分之二，说明**它不想做平台，它想做一个好用的终端产品**。

这也解释了 `cli/` 为什么反向扩张 2.6 倍（87 → 230 文件，含 10 种语言 i18n）：**砍掉的服务器野心，加倍投在了终端体验上。**

### 20.10 把 15 个新子系统按"为什么加"归类

```mermaid
flowchart TB
    subgraph G1["动机 A：让 Agent 能长时间干活"]
        A1["memory/ —— 跨会话记忆 FTS5"]
        A2["session/checkpoint*.ts —— 上下文重建（74 KB）"]
        A3["history/ —— 历史回捞"]
        A4["task/ —— 树形任务与进度"]
    end
    subgraph G2["动机 B：让 Agent 能多线程干活"]
        B1["actor/ —— 子 agent 派发与生命周期"]
        B2["workflow/ —— 确定性 JS 编排"]
        B3["inbox/ —— 完成通知唤醒"]
        B4["team/ · worktree —— 多会话隔离"]
    end
    subgraph G3["动机 C：让 Agent 能无人值守"]
        C1["cron/ —— 定时调度 + 分布式锁 + 哨兵"]
        C2["flag/ —— 实验功能门控"]
        C3["metrics/ —— 可观测"]
    end
    subgraph G4["动机 D：基础设施补齐"]
        D1["file/ · global/ · npm/ · pty/ · shell/"]
    end
```

**A + B + C 是同一个产品判断的三个面**：上游 OpenCode 面向"一次对话解决一个问题"；MiMo 面向"**长周期、可并行、能自己跑**"。README 里那句「long-horizon」（官网博客标题就是 `mimo-code-long-horizon`）不是营销，是这 12 个子系统的共同注脚。

### 20.11 一张总表：谁的功劳

把[第 3 章](#ch3)那份 15 条增量清单，按**真实归属**重新标一遍：

| 能力 | 归属 | 依据 |
|---|---|---|
| **Goal 独立裁判** | 🟢 **MiMo 首创** | `session/goal.ts` 上游不存在 |
| **四道死循环闸门** | 🟢 **MiMo 首创** | 三个检测器文件上游全不存在 |
| **Try-Best 检测 + checkpoint 验证** | 🟢 **MiMo 首创** | 上游无 checkpoint 概念 |
| **Checkpoint 上下文重建** | 🟢 **MiMo 首创** | `checkpoint*.ts` 全家族 74 KB+ 上游不存在 |
| **FTS5 记忆 + BM25 相对地板** | 🟢 **MiMo 首创** | `memory/` 上游不存在 |
| **可调压缩点 `/context-limit`** | 🟢 **MiMo 首创** | 上游 `compaction.ts` 无此配置 |
| **FORCED_ASK** | 🟢 **MiMo 首创** | 上游 permission 无此常量 |
| **Workflow 运行时 + 4 个内置流水线** | 🟢 **MiMo 首创** | `workflow/` 上游不存在 |
| **Orchestrator 模式 + 后台审批转发** | 🟢 **MiMo 首创** | `tool/session.ts` · `permission-forward-ref.ts` 上游不存在 |
| **Dream / Distill / Evolve** | 🟢 **MiMo 首创** | 对应 agent 与提示词上游不存在 |
| **Token Efficient 管线** | 🟢 **MiMo 首创** | `bash_token_efficient_*` 上游不存在 |
| **cron / inbox / task / team / history** | 🟢 **MiMo 首创** | 五个目录上游全不存在 |
| **26 个内置技能** | 🟢 **MiMo 首创** | 上游 `skill/` 只有 2 个文件 |
| **分家族提示词路由** | 🟡 **继承 + 扩张** | 上游 8 份 + 同结构 `if` 链；MiMo 加 5 份、重写 3 份、加双 ID 兜底 |
| **GPT 工具门控** | 🟡 **继承 + 扩张** | 判定表达式一字不差；MiMo 抽成函数并把作用域扩成整套 ABI |
| **沙箱脚本编排** | 🟡 **继承 + 重写** | 想法来自 `code-mode`；MiMo 换引擎（acorn→QuickJS）、换暴露面（MCP→宿主工具） |
| **`apply_patch` 工具** | 🔵 **上游已有** | `tool/apply_patch.ts` 上游 11 019 B，MiMo 11 226 B（≈未改） |
| **Effect 服务层 / SQLite / provider 抽象 / TUI 栈 / ACP** | 🔵 **上游已有** | 全盘继承 |

**统计**：13 项首创 · 3 项继承后大幅改造 · 大量基础设施直接继承。

> 🧠 **一句话**：MiMo 对 OpenCode 的改造，**不是把它变好用一点，而是换了一个产品命题**。上游问的是"怎么让 agent 成为可被任何客户端消费的服务"；MiMo 问的是"**怎么让 agent 能一个人干几个小时的活而不跑偏**"。13 项首创里有 6 项在防自欺、7 项在支撑长周期与并行——这两组加起来，就是那个新命题的全部答案。

### 20.12 这次 fork 给同类项目的三条经验

**① 先想清楚你 fork 的是"骨架"还是"产品"。** MiMo 继承的是 Effect 服务层、持久化、provider 抽象、TUI 栈——**全是骨架**。凡是体现产品判断的地方（提示词内容、工具面、权限线、内容库）它都重做了。**骨架可以借，产品判断不能借。**

**② 改造重心暴露产品定位。** 看一眼哪些目录膨胀、哪些收缩，就知道这个 fork 想变成什么。`server/` 砍四成 + `cli/` 涨 2.6 倍 = "不做平台，做终端产品"；加 12 个长周期/并行子系统 = "从一次对话变成一个工期"。

**③ 集中改一个文件是 fork 的合理战术，但要记账。** `prompt.ts` 涨到 4 595 行是有原因的——**在别人的骨架上塞新机制，集中改一处比先重构上游分层快得多**。但这笔债会滚，而且 fork 越久越难还（上游还在动）。MiMo 在注释里留了很多 `TODO: lift to mimocode.json config`，说明团队自己知道。


---

## 附录 A · 源码导览索引

> 路径均相对 `参考项目/MiMo-Code/packages/opencode/src/`（除非另注）。

| 想看什么 | 去哪个文件 |
|---|---|
| 主循环（含 goalGate、各闸门） | `session/prompt.ts`（4 595 行） |
| Goal 裁判 | `session/goal.ts`（233 行，`JUDGE_SYSTEM` 在 `:64-73`） |
| goalGate 四道逃生口 | `session/prompt.ts:2505-2600` |
| 阈值常量与签名算法 | `session/prompt.ts:169-215` |
| 高压提示去抖 | `session/prompt.ts:216-243` |
| 空参数工具调用检测 | `session/prompt/empty-step-detection.ts` |
| 文本复读检测 | `session/prompt/text-loop-recovery.ts` |
| n-gram 检测 | `session/prompt/text-ngram-detection.ts` |
| 步数上限提示词 | `session/prompt/max-steps.txt` |
| Try-Best 检测器 | `session/try-best-detector.ts` |
| Checkpoint 验证 / 重试 | `session/checkpoint-validator.ts` · `session/checkpoint-retry.ts` |
| 提示词路由（17 行决定一切） | `session/system.ts:26-39` |
| 环境块与缓存前缀 | `session/system.ts:66-90` |
| 无视觉能力降级 | `session/system.ts:105-118` |
| 20 份分家族提示词 | `session/prompt/*.txt` |
| Checkpoint 系统 | `session/checkpoint.ts`（1 648 行） |
| 超长消息截断 + 代理对修复 | `session/checkpoint.ts:52-69` |
| 记忆检索与 BM25 相对地板 | `memory/service.ts:71-129` |
| FTS 表结构 | `memory/fts.sql.ts` |
| 记忆重建 | `memory/reconcile.ts` · `memory/fts-query.ts` |
| GPT 工具集判定 | `tool/gpt.ts`（13 行） |
| 工具装配 | `tool/registry.ts:375-410` |
| `exec` 工具 | `tool/tool-script.ts:303` · `tool/tool-script-ref.ts` |
| QuickJS 沙箱 | `workflow/sandbox.ts:95-160` |
| 内置 workflow 注册（null 原型 + 启动即失败） | `workflow/builtin.ts` |
| Workflow 运行时 | `workflow/runtime.ts`（1 607 行） |
| FORCED_ASK | `permission/index.ts:188-195` |
| 工具组别名与 findLast | `permission/index.ts:591-614` |
| Orchestrator 的 session 工具 | `tool/session.ts`（53 KB，`KNOWN_VERBS`） |
| 后台审批路由 | `agent/config.ts` 的 `decideAskRouting` |
| 子 agent 派发 | `actor/spawn.ts`（1 010 行） |
| cron 调度 | `cron/scheduler.ts` · `cron-lock.ts` · `sentinel.ts` |
| inbox | `inbox/inbox.ts` |
| 子 agent 提示词 | `agent/prompt/{checkpoint-writer,dream,distill,gpt-tools,compaction,explore}.txt` |

## 附录 B · 关键文档索引

| 文档 | 为什么值得读 |
|---|---|
| `docs/architecture/codex-microkernel-runtime.md` | GPT 工具 ABI + `exec` 微内核完整说明，**含自我承认的未修复缺陷**（5 语言版本） |
| `docs/harness/MiMo Orchestrator Mode.md`（13 KB） | 一个窗口管所有任务的完整设计，含后台审批路由三分法 |
| `docs/harness/MiMo Token Efficient Mode.md`（9 KB） | 五层清理管线 + never-worse 守门 + 14 条正则速查 |
| `docs/harness/Agent Multi-Skill Workflow Orchestration Design.md` | 多技能自动编排 |
| `docs/harness/Mix of Harness and Hand-off.md` | harness 与交接的混合模式 |
| `docs/compose/spec/` · `plans/` · `reports/` | **自举证据**：这个项目自己用 compose 流程开发，spec/计划/报告都在树里 |
| `README.md`（含 `README.zh.md`） | 功能全表 + 配置 + 每个 flag 的作用 |
| `USE_RESTRICTIONS.md` | MIT 之外的使用限制，含"不许无监督自主执行高风险动作" |
| `AGENTS.md` · `CLAUDE.md` | 仓库自身给 Agent 的说明 |

---

*本文基于 `XiaomiMiMo/MiMo-Code` commit `076b790`（2026-07-27）核对写成。所有 `文件:行号` 引用可回 `参考项目/MiMo-Code/` 验证；统计数字（文件数、字节数、各目录条目数）由 GitHub Git Tree API 全量遍历计算（**5 222 条 blob，`truncated: false`**）。*
