# MiMo Code source analysis: on OpenCode’s skeleton, Xiaomi welded a full kit of “do not let the agent lie to itself” devices

> **Subject**: MiMo Code ([`XiaomiMiMo/MiMo-Code`](https://github.com/XiaomiMiMo/MiMo-Code), product name **MiMoCode**), against **commit `076b790`** (`076b790d9f192adbc92054c4d4437c13cb6993a8`, 2026-07-27 14:06 UTC, `main`, merge PR #1938 `checkpoint-writer-wait-timeout`).
> **Code scale**: repo **5 222 files / 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; the core `packages/opencode` is **1 677 files / 17.9 MB**.
> **Community scale**: **12.5k stars / 1.27k forks / 838 open issues**, repo created 2026-06-10—**a month and a half**.
> **Distribution**: npm package `@mimo-ai/cli` **v0.1.9**, binary command `mimo` (`packages/opencode/package.json:3-4,24-26`).
> **License**: **MIT**, but **there is also a `USE_RESTRICTIONS.md`** — it forbids military use, malicious network activity, unauthorized data collection, and “**autonomously executing high-risk actions without appropriate human oversight or authorization**.” Read both documents before using it.
> **One-sentence position**: Xiaomi’s MiMo team’s open-source terminal coding agent, slogan **"Where Models and Agents Co-Evolve"**. It was **not written from scratch**—the core package is still named `packages/opencode`, the root `package.json` `name` is still `"opencode"`, and `LICENSE` carries **Copyright 2026 MiMo Code, Xiaomi** alongside **Copyright 2025 opencode**. **It is a deep fork of OpenCode.** What is worth reading is not what it inherited, but **what Xiaomi welded on top of upstream**.
> **Audience**: readers of this series’ [opencode source analysis](./opencode.md) gain the most (the delta is immediately visible); not having read it does not block you. Every citation is `file:line`, relative to `参考项目/MiMo-Code/`, and can be checked at this commit; **uncertain places are marked; line numbers are not invented**.

---

## Contents

**Part I · What it is**
1. [Project overview: a fork’s value is what it welded on](#ch1)
2. [Panorama: Effect service layer + single-process TUI](#ch2)
3. [Delta versus upstream OpenCode: one increment roster](#ch3)

**Part II · The main loop and “do not let the agent lie to itself”**
4. [The heartland: Goal stop condition and an independent judge model](#ch4)
5. [Four doom-loop gates: empty-step / repeated-step / text-loop / max-steps](#ch5)
6. [Agent modes and sub-agents: three identities of 13 built-in agents](#ch6)
7. [The Try-Best detector and artifact-level verification](#ch7)

**Part III · Context engineering**
8. [One model, one prompt: 20 prompt files and the routing rules](#ch8)
9. [Checkpoint: treating context as a reconstructable snapshot](#ch9)
10. [Memory: SQLite FTS5 + a BM25 relative floor](#ch10)
11. [A tunable compaction point: `/context-limit` and cost tiers](#ch11)

**Part IV · Tools and permissions**
12. [The GPT microkernel: a smaller tool ABI for Codex](#ch12)
13. [`exec` and QuickJS: compose tools without amplifying permission](#ch13)
14. [The permission system: FORCED_ASK and the line a wildcard cannot crush](#ch14)

**Part V · Orchestration and ecosystem**
15. [Workflow: deterministic JS scripts orchestrating multiple agents](#ch15)
16. [Orchestrator mode: one window managing every task](#ch16)
17. [Dream / Distill / Evolve: letting the agent rewrite itself](#ch17)
18. [Token Efficient mode: washing the noise out of bash output](#ch18)
19. [Wrap-up: three most distinctive designs and three trade-offs](#ch19)

**Part VI · Layer-by-layer delta versus upstream**
20. [Layer-by-layer delta: what MiMo actually changed in OpenCode, and why](#ch20)

---

<h2 id="ch1">Chapter 1 · Project overview: a fork’s value is what it welded on</h2>

### 1.1 First, say the lineage out loud

The first thing you should notice when you open the repo: the only core package is named **`packages/opencode`**, and the root `package.json` `name` field still says `"opencode"`.

That is not casual naming. MiMo Code is a **deep fork of [OpenCode](./opencode.md)**—the subject of the second analysis in this series (the Anomaly team, TypeScript on Bun + the Effect framework, “the server is the ontology”). Upstream’s Effect service layer, SolidJS TUI, SQLite persistence, and provider abstraction, MiMo inherited wholesale. The two copyright lines sitting side by side in `LICENSE` write this down in so many words.

That creates a fork in reading strategy:

| Reading | Result |
|---|---|
| ❌ Treat it as a brand-new project and start from zero | You will spend 80% of your time rereading the OpenCode skeleton already analyzed |
| ✅ **Read the delta** | Look directly at what Xiaomi welded on—that is this repo’s information content |

This lecture takes the second road.

> 🧠 **Precedent in this series**: `Raven`’s Python runtime is a fork of `nanobot`; our reading then was “look at the later modifications against upstream.” MiMo × OpenCode is the same pattern, only **the modification amplitude is far larger**—large enough to deserve a separate 18-chapter analysis.

### 1.2 The product claim behind the slogan

The first line of the README: **"MiMo Code: Where Models and Agents Co-Evolve"** (models and agents co-evolve).

That sentence is not marketing copy; it corresponds to two concrete engineering facts:

1. **One model, one prompt** ([Chapter 8](#ch8)): under `session/prompt/` there are **20** system prompts, one per model family, routed by model ID.
2. **One model, one tool ABI** ([Chapter 12](#ch12)): the tool set the GPT/Codex family sees and the one the Claude family sees are **not the same**—the former has only four pieces, `bash` / `apply_patch` / `view_image` / `exec`; the latter is the full read/write/edit/grep/glob kit.

**The real meaning of “co-evolve”: the agent’s harness is reshaped per model, rather than giving every model one generic shell.**

> ⚠️ **One attribution question that must be cleared first: the “embryos” of both of these come from upstream OpenCode; they are not Xiaomi first-party inventions.** Upstream already had 10 family-split prompts and the same `if`-chain routing; the GPT tool-gating predicate expression is **byte-for-byte the same line** upstream. What Xiaomi did is **walk further down that road**—add 5 families, rewrite the 3 most-used files, and expand the gate from “a two-way swap” into “a wholesale tool-surface replacement.” [Chapter 20](#ch20) has an item-by-item attribution check based on a **real file-level diff**; wherever this lecture talks about “whose innovation,” that chapter is the authority.

### 1.3 The project’s digitized shape

```
MiMo-Code/  (commit 076b790 · MIT + USE_RESTRICTIONS · 5 222 files · 83.8 MB · 12.5k★)
├── packages/            17 workspace packages
│   ├── opencode/        1 677 files / 17.9 MB   ← core: CLI + Session engine + tools + TUI
│   │   └── src/
│   │       ├── cli/         230 files / 3.0 MB  ← TUI (SolidJS/OpenTUI) + subcommands, 10-language i18n
│   │       ├── skill/       414 files / 2.3 MB  ← 26 built-in skill packs (including scripts/templates/datasets)
│   │       ├── session/      68 files / 881 KB  ← main loop, checkpoint, compaction, goal
│   │       ├── tool/         84 files / 525 KB  ← 51 tool TS files
│   │       ├── provider/     34 files / 306 KB
│   │       ├── workflow/     14 files / 217 KB  ← deterministic JS orchestration
│   │       ├── server/       43 files / 210 KB
│   │       ├── plugin/ config/ actor/ lsp/ agent/ acp/ mcp/ cron/ permission/ inbox/ task/ …
│   ├── ui/              1 558 files            ← component library
│   ├── web/               717 files / 12.1 MB  ← site + docs (Astro/MDX)
│   ├── console/           481 files / 32.9 MB
│   ├── desktop/           217 files / 8.3 MB
│   ├── app/ sdk/ enterprise/ shared/ storybook/ plugin/ slack/ identity/ containers/ …
├── docs/
│   ├── architecture/    codex-microkernel-runtime.md (including en/fr/ja/ru, 5 languages total)
│   ├── harness/         Orchestrator Mode · Token Efficient Mode
│   │                    · Agent Multi-Skill Workflow Orchestration · Mix of Harness and Hand-off
│   └── compose/         spec/ plans/ reports/ —— self-bootstrapping spec-driven development records
├── .mimocode/           the project’s own config samples (skills/effect, themes, plugins)
├── sdks/ script/ infra/ nix/ patches/
```

**The largest source files** (under `packages/opencode/src/`, tests excluded):

| Bytes | File | What it is |
|---|---|---|
| **207 962** | `session/prompt.ts` | **Main loop** (4 595 lines)—the agent’s heart |
| 128 317 | `cli/cmd/tui/routes/session/index.tsx` | TUI session main view |
| **84 552** | `workflow/runtime.ts` | Deterministic workflow runtime (1 607 lines) |
| **74 327** | `session/checkpoint.ts` | Checkpoint system (1 648 lines) |
| 73 346 | `cli/cmd/tui/component/prompt/index.tsx` | Input-box component |
| 71 103 | `provider/transform.ts` | Provider request transform (1 815 lines) |
| 70 654 | `provider/provider.ts` | Provider registration and resolution |
| 58 171 | `acp/agent.ts` | ACP protocol implementation |
| 53 710 | `tool/session.ts` | Orchestrator’s `session` tool |
| 48 853 | `actor/spawn.ts` | Sub-agent dispatch (1 010 lines) |

> ⚠️ **Notice one contrast**: `session/prompt.ts` is a single 208 KB file, larger than Open Design’s entire prompt assembler (125 KB)—but it is not just prompts; it is **main loop + prompt assembly + various gates** mashed together. This is the repo’s largest maintainability hazard (unpacked in [Chapter 19](#ch19)).

### 1.4 The two layers of the license

`LICENSE` is MIT, but the repo root also has a **`USE_RESTRICTIONS.md`**. Among the forbidden uses it lists, one is directly related to this lecture’s theme:

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

**A project that made “prevent the agent from deceiving itself” a core design also wrote “do not autonomously execute high-risk actions without supervision” into the license terms**—those two things are consistent.

---

<h2 id="ch2">Chapter 2 · Panorama: Effect service layer + single-process TUI</h2>

### 2.1 The skeleton inherited from 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
```

**Inherited from upstream**: the Effect framework’s `Context.Service` / `Layer` dependency injection, SQLite + Drizzle persistence, provider abstraction, TUI stack, server mode (`mimo serve`), ACP protocol.

**Xiaomi-added** (listed item by item in the next chapter): Goal judge, Checkpoint, FTS5 memory, 20 family-split prompts, GPT microkernel, Workflow runtime, Orchestrator mode, Dream/Distill, Token Efficient pipeline, cron scheduling, inbox.

### 2.2 How the Effect service layer is organized

Every subsystem is a `Context.Service` + `Layer`, assembled by dependency injection. Take Goal as the example (`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),
)
```

The payoff for tests and substitution is obvious (swap one `Provider.layer` and the whole dependency tree is replaced); the cost is a **steep reading barrier**—the screen is full of `yield*`, and someone who does not know Effect will suffer on the first pass. That is upstream OpenCode’s choice; MiMo inherited it wholesale.

> 📌 **State lifetime**: Goal’s state lives in `InstanceState` (one per project instance), indexed by `sessionID`, and is cleared when the instance is destroyed (comment at `goal.ts:23-24`). This is the standard place for “session-scoped volatile state,” in contrast with checkpoints that are persisted.

---

<h2 id="ch3">Chapter 3 · Delta versus upstream OpenCode: one increment roster</h2>

This chapter is the map of the whole lecture. **Reading MiMo Code = reading this table.**

| # | Xiaomi-added | One sentence | Chapter |
|---|---|---|---|
| 1 | **Goal stop condition + independent judge** | When the agent wants to stop, another model reads the transcript and decides whether it actually finished | [Chapter 4](#ch4) |
| 2 | **Four doom-loop gates** | Empty-argument tool calls / repeated action signatures / text replay / step cap, each with a soft and a hard grade | [Chapter 5](#ch5) |
| 3 | **Try-Best detector + artifact verification** | Catch “I did my best” fake completion; a checkpoint must be validated after write, and rewritten if it fails | [Chapter 7](#ch7) |
| 4 | **Prompt families 8 → 20 files** | The routing mechanism is **inherited from upstream**; Xiaomi added 5 families, **rewrote the 3 most-used files** (gpt 2.7x / default 2.4x / anthropic 1.7x), and added dual-ID fallback | [Chapter 8](#ch8) · [§20.3](#ch20) |
| 5 | **Checkpoint system** | When context is about to top out, **rebuild** from snapshot + memory + task progress, rather than simply compacting | [Chapter 9](#ch9) |
| 6 | **FTS5 memory + BM25 relative floor** | SQLite full-text search, filtering common-word noise by relative score | [Chapter 10](#ch10) |
| 7 | **Tunable compaction point** | `/context-limit` lets the model compact earlier than its own window (save money / raise quality) | [Chapter 11](#ch11) |
| 8 | **GPT tool gating: two-way swap → wholesale replacement** | The predicate expression is **byte-for-byte identical to upstream**; Xiaomi extracted it as `usesGPTToolset()` and expanded the scope from “`apply_patch` ↔ `edit`/`write` two-way swap” into hiding read/grep/glob/multiedit/notebook_edit as a **wholesale ABI replacement** | [Chapter 12](#ch12) · [§20.4](#ch20) |
| 9 | **Sandbox: acorn hand-written interpreter → QuickJS** | Upstream `code-mode` is a **3 465-line hand-written AST interpreter**, exposing only MCP tools; Xiaomi swapped in **QuickJS WASM**, and instead exposes **host tools** via a late-bound registry | [Chapter 13](#ch13) · [§20.5](#ch20) |
| 10 | **FORCED_ASK** | A wildcard allow cannot crush delete-class operations | [Chapter 14](#ch14) |
| 11 | **Workflow runtime** | Deterministic JS scripts orchestrating multiple agents, 4 built-in pipelines | [Chapter 15](#ch15) |
| 12 | **Orchestrator mode** | One window, one session managing every task; child sessions run in independent worktrees | [Chapter 16](#ch16) |
| 13 | **Dream / Distill / Evolve** | Distill knowledge, distill skills, rewrite itself | [Chapter 17](#ch17) |
| 14 | **Token Efficient pipeline** | Wash ANSI / progress bars / secrets / over-long lines out of bash output | [Chapter 18](#ch18) |
| 15 | 26 built-in skills · cron scheduling · inbox · voice input · task tree | Ecosystem surface | Scattered |

**Shared theme**: of these 15 items, **6** (1, 2, 3, 5, 10, 13) all solve the same class of problem—**the agent will lie to itself**. It will say “done” when it is not done, will repeat the same action thinking it is making progress, will call the same tool over and over with empty arguments, will treat a wildcard grant as license to delete a file.

> 🧠 **One sentence**: if opencode’s theme is “the protocol is the product,” hermes is “closed-loop learning,” and CodeWhale is “safety as mechanism,” then **MiMo Code’s theme is “do not trust the model’s self-report.”**

---

<h2 id="ch4">Chapter 4 · The heartland: Goal stop condition and an independent judge model</h2>

### 4.1 The problem it solves

The most common failure in a long task is not a crash; it is an **optimistic stop**: the model gets halfway, feels it is close enough, outputs a paragraph of “I have already completed XXX,” and clocks out. The user comes back to find tests unrun, edges untreated, files never written.

The `/goal` command sets a **stop condition** on the session. After that (`session/goal.ts:17-21` file-header comment):

> 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**.

The last half-sentence is the design essence: **the judge does not do the work, so its judgment stays “cold” relative to the worker’s optimism.**

### 4.2 The judge’s prompt

`JUDGE_SYSTEM` at `goal.ts:64-73`, especially the constraint on `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」**—this one sentence nails the relation between judge and judged. Without it, the model only has to say “this cannot be done” and the judge lets it through; the whole mechanism is then dead.

Three accompanying details:
- **`temperature: 0`** (`goal.ts:188`)—the judge must be reproducible.
- **What the judge sees is native model messages, not rendered text** (`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.” It can see the real tool calls and return values, not the assistant’s retelling.
- **No evidence means fail**: no clear evidence in the transcript → `{"ok": false, "reason": "insufficient evidence"}`. **Default is do not let through.**

### 4.3 goalGate in the main loop

```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
```

The implementation is at `session/prompt.ts:2505-2600`. **Four escape hatches, not one of which can be missing:**

| Escape hatch | Location | Why it must exist |
|---|---|---|
| **fail-open** | `prompt.ts:2525-2533` | “fail-open on any judge error so **a flaky judge can never trap the user**”—if the judge itself hangs, it must not lock the user in |
| **`impossible`** | `prompt.ts:2534` | When the condition itself cannot be reached, there must be an exit |
| **`MAX_GOAL_REACT = 12`** | `prompt.ts:169-175` · `:2558` | “the safety valve against a **never-satisfiable condition burning tokens forever**” |
| **Applies only to the main agent** | `prompt.ts:2507` | Sub-agents have their own `MAX_PRE_REACT (=3)`; they do not stack |

The comment on `MAX_GOAL_REACT` also explains why it is 12 rather than 3: “Higher than spawned actors' `MAX_PRE_REACT (=3)` because **main-session goals are usually larger**”—main-session goals are usually larger, so they need more rounds. And it honestly marks `TODO: lift to mimocode.json config (e.g. session.maxGoalReact)`.

### 4.4 How the judge’s verdict gets back to the UI

Every verdict fires a `session.goal` event (`goal.ts:46-60`), carrying `goal` (`undefined` means already cleared) and `lastVerdict`, the latter containing `attempt` (which try), **`messageID`**, and `error` (whether the judge failed).

The comment on the `messageID` field says: “The assistant message the judge evaluated — **anchors the verdict to a turn**.” The TUI can therefore draw a mark next to that specific turn, and the user can trace “which stretch of text it was looking at when it judged.”

> 🧠 **One sentence**: the Goal judge is the first design in this series that thoroughly separates **acceptance** from **construction** with two independent model calls. Open Design’s five-juror review is five turns of the same session (shared context to keep consistency); MiMo does the opposite—**deliberately does not share, because what it wants is “cold.”**

---

<h2 id="ch5">Chapter 5 · Four doom-loop gates: empty-step / repeated-step / text-loop / max-steps</h2>

Goal governs “should it stop”; this chapter governs “**do not spin in place**.” Each of the four gates catches one disease, and **each is two grades, soft → hard**.

### 5.1 Gate one: empty-argument tool calls (`session/prompt/empty-step-detection.ts`)

**The disease** (file-header comment):

> 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.

**The test** (`isEmptyStep`, `:44-67`)—all four are required:

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

The definition of “empty input” is also fine-grained (`isEmptyInput` / `isEmptyValue`, `:73-90`): no keys, or every value is `null` / `undefined` / empty string / whitespace-only / empty array / empty object. **Numbers and booleans count as truth**—if the model passed `0` or `false`, that is intent.

**What is most worth learning is the boundary it explicitly draws** (file-header `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.

Translation: **“the model said nothing” is not a doom loop; that is a natural turn end.** An earlier version treated it as a loop and false-positived itself into unusability. True “the model produces nothing” is backstopped by timeouts.

**Soft → hard two grades** (`:92-104`):

| Grade | Injected system-reminder |
|---|---|
| Soft (1st time) | 「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**.」 |
| Hard (2nd time) | 「Second empty tool call. **Final chance before this turn is halted.**… Any further empty-argument tool call will **terminate this turn**.」 |

### 5.2 Gate two: repeated action signature (`session/prompt.ts:176-215`)

**Threshold**: `REPEATED_STEP_THRESHOLD = 3`—“Three in a row is a strong signal the model is stuck repeating itself rather than making progress.”

**The key is how the signature is computed.** `stepSignature` (`prompt.ts:198-215`) takes **only tool calls**, deliberately excluding text and 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.

And `stableStringify` (`prompt.ts:177-196`) solves another hidden problem:

> `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**.

So it recursively serializes by **sorted keys**.

> 🔑 **These two details are two faces of the same insight**: when judging “is this the same action,” things that are **semantically the same but represented differently** must be normalized (key order), and things that are **semantically different but represented similarly** must be excluded (narrative text). Get either one backwards and the detector is dead.

### 5.3 Gate three: text-replay loop (`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)
}
```

That `replace` is clever: when the model replays, the opening often random-walks among “Let me…” / “I’ll…” / “I will…” / “Let’s…”, and **stripping those openings at normalize time** is what lets you see that the rest is the same sentence.

Two-grade recovery prompts (`:24-46`): the soft one says “try another way, do not repeat”; the hard one says “If you repeat the same output again, **the session will be terminated**.”

There is also a `text-ngram-detection.ts` (3 785 bytes) doing n-gram-level repetition detection, complementary to the whole-sentence comparison above.

### 5.4 Gate four: max-steps (`session/prompt/max-steps.txt`)

When the step cap is reached, a **tools-fully-disabled** prompt is injected:

```
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
```

Note item 3: **「overrides ALL other instructions, including any user requests」**—even if the user said “you must finish editing this file” in context, it cannot crush this. This is the standard practice of writing “priority” into the prompt.

And what it requires is not just “stop,” but **a clear handoff**: what was done, what remains, what to do next. **So a truncated turn still has a deliverable.**

### 5.5 How the four gates relate

```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
```

**The layers are clear**: step-level gates catch local spinning, the turn-level gate catches budget exhaustion, the session-level judge governs “did it actually finish.” The three layers each govern their own thing; they do not substitute for one another.

---

<h2 id="ch6">Chapter 6 · Agent modes and sub-agents: three identities of 13 built-in agents</h2>

### 6.1 Three modes

`agent/agent.ts:35` defines an agent’s three identities:

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

The 13 built-in agents (`agent/agent.ts:133-418`) split into two groups by identity:

| mode | agent | Use |
|---|---|---|
| **primary** (user-switchable) | **`build`** | Default, full tool permission |
| | **`plan`** | **Read-only** analysis mode, for exploring code and designing a plan |
| | **`compose`** | Orchestration mode, spec-driven development + skill-driven workflows |
| | `orchestrator` | One window managing every task ([Chapter 16](#ch16), flag-gated) |
| | `max` | High-budget mode |
| **subagent** (created by the system on demand) | `general` · `explore` | General / explore |
| | `title` · `summary` · `compaction` | Title / summary / compaction |
| | **`checkpoint-writer`** | Automatically maintains `checkpoint.md` ([Chapter 9](#ch9)) |
| | **`dream`** · **`distill`** | Distill knowledge / distill skills ([Chapter 17](#ch17)) |

### 6.2 Sticky Tab: why compose, once entered, cannot be left

The switching rule the README describes is unusual:

> 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: 🔒 一旦进入即隔离
```

**The reason is “tool-call reliability”**: if the skill/tool set the model saw at session start changes mid-session, the calling habits it has already internalized will mismatch. The tool set between build ↔ plan is an inclusion relation (plan is a read-only subset of build), so mutual switching is safe; compose’s skill set is completely different, and cutting in and out would destroy reliability.

When switching from `plan` to `build`, a state-change prompt is injected (`session/prompt/build-switch.txt`, only 233 bytes):

```
<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>
```

**A mode switch must be told to the model explicitly**—otherwise it will keep acting by read-only habits.

### 6.3 Sub-agent dispatch: the actor system

`actor/spawn.ts` (1 010 lines) is the dispatch core of sub-agents. A few constants worth remembering (`:34-40`):

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

The comments likewise mark `TODO: lift to mimocode.json config`, and sketch a future constraint direction:

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

**The platform cap is a hard ceiling; a hook may only tighten, never widen**—this is a good rule for extension-point design (the same idea as Open Design’s closed `until` vocabulary).

Compare the three ReAct caps:

| Cap | Value | Scope |
|---|---|---|
| `MAX_GOAL_REACT` | **12** | Main-session goal re-entry ([Chapter 4](#ch4)) |
| `MAX_PRE_REACT` / `MAX_POST_REACT` | **3** | Sub-agent preStop / postStop re-entry |
| `MAX_TASK_GATE_SUBAGENT_REACT` | — | Task gate (`task/gate.ts`) |

**The main session gets 12, a sub-agent gets 3**—because “main-session goals are usually larger” (`prompt.ts:171-173`).

### 6.4 The stall watchdog

`actor/spawn.ts:42-46` describes a three-layer time-window design:

> 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**.

The three periods have to sit offset from one another: per-step heartbeat < watchdog scan < 60s registry scan < 90s stall-judgment window. **Scan too often and you hammer the database; scan too slowly and you miss a stuck child.** This kind of “multiple timers locating one another” comment is rare in implementations, but extremely valuable for maintenance.

### 6.5 Special treatment of system agents

`agent/config.ts:5` defines a closed set:

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

These three “system agents” are treated differently in two places:

**① Permission asks are auto-denied** (`decideAskRouting`, `agent/config.ts:38-48`)—they have no path to a human; see [§16.4](#ch16).

**② Invalid-output policy is declared separately** (`config.ts:11-15`):

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

The comment explains: “System agents must **opt into** an invalid-output contract instead of **inheriting the user-facing primary retry** when they run with agentID "main".”

**A system agent cannot inherit user-facing retry logic**—a retry the user can see pops a prompt and occupies the foreground; system agents run in the background and need another failure-handling path.

---

<h2 id="ch7">Chapter 7 · The Try-Best detector and artifact-level verification</h2>

`session/try-best-detector.ts` (9 283 bytes) catches another class of self-deception: the model says “I have already done my best,” “this is the best result currently achievable,” and then stops—but what the user wants is **done**, not **tried**.

It is complementary to the Goal judge:

| | **Goal judge** | **Try-Best detector** |
|---|---|---|
| Enablement | Requires the user to `/goal` explicitly, **opt-in** | **Always on** |
| Means | An independent model reads the transcript | Phrase-level heuristics |
| Cost | One extra model call | Near zero |

Accompanying it are `session/checkpoint-validator.ts` (8 174 bytes) and `session/checkpoint-retry.ts` (7 065 bytes)—after a checkpoint is written it must be **validated**, and if it fails it must be **retried**. Same main line of “do not trust the model’s self-report”: letting the checkpoint-writer sub-agent finish writing a snapshot **does not mean the snapshot is correct**.

> 📌 **Series contrast**: openworker’s `verify-scorecard` has the model check itself and emit a scorecard (the host programmatically checks that the card exists); Open Design’s Design Jury is five jurors scoring; MiMo is **independent judge + phrase heuristics + artifact verification**, three pipes at once. None of the three trusts the model saying “I am done,” but the ways of not trusting differ.

---

<h2 id="ch8">Chapter 8 · One model, one prompt: 20 prompt files and the routing rules</h2>

> ⚠️ **Attribution first: family-split prompt routing is upstream OpenCode’s design**, not a Xiaomi first-party invention. Upstream `session/prompt/` already had **10 files** (anthropic / beast / codex / copilot-gpt-5 / default / gemini / gpt / kimi / meta / trinity) + an `if` chain of identical structure. And of those, **5 files are byte-identical in MiMo** (gemini 15 372 = 15 372, kimi 8 695 = 8 695, codex 7 390 = 7 390, trinity off by 1 byte, copilot-gpt-5 off by 2 bytes).
>
> **The four things Xiaomi did**: ① add 5 families (deepseek / glm / minimax / orchestrator / compose); ② **rewrite the 3 most-used files** (gpt 9 284→25 447, default 8 528→20 800, anthropic 8 212→14 281); ③ add **dual-ID fallback** (upstream only used `model.api.id`); ④ put the three loop detectors in the same directory. Item-by-item evidence is in [§20.3](#ch20).

### 8.1 Full directory

20 files under `packages/opencode/src/session/prompt/`:

| File | Bytes | Aimed at |
|---|---|---|
| `gpt.txt` | 25 447 | GPT family (paired with the GPT tool ABI) |
| `orchestrator.txt` | 21 002 | Orchestrator mode |
| **`default.txt`** | 20 800 | **Fallback** |
| `gemini.txt` | 15 372 | Gemini |
| `anthropic.txt` | 14 281 | Claude |
| `copilot-gpt-5.txt` | 14 239 | Copilot’s GPT-5 |
| `default.old.txt` | 14 195 | Old fallback (archive) |
| `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 mode |
| `glm.txt` | 4 890 | GLM |
| `max-steps.txt` · `build-switch.txt` | 750 / 233 | State-switch prompts |
| `empty-step-detection.ts` · `text-ngram-detection.ts` · `text-loop-recovery.ts` | — | The three detectors |

### 8.2 Routing rules: a string of ifs

`session/system.ts:26-39` — all of the routing logic is these 17 lines:

```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]
}
```

Three things worth noticing:
1. **Order is meaningful**: `gpt-4`/`o1`/`o3` must be tested before `gpt`, otherwise gpt-4 falls into `PROMPT_GPT`.
2. **Dual-ID fallback**: first match `model.id`, if it misses then try `model.api.id`—when the same model is exposed through a different provider the ID may differ (e.g. `xiaomi/mimo-v2.5` on OpenRouter).
3. **Final fallback `PROMPT_DEFAULT`**: unmatched models get a generic prompt; they do not run naked.

**The architecture doc itself admits the roughness here** (`docs/architecture/codex-microkernel-runtime.md`):

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

That is: `system.ts`’s `provider()` decides which prompt to use, `tool/gpt.ts`’s `usesGPTToolset()` decides which tool set to give—**two functions each matching their own strings**, and they may later disagree.

### 8.3 Environment block and cache prefix

Inside the environment block assembled at `system.ts:66-84` is a cache optimization:

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

The comment explains:

> 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.

**Using request time would make a session that crosses midnight suddenly invalidate its cache on some turn.** Anchoring to session creation time does not.

The same stretch also has an honest risk annotation (`:87-89`): the vision-model list is **lazily parsed**; if provider config changes mid-session, this block will differ across turns and **break the cache prefix**—“In practice provider config is stable within a session.” **Known, acceptable, written down.**

### 8.4 Degradation when there is no vision capability

When the model does not support images (`system.ts:105-118`), a `<vision-capability>` block is injected:

> 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.

And it **lists the currently configured vision-capable models** (at most 3) for it to call with `--model`, and even gives a sample command. Finally it adds a distinction: if what you want is the file’s **binary structure** rather than **visual content**, use `hexdump -C`, not the read tool.

**This is the textbook writing of “capability degradation”**: it does not just say “you cannot”; it says “you cannot, but this is a path that works, and this is the concrete way to walk it.”

> One detail: `maskVisionCapability` (`system.ts:85-87`)—model IDs containing `gpt`/`claude`/`gemini` **do not get this injected**, because vision-capability detection on those families false-positives easily; better not to prompt.

---

<h2 id="ch9">Chapter 9 · Checkpoint: treating context as a reconstructable snapshot</h2>

### 9.1 Difference from “compaction”

The traditional practice is **compaction**: context is about to fill, so you have the model summarize the preceding dialogue into one paragraph and replace the original. The problem is that a summary is **lossy and irreversible**, and the summary itself also occupies context.

MiMo’s checkpoint is another road (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.

It is not kneading history into a paragraph; it is **reassembling a context from several structured sources**.

Four files:

| File | Content | Who maintains it |
|---|---|---|
| `MEMORY.md` | Project-persistent knowledge, rules, architecture decisions | `/dream` + hand |
| `checkpoint.md` | Structured state snapshot | **checkpoint-writer sub-agent, automatically** |
| `notes.md` | Scratch draft area | Agent writes as it goes |
| `tasks/<id>/progress.md` | Per-task progress log | Task system |

### 9.2 Budgeted injection

README:

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

In the implementation this corresponds to `readBudgeted` / `readBudgetedSectionAware` and `CHECKPOINT_SECTION_BUDGETS` (imported at `checkpoint.ts:36-38`). **Per-section budgets** mean different subsections of the checkpoint have their own quotas; an important section will not be squeezed out by a secondary one.

### 9.3 Truncation strategy for over-long user messages

`truncateVerbatimUserMsg` at `checkpoint.ts:52-69` is a small, complete engineering sample:

```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")
```

Three details:
1. **Head 60% tail 30%**—the head has intent, the tail often has conclusions / the latest request, the middle is the most dispensable.
2. **Surrogate-pair repair**: `slice()` cuts by UTF-16 code units and may split a surrogate pair in half. So a trailing high surrogate and a leading low surrogate are each stripped—the comment’s original words: “**otherwise emoji / non-BMP chars don't render as garbage**.”
3. **The elision marker brings a retrieval path**: `messageID=... use the history tool with operation=around`. **When the model sees the omitted part, it knows how to get the full text back.**

### 9.4 Debouncing high-pressure prompts

`nudgedSinceBoundary` at `prompt.ts:216-243` solves a concrete nuisance: when context pressure is high you want to prompt the model “time to refresh memory,” but a sustained high-pressure turn emits many messages (one per tool call), and a fixed window would let the already-nudged message slide out of the window and **re-fire the nudge mid-turn**.

The solution is to **partition episodes by checkpoint boundary**:

> 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.**

**The debounce window should align to the event where “state actually changed,” not to a made-up message count.**

---

<h2 id="ch10">Chapter 10 · Memory: SQLite FTS5 + a BM25 relative floor</h2>

### 10.1 Table structure

The whole of `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),
])
```

Paired with an FTS5 virtual table `memory_fts_idx`, joined at search time.

### 10.2 Four retrieval-engineering details

**① Query construction** (`memory/fts-query.ts`, called from `service.ts:66-69`):

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

Treat punctuation as separators, quote each alphanumeric run as a phrase literal, then OR-join them—**so punctuation in the user query is not treated as FTS5 syntax** (`-` `"` `*` `(` `)` all have meaning in `MATCH` grammar; leaving them untreated errors or drifts semantically).

**② BM25 relative floor** (`service.ts:71-84`)—this comment is the most brilliant in the whole file:

> 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.

Three layers of consideration, each with a reason:
- OR-join introduces common-word noise → needs filtering
- An absolute threshold would false-kill on a small corpus → use a **relative** threshold (15% of the top score)
- A relative threshold would also false-kill when there is no discrimination at all → **#1 is always kept**

**③ Over-fetch** (`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.

**④ Score-direction flip** (`service.ts:122-129`): FTS5’s `bm25()` is **smaller-is-better**; externally it is unified to **larger-is-better** (negate). Places where “the external convention and the internal implementation point opposite ways” are the easiest to get wrong; the code notes it explicitly.

### 10.3 Lazy rebuild and cross-product indexing

`service.ts:60-65`: before every search, reconcile once (can be configured off), the reason being “covers **off-tool writes**”—the user may have edited `MEMORY.md` directly in an editor, without going through a tool. The `fingerprint` field makes rebuild incremental.

There is also a cross-product Easter egg (`service.ts:38` + `:62`): when `memory.cc_index` config is on, `~/.claude/projects` is also indexed—**it reads Claude Code’s project memory**.

---

<h2 id="ch11">Chapter 11 · A tunable compaction point: <code>/context-limit</code> and cost tiers</h2>

This is a purely product-driven engineering feature. The README gives three motives, each very concrete:

> - **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**.

Config supports wildcards and “longest pattern wins”:

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

Two safety constraints:
- **Can only be turned down, never up**: “The value is always **clamped to what the provider actually accepts**, so it can only lower the compaction point, never raise it.”
- **`0` restores the model’s own window**.

**UI presentation was also thought through**: the prompt footer shows `33.0K/260K↓ (13%)`, and that **`↓` means a budget is currently in effect**—the user can see at a glance they are not using the model’s full window. `mimo models <provider>` prints per-model “the resolved window” and “where it will actually compact”; `/status` gives a breakdown.

> 🧠 **One sentence**: this is a rare feature in this series that **encodes a commercial billing rule directly into agent config**. The number 272K is not a technical threshold; it is OpenAI’s price-tier boundary.

---

<h2 id="ch12">Chapter 12 · The GPT microkernel: a smaller tool ABI for Codex</h2>

> ⚠️ **Attribution first**: the expression that decides the GPT family **exists byte-for-byte in upstream OpenCode** (`tool/registry.ts:293`):
>
> ```ts
> input.modelID.includes("gpt-") && !input.modelID.includes("oss") && !input.modelID.includes("gpt-4")
> ```
>
> But upstream only uses it for **one two-way swap**—`apply_patch` ↔ `edit`/`write`. What Xiaomi did: extract it as a named function `usesGPTToolset()` (standalone file `tool/gpt.ts`), and expand the scope into a **wholesale ABI replacement** (additionally gate `exec`, `view_image`, and hide read/grep/glob/multiedit/notebook_edit). **From “swap one screwdriver” to “swap the whole toolbox.”** Details in [§20.4](#ch20).

### 12.1 The core claim

`docs/architecture/codex-microkernel-runtime.md` opens with the claim (and first clarifies its own terminology):

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

The practice is: **do not build a new agent engine for GPT**; instead do three things on the unified Session runtime—① use a GPT/Codex-specific system prompt; ② assemble a smaller model-specific tool ABI through `ToolRegistry`; ③ provide QuickJS `exec` to compose host tools without amplifying permission.

One-sentence principle:

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

### 12.2 Two tool surfaces

```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
```

The predicate function is only 3 lines (`tool/gpt.ts:11-13`):

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

It excludes `oss` (gpt-oss is an open-weight model, tool behavior differs) and `gpt-4` (older models take the `beast.txt` road).

`ToolRegistry.available()` (`tool/registry.ts:375-410`) filters on this: `ToolScriptTool` (i.e. `exec`), `ApplyPatchTool`, `ViewImageTool` are **open only to GPT**; `EditTool` / `MultiEditTool` / `WriteTool` / `ReadTool` / `GrepTool` / `GlobTool` and so on are **hidden from GPT**.

### 12.3 Why give GPT fewer tools

On the surface it is “subtraction”; in fact it is **aligning to the model’s training distribution**. Codex-family models were trained in OpenAI’s own harness on the `bash` + `apply_patch` ABI; give them a Claude-style read/write/edit/grep/glob set and they are more likely to call wrong.

> 📌 **Contrast Open Design**: OD’s practice is **define no tools at all**, and directly use the hosted CLI’s native tools. MiMo is **the same engine, swapping the tool surface per model**. Both solve the same problem—“different models are used to different tools”—but one relies on delegation, one on adaptation.

### 12.4 Honest boundaries of `apply_patch` and `view_image`

When introducing `apply_patch`, the architecture doc actively marks a defect:

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

`view_image`’s three current limits are also written out: `detail` only writes metadata and does not change processing; there is no independent image-size limit; `exec` cannot pass through image attachments, so images must call `view_image` directly.

**Listing your own unfixed defects in the architecture doc is a signal of this repo’s documentation quality.**

---

<h2 id="ch13">Chapter 13 · <code>exec</code> and QuickJS: compose tools without amplifying permission</h2>

> ⚠️ **Attribution first: the idea of “run a script in a confined sandbox to orchestrate tools” comes from upstream**—OpenCode has `tool/code-mode.ts` (11.8 KB, tool name `execute`, described as “Run a confined orchestration script with access to connected MCP tools”), backed by the independent package `@opencode-ai/codemode`.
>
> **But the implementation was swapped wholesale**, and the reason for the swap is hard: upstream’s sandbox is an **acorn-based 3 465-line hand-written AST interpreter** (`packages/codemode/src/interpreter/runtime.ts`); MiMo swapped in **QuickJS-emscripten (a real WASM JS engine)**; upstream exposes only **MCP tools**, MiMo instead exposes **host tools** via a late-bound registry. Those two differences determine every security design that follows. Details in [§20.5](#ch20).

### 13.1 What it is

The model submits a TypeScript/JavaScript async function body and calls host tools via `tools.<name>()`. One `exec` can batch-call and aggregate multiple tools, saving multi-round round-trips. The implementation is `ToolScriptTool` (`tool/tool-script.ts:303`), exposed to the model as `exec`.

**The danger is obvious**: if `exec` can reach an outer-forbidden tool, the permission system is pierced.

### 13.2 Three lines of defense

**① 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` 的别名，权限和执行路径相同。

**The key is “same filtered registry”**—`exec` does not get a full tool table; it gets the identical copy the outer layer already has.

**② Control-flow tools are excluded**:

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

**This distinction is precise**: tools that produce side effects (bash, edit) can be called inside exec, because the permission gate still fires as usual; but tools that **change scheduling state** cannot, because that would let one tool call change the whole session’s trajectory without the user knowing.

**③ Two layers of safety boundary**:

| Layer | What it does | What it does not |
|---|---|---|
| QuickJS (`workflow/sandbox.ts:106`) | Isolate guest code: **no Node, `process`, `fetch`, timer, module loading** | Does not isolate `bash`—that is still a real Shell |
| Host tools | permission, external-directory, memory guard, the tool’s own validation | — |

The doc says it plainly: “QuickJS 只隔离 `exec` 代码。**`bash` 仍是真实 Shell，不是容器 sandbox。**”

### 13.3 Resource limits and “active compute”

| Resource | Default / cap |
|---|---|
| Nested tool calls | Default 50, max 500 |
| Concurrent calls | 8 |
| **Active compute** | Default 60 seconds, max 600 seconds |
| Wall clock | 30 minutes |
| Guest memory | Default 64 MiB |
| Code / return value / logs | 128 KiB / 256 KiB / 64 KiB |
| `files.*` single file | 10 MiB |

**The “active compute” concept deserves its own paragraph.** `sandbox.ts:112-131` implements a pause/resume clock:

```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.**

Time the script spends waiting for a host tool to return **does not count against its compute budget**. Only when guest bytecode is actually running is time charged. Otherwise a normal script calling bash 5 times (20 seconds each) would be false-killed by the 60-second budget. Wall clock (30 minutes) separately backstops a true hang.

**File access is also clamped**: `files.readText` can only read UTF-8 text inside the worktree or OS tmp; `files.writeText` **can only write OS tmp**. Project changes must go through permission-gated host tools.

### 13.4 Three hard constraints of the QuickJS integration

`sandbox.ts:100-105` records the conclusions of the 2026-06-01 technical verification:

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

The third is especially fatal—the comment on the `deferreds` variable emphasizes it again: an **unsettled** deferred (the host promise is still in flight when the script returns) must also be released first, otherwise `vm.dispose()` will **hard-crash the process** because of live GC objects.

> 🧠 **One sentence**: `exec` is the highest engineering-density place in this repo—it has to satisfy “can compose,” “does not amplify permission,” “does not false-kill a normal script,” and “does not crash the process” at once, and each constraint left a concrete implementation trace.

---

<h2 id="ch14">Chapter 14 · The permission system: FORCED_ASK and the line a wildcard cannot crush</h2>

### 14.1 The rule model

The base model in `permission/index.ts` is conventional: `Action = allow | deny | ask` (`:33`), a rule is a `{permission, pattern, action}` triple (`:38-44`), multiple rule sets are merged with `merge()` (which is just `flat()`, `:587-589`), and `evaluate()` evaluates (`:184-186`).

What is interesting is in two places.

### 14.2 FORCED_ASK: a wildcard grant cannot crush delete

`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"])
```

Unpack the four properties of this rule:

| Property | Meaning |
|---|---|
| **Wildcard allow is ineffective** | `permissions.allow: ["*"]` cannot pre-authorize delete |
| **Historical approval is also ineffective** | A stored `{permission:"*", pattern:"*", action:"allow"}` likewise cannot |
| **Explicit deny still takes effect** | Forced ask ≠ forced execute |
| **The only bypass is a tool-side env var** | `MIMOCODE_AUTO_APPROVE_DELETE`—**to bypass you must be explicit, and bypass at another layer** |

**The core reason**: “the intent to perform an irreversible action must be **recorded in-band**, not inherited from a broad blanket rule”—the intent of an irreversible action must be **recorded on the spot**, not inherited from a broad blanket grant.

> 📌 **Series contrast**: CodeWhale’s “safe by construction” (the model simply does not have an over-permissioned tool), openworker’s five-grade mode + inbox, Claude Code’s permission gate + hooks, Open Design’s **full delegation**. MiMo walks “generic rules + a few non-negotiable exceptions”—**rules can be wide, but some lines a wildcard cannot cross.**

### 14.3 Tool-group aliases and findLast

`disabled()` at `permission/index.ts:591-614` handles a practical config-convenience problem:

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

When matching rules, besides matching by the tool’s own name, **EDIT_TOOLS additionally match on the group alias `edit`**. So the user writing `edit: "deny"` can ban the whole edit family in one shot.

And the choice of `findLast` is the key:

> `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.

**Use array order to express priority, use `findLast` so later writing wins**—no extra priority field is needed, and it matches the intuition that “what is written later in a config file overrides what was written earlier.”

---

<h2 id="ch15">Chapter 15 · Workflow: deterministic JS scripts orchestrating multiple agents</h2>

### 15.1 The split from “conversation”

The README’s definition is clear:

> 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.

| | **Conversational** (build + `/compose-next`) | **Workflow** |
|---|---|---|
| Control flow | The model decides | **Hard-coded in the script** |
| Interaction | Direction can be changed mid-way | **No interaction needed** |
| Parallelism | Relies on the model dispatching sub-agents | **Automatic parallelization** |
| Retry | The model judges itself | **Bounded retry** |
| Fits | Judgment needs to be injected mid-way | **Requirements are clear, tasks can be split** |

The README gives an explicit selection recommendation: “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 Four built-in pipelines

| Workflow | Phases | Character |
|---|---|---|
| **`compose`** | Brainstorm → Design → Implement → Verify → Review → Report → Merge | **Automatically parallelizes independent tasks onto isolated git worktrees**; each task uses TDD; structured output is passed between phases |
| **`deep-research`** | Brief → Plan → Research → Reflect → Write → Review | Plan independent research angles → parallel sub-agents collect findings **with citations** → reflect on gaps → write one coherent Markdown → **cold-review citations**. Convergent; continuable via file checkpoints |
| **`fact-check`** | Plan → Search → Extract → Group → Crosscheck → Report | **Adversarial** fact verification: parallel search → extract checkable facts → dedupe and group → **3-juror adversarial vote** cross-checks item by item |
| **`research-experiment`** | Baseline → Loop → Audit → Report | An autonomous optimization loop aimed at **mechanically verifiable metrics**: build a baseline → hypothesize / implement / evaluate / keep or roll back → **audit whether it is gaming the metric** → emit a reproducible result log |

The last two are especially interesting:
- `fact-check`’s **3-juror adversarial vote** and Open Design’s Design Jury are the same family of design, but pointing opposite ways—OD’s five jurors **collaboratively review the same artifact**; MiMo’s three jurors **adversarially verify the same fact**.
- `research-experiment` explicitly requires **auditing metric gaming** (“audits for metric gaming”), and requires the caller to provide a **fixed-budget evaluation command** and a **clear editable-file scope**. This is the “do not let the agent lie to itself” theme extended into the experiment setting.

### 15.3 How built-in scripts are baked into the binary

The comment at `workflow/builtin.ts:4-20` explains a Bun-specific trick:

```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**.

And it honestly marks the cost: tsgo will treat the `.js` as a real module and report TS1192, so a `@ts-expect-error` scoped only to this line is added.

### 15.4 Two defensive details

**① Fail at boot** (`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.

If a built-in workflow’s meta fails to parse → **the whole app cannot start**, and the error message carries the filename. Same philosophy as Open Design’s registry-dedup `throw`: **let the error be early and loud**.

**② Null-prototype registry** (`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)
```

**Use `Object.create(null)` against prototype-chain pollution**—looking up `constructor` returns `undefined` rather than `Object.prototype.constructor`. When the registry’s keys may come from model output, this is required.

### 15.5 Custom and override

Drop a `.js` into `.mimocode/workflows/` or `.claude/workflows/` and you can define your own workflow; **a same-named file can override a built-in** (e.g. `.mimocode/workflows/compose.js`).

Note that it also scans the `.claude/` directory—same as skills and the memory index, **MiMo in several places actively compatible with Claude Code’s directory conventions**.

---

<h2 id="ch16">Chapter 16 · Orchestrator mode: one window managing every task</h2>

`docs/harness/MiMo Orchestrator Mode.md` (13 KB) is the complete design document for this experimental feature. **Off by default**, gated by a single flag `MIMOCODE_EXPERIMENTAL_ORCHESTRATOR`.

### 16.1 The problem it solves

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

To push several things forward in parallel, the conventional practice is to open several terminals, each running a session, then keep switching to watch progress. Orchestrator’s claim is: **one window, one session, pure natural language managing every task**.

### 16.2 The model

```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["汇报给用户"]
```

**The core boundary** (doc §1):

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

Each child is an **independent session** (its own session id, task panel, memory), running in the background with `mode: "peer"`. **A child is a peer, not an in-session subagent**—the user can fully attach into any child the same way as `mimo -c <id>` to inspect / take over.

### 16.3 The 8 verbs of the `session` tool

Only Orchestrator mode can see this tool (gated by agent name + flag). Implementation in `tool/session.ts` (53 KB, `KNOWN_VERBS`):

| verb | Role | Key parameters |
|---|---|---|
| `create` | Dispatch a new child session in the background | `task` (required) · `mode` · `model` · `dir` · **`isolate`** |
| `switch` | Switch the front-end panel to a session | `sessionID` |
| `list` | List all child sessions | — |
| `cancel` | Stop a child session (if it was isolate, delete the worktree and branch) | `sessionID` |
| `ask` | **Read-only, one-shot** bypass question (based on a frozen snapshot, does not interrupt the run) | `session_id` + `question` |
| `setmode` | Change the child’s mode for **subsequent turns** (plan finishes planning, switch to build, **same session**) | `sessionID` + `mode` |
| `approve` | Approve the child’s currently pending permission request | `sessionID` |
| `grant-approval` | **Pre-authorize** future requests to auto-approve | `target` (a given child or `all`) |

### 16.4 Three mechanisms worth learning

**① Permission-approval routing for background sessions** (doc §4)

The problem is very real: a child running in the background has no user-facing panel; hitting a permission gate that needs `ask` is **directly denied** (`interactive:false` → `DeniedError`), and the user cannot see it or approve it.

`decideAskRouting` (`agent/config.ts`) does a **four-way split**:

| Scenario | Disposition |
|---|---|
| System agents (checkpoint-writer / dream / distill) | **Still auto-deny** |
| **Orchestrator peer child** (background + `mode:peer` + has a parent session) | **Forward the approval** |
| Other background (compose’s subagent, etc.) | **Still auto-deny** |

After forwarding, the user can switch into the child and approve directly, or the Orchestrator can approve by delegated authorization. **The test is “is there a path to a human,”** not “is it background.”

**② `--isolate` and git worktree**

When on, the child runs in its own git worktree (branch `mimocode/<task>`), located at `<data>/worktree/<projID>/<task-slug>`. Multiple children editing the same repo do not conflict. **A non-git directory automatically degrades** to running directly in `dir`.

There is a heavy accompanying warning (§3.2):

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

**“Finished” and “can delete” are two different things**—this pit is written into the docs.

**③ Do not poll** (§3.3)

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

Interrupting the Orchestrator **does not** stop children (they keep running and notify on completion); only when the whole session exits do children exit with it. Recovery is `session list` + forwarding a message with `actor`’s send—**there is no separate resume command**.

> 📌 **Contrast openworker’s inbox**: openworker’s inbox is “when the agent needs a human, hang and wait for you”; MiMo’s inbox is “when a child session is done, wake the parent session.” The same primitive, one facing the human, one facing internal scheduling.

---

<h2 id="ch17">Chapter 17 · Dream / Distill / Evolve: letting the agent rewrite itself</h2>

Three capabilities make up the agent-side evolution path in the “co-evolve” slogan:

| Capability | What it does | Prompt |
|---|---|---|
| **`/dream`** | Scan recent session traces, **extract persistent knowledge into project memory**, and **delete stale entries** | `agent/prompt/dream.txt` (7 127 bytes) |
| **`/distill`** | Discover **recurring hand workflows** in recent work, and **pack high-confidence candidates into reusable skills / sub-agents / commands** | `agent/prompt/distill.txt` (8 700 bytes) |
| **`evolve` skill** | “**Total self-modification** — rewrite any layer of the agent: tools, behavior hooks, knowledge, workflows, **even the UI**” | `skill/builtin/.bundle/evolve/` |

The half-sentence in `/dream`, “**and delete stale entries**,” is the key—most memory systems only add, and get messier as they add. **A memory system that will delete is the one with long-term usability.**

`/distill`’s idea is **to reverse-engineer an abstraction from behavior**: you hand-did the same flow three times, it solidifies that flow into a skill. This is the same family of thought as hermes-agent’s “closed-loop learning five-piece kit.”

`evolve` is the most radical of the three—even the UI can be changed. Accompanying it are `skill-creator` (interactively create/improve skills) and **`drive-mimo`** (**use one MiMoCode process to script, test, and automate another MiMoCode process**, headless or TUI mode).

> ⚠️ **`drive-mimo` deserves a mention of its own**: it lets an agent drive an agent; this is the infrastructure of self-bootstrapping tests, and also the premise of “co-evolve” landing—**without it, after rewriting itself there is no way to verify**.

### 17.1 Full table of built-in skills (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`

Note that among them are **`claude-code`, `codex`, `grok-build`**—**MiMo can delegate a task to these external CLIs** (and only exposes them when the corresponding executable is already installed).

> 🔄 **An interesting reverse relation**: Open Design treats `mimo` as an engine (it is one of OD’s 26 adapters, `streamFormat: json-event-stream`, MCP injection via `MIMOCODE_CONFIG_CONTENT`); MiMo can in turn treat Claude Code / Codex / Grok Build as tools. **In this ecosystem there is no absolute host and guest.**

### 17.2 Skill retrieval

The matching strategy the README describes is three-level: **exact name → localized alias → BM25 relevance**. High-confidence auto-loads; uncertain ones are ranked and handed to the agent to judge.

The prompt at `system.ts:141-152` also requires the model to **rewrite the user request into a dimensioned Skill Query** (action / input / output / audience), and **keep the skill ID/name/alias the user explicitly mentioned in the original wording**, so exact match takes priority over BM25.

One sentence on multi-skill orchestration: “mentioning **two or more skills** in a single message auto-loads them and **injects a multi-skill orchestration plan**” (corresponding to `docs/harness/Agent Multi-Skill Workflow Orchestration Design.md`).

Three environment variables can turn skills off (README): `MIMOCODE_DISABLE_BUILTIN_SKILLS` (all off), `MIMOCODE_DISABLE_OFFICIAL_SKILLS` (only the 5 office/media ones), `MIMOCODE_DISABLE_SLASH_SKILLS` (only hide them from TUI autocomplete; the agent can still use them). **The difference between the third and the first two is written very clearly**—the first two truly remove from the skill list; the third only affects completion.

---

<h2 id="ch18">Chapter 18 · Token Efficient mode: washing the noise out of bash output</h2>

`docs/harness/MiMo Token Efficient Mode.md` (9 KB). **Experimental, off by default**, single flag `MIMOCODE_EXPERIMENTAL_TOKEN_EFFICIENCY`.

### 18.1 The problem

bash stdout/stderr is often bloated into context by this noise: ANSI color codes, OSC hyperlinks, DCS terminal control sequences, `\r` progress-bar multi-frame overlap, accidentally printed API keys / JWTs / PEM certificates, minified JS / single-line JSON and other over-long lines, pytest/go test dead information.

### 18.2 Three core constraints

| Constraint | Meaning |
|---|---|
| **Clean only inline, not on disk** | Cleaning is **only for the LLM**; TUI live preview and disk archive **keep the original bytes**, for human debug |
| **never-worse gate** | Unified spit-back at the tail of the pipeline: **any stage that made the output larger is discarded**, falling back to the Raw path |
| **Single flag, off by default** | The only switch, and it is off by default |

The first is the most important: **what the model sees and what the human sees are two copies**. When a human debugs they need the original ANSI and progress bars; the model does not.

### 18.3 Five-layer pipeline

Implemented in `tool/bash_token_efficient_pipeline.ts` and `bash_token_efficient_heuristic.ts`:

| Layer | Duty | Order constraint |
|---|---|---|
| `clean_progress_pipeline` | Fold `\r` progress bars by line, keep only the last frame | **Must precede ansi** |
| `clean_ansi_pipeline` | Strip ANSI CSI/OSC/DCS, backspace overstrike, control bytes | After progress, before downstream regexes |
| `clean_redact_pipeline` | PEM, Bearer, JWT, AWS/GH/OpenAI/Anthropic/Slack secrets | **Must run before dedupe/truncate** |
| `clean_longline_pipeline` | A single line over 500 characters is crushed to head 160 characters + an elision hint | Put last as the backstop |
| never-worse gate | If cleaned bytes did not get smaller, spit back the original | Tail of the pipeline |

**Every order constraint has a reason**: progress bars must be folded first, otherwise after ANSI is stripped you are left with a pile of overlapping frames; secrets must be redacted before truncation, otherwise they may be cut in the middle and dodge the regex.

There are 14 redaction regexes in total (4 ESC + 1 control-byte character class + 1 cross-line PEM block + 8 inline-secret patterns), covering Bearer/Token, JWT (`eyJ` three-segment base64url), AWS (`AKIA`/`ASIA` + 16 digits), GitHub (`gh[pousr]_` + ≥20), OpenAI (`sk-` + ≥20), Anthropic (`sk-ant-` + ≥20), and so on.

> 🧠 **The never-worse gate is a pattern worth remembering on its own**: any “optimization” pipeline should have a “if it did not get better, fall back to the original” backstop. Cleaning rules always have input shapes you did not think of; this gate makes the worst case **equal to not cleaning**, rather than equal to wrecking.

---

<h2 id="ch19">Chapter 19 · Wrap-up: three most distinctive designs and three trade-offs</h2>

### 19.1 Three most distinctive designs

#### ① “Do not trust the model’s self-report” was made into a full depth-in-depth kit

This is MiMo Code’s real theme, and it is not a slogan; it is **four layers, seven mutually independent mechanisms**:

```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
```

**The four layers each govern their own thing; none can substitute for another.** This depth-in-depth is absent from the other projects in this series—the others usually have only one or two of the layers.

And every layer left a comment on **why this threshold**: `MAX_GOAL_REACT = 12` because a main-session goal is larger than a sub-agent’s; `REPEATED_STEP_THRESHOLD = 3` because “three in a row is a strong signal”; empty-step detection **deliberately does not catch** empty terminals because that would false-positive in bulk.

#### ② Pushing “reshape per model” from an embryo to the extreme (**inherited + heavily expanded**, not first-party)

⚠️ Attribution first: **family-split prompt routing and the GPT tool-gating predicate expression both come from upstream OpenCode** (item-by-item evidence in [Chapter 20](#ch20)). Listing it among “most distinctive designs” is because **Xiaomi walked this road to a place upstream did not**:

| Dimension | Upstream OpenCode | **MiMo Code** |
|---|---|---|
| Prompt families | 8 files | **20 files** (+deepseek/glm/minimax/orchestrator/compose) |
| Volume of the three main prompts | 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)** |
| Routing input | Only `model.api.id` | **Dual-ID fallback** (`model.id` → `model.api.id` → default) |
| GPT gating scope | `apply_patch` ↔ `edit`/`write` **two-way swap** | **Wholesale ABI replacement**: additionally gate `exec`/`view_image`, hide read/grep/glob/multiedit/notebook_edit |
| Gating code shape | An inline boolean expression in the registry | Extracted as a named function `usesGPTToolset()` in a standalone file `tool/gpt.ts` |

**This is a frontal negation of the “generic harness” assumption**—everyone else’s idea is “write one good prompt so every model can use it”; this road’s idea is “**each model’s training distribution is different, so the harness should reshape for it**.” **Upstream opened the start; Xiaomi turned it into a product claim** (slogan “Models and Agents Co-Evolve”).

The cost is equally blunt: 20 prompt files have to be maintained each, routing is a string of string `if`s, and **prompt routing and tool routing are two independent rule sets** (the architecture doc itself admits they “have not yet been unified into a model-capability negotiation layer”)—in practice `gpt-oss-120b` will show the inconsistency of the prompt taking the GPT road while the tool surface is not the GPT ABI.

#### ③ Sandbox engine swap: from a 3 465-line hand-written interpreter to QuickJS WASM (**rewrite, not first-party**)

⚠️ Attribution likewise first: **“run a script in a confined sandbox to orchestrate tools” comes from upstream** (`tool/code-mode.ts`, tool name `execute`, backed by the independent package `@opencode-ai/codemode`). But **the implementation was swapped wholesale**, and this replacement itself is the engineering decision worth recording:

| Dimension | Upstream `@opencode-ai/codemode` | **MiMo `workflow/sandbox.ts`** |
|---|---|---|
| Isolation means | **acorn + 3 465-line hand-written AST interpreter** | **QuickJS-emscripten (a real WASM JS engine)** |
| What is exposed | **MCP tools only** | **Host tools** (via late-bound registry, the same already-filtered table as the outer layer) |
| Control-flow tools | — | **Explicitly excluded**: task/actor/question/skill/workflow/cron/session |
| Timing | — | **Active compute**: time waiting for a host tool is not charged against the 60s budget; wall clock 30min as backstop |
| Known cost | You have to implement JS semantics yourself; coverage has holes | **Every `QuickJSHandle` (including unsettled deferreds) must be released before dispose, otherwise the process aborts** |

**Why it was worth swapping**: a hand-written interpreter means JS semantics have to be implemented by you—every place not implemented is a behavior difference, every place implemented wrong may be an escape hatch, and 3 465 lines have to be maintained long-term. After swapping to a real engine, isolation is guaranteed by the WASM boundary, and the cost moves to “you must correctly manage handle lifetime”—**this is a trade of “semantic-correctness risk” for “resource-management risk”**; the latter has a determinate checklist, the former does not.

And expanding the exposure surface from MCP tools to host tools **directly raises the permission-risk grade**—hence the whole kit of accompanying designs: late-bound registry, control-flow tool exclusion, two-layer boundary. The sentence “the model decides what to do, `exec` is responsible for how to compose, the host decides whether to allow” can be matched item by item in the code.

### 19.2 Three trade-offs you must know

#### Trade-off one: `session/prompt.ts` a single file of 4 595 lines / 208 KB

Main loop, prompt assembly, goal gate, various gates, structured output, predicting the next message… all in one file. As a contrast:

| Project | Main-loop file | Lines |
|---|---|---|
| Open Design | `prompts/system.ts` (**assembly only**, no loop) | 2 075 |
| **MiMo Code** | **`session/prompt.ts` (loop + assembly + gates)** | **4 595** |

Its comment density is high (many design decisions are written down), but **a single file carries too many concerns**. This is the repo’s most obvious maintainability debt.

#### Trade-off two: a large number of experimental features off by default, flag-gated

Orchestrator (`MIMOCODE_EXPERIMENTAL_ORCHESTRATOR`) and Token Efficient (`MIMOCODE_EXPERIMENTAL_TOKEN_EFFICIENCY`) are both off by default. The benefit is a stable main path; the cost is that **most users will never turn on these well-designed capabilities**, and flag branches will explode the combination of code paths (the promise “when off, MiMoCode is identical to before” needs continuous verification).

A month and a half, 12.5k stars, **838 open issues**—this issue count is high relative to repo age, related to fast iteration + many experimental features.

#### Trade-off three: the dual cost a fork brings

Inheriting OpenCode’s skeleton saved a huge amount of early work, but it also means:
- **Upstream evolution has to be continuously merged**—the package is still named `packages/opencode`, the root `package.json` `name` is still `"opencode"`, which shows it has not fully decoupled
- **The Effect framework’s reading barrier** is brought by upstream, not chosen by Xiaomi, but people who read the code have to bear it together
- Some leftovers (such as an archive file like `default.old.txt`) stay in the tree

### 19.3 Its place in this series

| Dimension | Other projects in this series | **MiMo Code** |
|---|---|---|
| **Lineage** | Most written from zero | **A deep fork of OpenCode** (like Raven vis-à-vis nanobot, but the modification amplitude is far larger) |
| **Prompt strategy** | One generic prompt | **20 files, one per model family** |
| **Tool surface** | The same set for every model | **GPT family swaps to a 4-piece ABI** |
| **Stop judgment** | The model says stop and it stops | **Independent judge model, four escape hatches** |
| **Doom-loop defense** | Usually 1–2 layers | **3 step-level + 1 turn-level + 2 session-level + 1 artifact-level** |
| **Context** | Compaction-first | **Checkpoint reconstruction + a tunable compaction point (including a billing tier)** |
| **Orchestration** | Sub-agents | **Deterministic JS workflow + Orchestrator mode (peer child + worktree)** |
| **Self-improvement** | hermes’s closed-loop learning | **dream / distill / evolve + drive-mimo self-bootstrap** |
| **License** | Mostly pure MIT/Apache | **MIT + a separate USE_RESTRICTIONS** |

> 🧠 **Last sentence**: what opencode answers is “how to make an agent a service any client can consume”; on its skeleton MiMo Code answered another question—**“the model will lie to itself; what should the harness do?”**
>
> The answer it gives is not one mechanism, but **four layers, seven devices**: step-level catches spinning (empty-step detection / repeated signature / text replay), turn-level catches budget (max-steps), session-level dispatches an independent judge (Goal judge / Try-Best detector), artifact-level validates the snapshot (checkpoint validation + retry). Plus the claim that “each model’s harness should be reshaped for it,” constituting a quite different coding-agent design.
>
> As for whether the slogan “co-evolve” has been delivered—`evolve` can rewrite itself, `drive-mimo` can verify the change, `distill` can grow new skills from behavior—**the infrastructure is complete; the rest depends on the community.**

---

<h2 id="ch20">Chapter 20 · Layer-by-layer delta: what MiMo actually changed in OpenCode, and why</h2>

> **Relation of this chapter to the first 19**: the earlier chapters talk about “what MiMo has”; this chapter talks about “**which of those it added itself, which it inherited, and why it changed them this way**.”
>
> **Method**: take `参考项目/opencode` (commit `62e4641`, a full checkout of 6 252 files) and MiMo (commit `076b790`, Git Tree API full 5 222 blobs) and do **file-level size comparison + content comparison of key files**. Every judgment has reproducible evidence; it does not rely on impression.

### 20.1 First look at the totals: 1.75×, but it did not fatten uniformly

All `.ts` / `.tsx` under `packages/opencode/src/`:

| | Volume | Ratio |
|---|---|---|
| OpenCode `62e4641` | **2 610 KB** | 1.00x |
| MiMo Code `076b790` | **4 572 KB** | **1.75x** |

But **growth is completely non-uniform**—that is the information-bearing place:

```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
```

**How to read it**: a fork’s “modification center of gravity” is hidden in this figure. Of the 15 subsystems MiMo added, 7 (workflow / actor / memory / cron / inbox / task / team) point at the same thing—**let the agent work long-horizon, multi-threaded, across sessions**; and the shrinkage of `server/` and `acp/` shows it **gave up part of upstream’s “the server is the ontology” ambition**, and moved the center of gravity back to the terminal.

### 20.2 The main loop: `session/prompt.ts` from 65 KB to 208 KB

This is the largest single-point change in the whole repo, **3.2×**:

| File | OpenCode | MiMo | Change |
|---|---|---|---|
| `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 |

**At the same time it deleted an entire upstream `session/llm/` subdirectory** (`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) and `session/tools.ts` (23 424 B).

**What this means**: upstream split LLM request construction, native runtime, and tool assembly across four or five files; MiMo **gathered them into `prompt.ts`, and at the same time stuffed a large amount of new logic in** (goalGate, four gates, structured output, predicting the next message…).

> ⚠️ **This is an “anti-refactor”**: upstream is splitting, MiMo is merging. The benefit of merging is that the main loop’s control flow can be seen in one glance (you do not jump among five files); the cost is the largest technical debt named in [§19.2](#ch19)—**a 4 595-line single file**.
>
> From a fork’s point of view this choice is understandable: **if you want to stuff seven or eight new mechanisms onto someone else’s skeleton, the fastest path is to concentrate the change in one file, not first refactor upstream’s layering.** The cost is that this debt will only roll larger.

### 20.3 Prompts: 8 → 20 files, but 5 files did not move a single byte

This is the place in this chapter that most needs attribution clarified.

**Already upstream** (`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`

**Upstream’s routing function** (`session/system.ts:27-42`)—structure identical to MiMo’s:

```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]
}
```

**File-by-file comparison**:

| Prompt | OpenCode | MiMo | Judgment |
|---|---|---|---|
| `gemini.txt` | 15 372 | 15 372 | **byte-identical, unchanged** |
| `kimi.txt` | 8 695 | 8 695 | **byte-identical, unchanged** |
| `codex.txt` | 7 390 | 7 390 | **byte-identical, unchanged** |
| `trinity.txt` | 7 748 | 7 749 | off by 1 byte |
| `copilot-gpt-5.txt` | 14 241 | 14 239 | off by 2 bytes |
| `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 | 🆕 (old-version archive) |
| `meta.txt` (muse-spark) | 9 151 | — | ❌ deleted |
| `plan-mode.txt` · `plan-reminder-anthropic.txt` · `plan.txt` | 4 547 / 4 056 / 1 484 | — | ❌ deleted |

**The conclusion is clear**:

| | Whose |
|---|---|
| **The family-split routing pattern** | **OpenCode** |
| Content of 5 prompt files | **OpenCode** (not a word changed) |
| Adding 5 families (deepseek/glm/minimax/orchestrator/compose) | **MiMo** |
| **Rewriting the 3 most-used files** (gpt/default/anthropic) | **MiMo** |
| **Dual-ID fallback** | **MiMo** |
| Putting the three loop detectors into the `prompt/` directory | **MiMo** |

**Why rewrite only those 3 files?** Because `gpt.txt` / `default.txt` / `anthropic.txt` cover the vast majority of actual usage—GPT family, Claude family, and every unmatched model. **Xiaomi spent effort on the three paths with the highest hit rate, and inherited the edge families as-is.** This is a very pragmatic trade-off.

**Why dual-ID fallback matters** (`session/system.ts:39`):

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

Upstream only looks at `model.api.id`. But when the same model is exposed through a different provider the ID differs—`xiaomi/mimo-v2.5` on OpenRouter, `internal/xiaomi/mimo-v2.5` on a self-built gateway. **The model-access paths MiMo has to support are far more than upstream** (MiMo Auto / Xiaomi platform OAuth / Codex OAuth / import from Claude Code / any OpenAI-compatible endpoint), so both IDs must be tried.

### 20.4 GPT tool gating: the same one-line expression, scope expanded by an order of magnitude

**Upstream `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")
}
```

**The predicate logic is byte-for-byte identical.** The difference is in two places:

| | OpenCode | MiMo |
|---|---|---|
| **Code shape** | A local variable `usePatch` inlined in the registry | Extracted as a **named export in a standalone file** `usesGPTToolset()` |
| **Scope** | Only governs `apply_patch` ↔ `edit`/`write` **two-way swap** | Additionally gates `exec`, `view_image`; **additionally hides** read/grep/glob/multiedit/notebook_edit |

**Why extract it**: upstream’s variable is named `usePatch`—the name says it only cares about “whether to use the patch tool.” MiMo needs to use the same predicate in **multiple places** (tool assembly, sub-agent prompt stitching `system.ts:44-48`); an inline variable is no longer enough, it must be extracted into a named concept.

> ⚠️ **One easy-to-confuse detail**: `tool/gpt.ts` also has an `isGPTModel` (lines 1-5), called by `isMcpToolSearchEnabled` (lines 7-9). It is **not the same function** as `usesGPTToolset`—`isGPTModel` only excludes `gpt-oss`, and returns true for `gpt-4` family models; `usesGPTToolset` excludes both `oss` **and** `gpt-4`, and returns false for the `gpt-4` family. The two behaviors fork on gpt-4 family models—MCP tool search is on for the gpt-4 family, but the GPT-specific toolbox is off for the gpt-4 family.

**From “swap one screwdriver” to “swap the whole toolbox”**—this scope expansion is MiMo’s real contribution, not that expression itself.

### 20.5 Sandbox: from a 3 465-line hand-written interpreter to QuickJS

This is **the highest-technical-content replacement**, and also the one most easily covered up by the sentence “MiMo has an exec tool.”

| | Upstream `@opencode-ai/codemode` | MiMo `workflow/sandbox.ts` + `tool/tool-script.ts` |
|---|---|---|
| Tool name | `execute` | `exec` |
| Entry file | `tool/code-mode.ts` (11 808 B) → deleted | `tool/tool-script.ts` (27 188 B) 🆕 |
| **Isolation implementation** | **acorn + hand-written AST interpreter**<br/>`interpreter/runtime.ts` **3 465 lines** | **QuickJS-emscripten** (WASM JS engine)<br/>`workflow/sandbox.ts` 15 878 B |
| Package dependency | `acorn` 8.15.0 + `typescript` (transpile) | `quickjs-emscripten` |
| **What is exposed** | **MCP tools only** (“access to connected **MCP** tools”) | **Host tools** (late-bound registry, the same already-filtered table as the outer layer) |
| Control-flow tools | — | **Explicitly excluded**: task/actor/question/skill/workflow/cron/session |
| Resource limits | Has execution limits (`ResolvedExecutionLimits`) | Nested 50/500 · concurrency 8 · **active compute 60s/600s** · wall 30min · memory 64 MiB |
| Known hard constraint | — | **Handles must all be released before dispose**, otherwise **the process aborts** (including unsettled deferreds) |

**Why it was worth swapping (three reasons, ranked by importance)**:

**① A hand-written JS interpreter is a bottomless pit.** 3 465 lines are only the starting point—JS’s semantic surface (prototype chain, closures, generators, Proxy, getter/setter, exception semantics, `this` binding…) has to be written for every piece you fill in, **every place not implemented is a behavior difference, every place implemented wrong may be an escape hatch**. After swapping to a real engine, that part of the risk is caught once by the WASM boundary.

**② The cost moves from “semantic correctness” to “resource management.”** QuickJS’s pit is hard (not releasing a handle aborts the process directly), but it is **a determinate checklist**—the three 2026-06-01 technical-verification conclusions at `sandbox.ts:100-105` are that checklist. **Semantic correctness has no checklist.**

**③ The exposure surface changed, and the risk grade followed.** Upstream only exposes MCP tools—those tools were already at the periphery of the permission system. MiMo wants to expose **host tools** (bash / edit / read…), **permission risk is raised one grade directly**. Hence late-bound registry (take the already-filtered table identical to the outer layer), control-flow tool exclusion, and the honest statement “QuickJS only isolates the script; `bash` is still a real Shell”—**this whole kit of accompanying designs was forced by the exposure-surface expansion.**

> 🧠 **General rule**: when you expand a sandbox’s exposure surface from “peripheral capability” to “core capability,” **both the isolation means and the permission-passing mechanism have to be redesigned**; you cannot just “register a few more functions.”

### 20.6 Shell execution: a wholesale rewrite

| 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 swapped for 87 KB.** Each of the five newly added files has a clear division of labor: tokenize (`shell-tokenize`), wrap (`shell-wrap`), interactive (`bash-interactive`), output-cleaning pipeline and heuristics (Token Efficient of [Chapter 18](#ch18)).

**Note that `bash.txt` and `bash.gpt.txt` are two files**—even the bash tool’s **description text** is split by model family. This is evidence that the “reshape per model” claim of [§20.4](#ch20) is carried through to the tool-description layer.

### 20.7 Permissions: 3.4×, and a “non-negotiable line” upstream did not have

| File | OpenCode | MiMo |
|---|---|---|
| `permission/index.ts` | 7 861 B | **26 415 B (↑3.4x)** |
| `permission/arity.ts` | 6 376 B | 6 376 B (**byte-identical, unchanged**) |
| `permission/permission-forward-ref.ts` | — | 7 536 B 🆕 |
| `permission/evaluate.ts` · `schema.ts` · `permission.sql.ts` | — | 🆕 |

**`FORCED_ASK` does not exist at all upstream** (`grep -rn "FORCED_ASK" opencode/packages/opencode/src/permission/` returns nothing).

The newly added `permission-forward-ref.ts` corresponds to Orchestrator’s **background-approval forwarding** in [Chapter 16](#ch16)—upstream has no Orchestrator, so naturally it does not need “forward a background session’s permission request to a human.”

> **The causal chain is clear**: MiMo added Orchestrator (peer children running in the background) → a background session hitting a permission ask is directly denied → a forwarding mechanism is needed → `permission-forward-ref.ts` + `decideAskRouting` four-way split (auto-deny / forward to the main session / inherit the parent session’s existing grant / let through directly). **A new feature forced the permission system to expand; that is the main source of the 3.4×.**

### 20.8 Skills: 2 → 414 files

| | OpenCode | MiMo |
|---|---|---|
| `skill/` file count | **2** (`discovery.ts` + `index.ts`) | **414** |
| Built-in skill packs | None | **26** (`skill/builtin/.bundle/`) |

Upstream only has the skill **discovery mechanism**, and ships no built-in skills. MiMo stuffed in 26 complete skill packs (including scripts, templates, datasets—the largest single file is a 78 KB Python script in `data-analytics`).

**This is a difference of product position**: upstream OpenCode positions itself as a “platform,” content left to the user; MiMo positions itself as an “out-of-the-box product,” so it has to ship a content library. **Same logic as Open Design stuffing in 151 design-system packs**—the trade-off “a content library is a maintenance burden” named in [§19.2](#ch19), both houses have to pay.

### 20.9 The places that shrank also carry information

| Directory | OpenCode | MiMo | Note |
|---|---|---|---|
| `server/` | 72 files | **43** | Upstream “the server is the ontology,” the server layer is extremely heavy; MiMo’s center of gravity returned to the terminal |
| `acp/` | 12 files | **4** | ACP (Agent Client Protocol) is a first-class citizen upstream; MiMo keeps it but no longer deep-farms it |
| `background/` | 1 | ❌ deleted | Replaced by `actor/` + `cron/` + `inbox/` |
| `image/` | 1 | ❌ deleted | Swapped for `tool/view-image.ts` |

**How to read it**: when this series analyzed opencode it positioned it as “**the server is the ontology**”—the TUI and the same-process server also walk the full HTTP/RPC. MiMo cut `server/` by forty percent and `acp/` by two thirds, which shows **it does not want to be a platform; it wants to be a usable terminal product**.

This also explains why `cli/` expanded 2.6× in the opposite direction (87 → 230 files, including 10-language i18n): **the server ambition that was cut was doubled into the terminal experience.**

### 20.10 Classify the 15 new subsystems by “why they were added”

```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 are three faces of the same product judgment**: upstream OpenCode faces “one conversation solving one problem”; MiMo faces “**long-horizon, parallelizable, able to run on its own**.” The README’s “long-horizon” (the official-site blog title is exactly `mimo-code-long-horizon`) is not marketing; it is the common footnote of these 12 subsystems.

### 20.11 One master table: whose credit

Take the 15-item increment roster of [Chapter 3](#ch3) and re-tag it by **real attribution**:

| Capability | Attribution | Basis |
|---|---|---|
| **Goal independent judge** | 🟢 **MiMo first-party** | `session/goal.ts` does not exist upstream |
| **Four doom-loop gates** | 🟢 **MiMo first-party** | The three detector files all do not exist upstream |
| **Try-Best detection + checkpoint validation** | 🟢 **MiMo first-party** | Upstream has no checkpoint concept |
| **Checkpoint context reconstruction** | 🟢 **MiMo first-party** | The whole `checkpoint*.ts` family 74 KB+ does not exist upstream |
| **FTS5 memory + BM25 relative floor** | 🟢 **MiMo first-party** | `memory/` does not exist upstream |
| **Tunable compaction point `/context-limit`** | 🟢 **MiMo first-party** | Upstream `compaction.ts` has no such config |
| **FORCED_ASK** | 🟢 **MiMo first-party** | Upstream permission has no such constant |
| **Workflow runtime + 4 built-in pipelines** | 🟢 **MiMo first-party** | `workflow/` does not exist upstream |
| **Orchestrator mode + background-approval forwarding** | 🟢 **MiMo first-party** | `tool/session.ts` · `permission-forward-ref.ts` do not exist upstream |
| **Dream / Distill / Evolve** | 🟢 **MiMo first-party** | The corresponding agents and prompts do not exist upstream |
| **Token Efficient pipeline** | 🟢 **MiMo first-party** | `bash_token_efficient_*` does not exist upstream |
| **cron / inbox / task / team / history** | 🟢 **MiMo first-party** | All five directories do not exist upstream |
| **26 built-in skills** | 🟢 **MiMo first-party** | Upstream `skill/` has only 2 files |
| **Family-split prompt routing** | 🟡 **inherited + expanded** | Upstream 8 files + same-structure `if` chain; MiMo added 5, rewrote 3, added dual-ID fallback |
| **GPT tool gating** | 🟡 **inherited + expanded** | Predicate expression byte-for-byte identical; MiMo extracted it as a function and expanded the scope into a wholesale ABI |
| **Sandbox script orchestration** | 🟡 **inherited + rewritten** | The idea comes from `code-mode`; MiMo swapped the engine (acorn→QuickJS) and the exposure surface (MCP→host tools) |
| **`apply_patch` tool** | 🔵 **already-upstream** | `tool/apply_patch.ts` upstream 11 019 B, MiMo 11 226 B (≈ unchanged) |
| **Effect service layer / SQLite / provider abstraction / TUI stack / ACP** | 🔵 **already-upstream** | Inherited wholesale |

**Stats**: 13 first-party · 3 inherited-then-heavily-changed · lots of infrastructure inherited.

> 🧠 **One sentence**: MiMo’s modification of OpenCode is **not making it a bit more usable; it is swapping the product proposition**. What upstream asks is “how to make an agent a service any client can consume”; what MiMo asks is “**how to let an agent work several hours by itself without going off the rails**.” Of the 13 first-party items, 6 are anti-self-deception, 7 support long-horizon and parallelism—those two groups together are the entire answer to that new proposition.

### 20.12 Three lessons this fork gives similar projects

**① First think clearly whether what you are forking is a “skeleton” or a “product.”** What MiMo inherited is the Effect service layer, persistence, provider abstraction, TUI stack—**all skeleton**. Everywhere that embodies a product judgment (prompt content, tool surface, permission line, content library) it redid. **A skeleton can be borrowed; a product judgment cannot.**

**② The modification center of gravity exposes product position.** Look at which directories bloated and which shrank, and you know what this fork wants to become. `server/` cut forty percent + `cli/` up 2.6× = “do not be a platform, be a terminal product”; adding 12 long-horizon/parallel subsystems = “from one conversation to one job of work.”

**③ Concentrating the change in one file is a reasonable fork tactic, but you have to keep the books.** `prompt.ts` growing to 4 595 lines has a reason—**stuffing new mechanisms onto someone else’s skeleton, concentrating the change in one place is far faster than first refactoring upstream’s layering.** But this debt will roll, and the longer the fork the harder it is to repay (upstream is still moving). MiMo left many `TODO: lift to mimocode.json config` in comments, which shows the team itself knows.

---

## Appendix A · Source-guide index

> Paths are all relative to `参考项目/MiMo-Code/packages/opencode/src/` (unless otherwise noted).

| Want to see | Go to this file |
|---|---|
| Main loop (including goalGate, the various gates) | `session/prompt.ts` (4 595 lines) |
| Goal judge | `session/goal.ts` (233 lines, `JUDGE_SYSTEM` at `:64-73`) |
| goalGate’s four escape hatches | `session/prompt.ts:2505-2600` |
| Threshold constants and signature algorithm | `session/prompt.ts:169-215` |
| High-pressure prompt debounce | `session/prompt.ts:216-243` |
| Empty-argument tool-call detection | `session/prompt/empty-step-detection.ts` |
| Text-replay detection | `session/prompt/text-loop-recovery.ts` |
| n-gram detection | `session/prompt/text-ngram-detection.ts` |
| Max-steps prompt | `session/prompt/max-steps.txt` |
| Try-Best detector | `session/try-best-detector.ts` |
| Checkpoint validation / retry | `session/checkpoint-validator.ts` · `session/checkpoint-retry.ts` |
| Prompt routing (17 lines decide everything) | `session/system.ts:26-39` |
| Environment block and cache prefix | `session/system.ts:66-90` |
| No-vision-capability degradation | `session/system.ts:105-118` |
| 20 family-split prompts | `session/prompt/*.txt` |
| Checkpoint system | `session/checkpoint.ts` (1 648 lines) |
| Over-long message truncation + surrogate-pair repair | `session/checkpoint.ts:52-69` |
| Memory retrieval and BM25 relative floor | `memory/service.ts:71-129` |
| FTS table structure | `memory/fts.sql.ts` |
| Memory rebuild | `memory/reconcile.ts` · `memory/fts-query.ts` |
| GPT toolset predicate | `tool/gpt.ts` (13 lines) |
| Tool assembly | `tool/registry.ts:375-410` |
| `exec` tool | `tool/tool-script.ts:303` · `tool/tool-script-ref.ts` |
| QuickJS sandbox | `workflow/sandbox.ts:95-160` |
| Built-in workflow registration (null prototype + fail at boot) | `workflow/builtin.ts` |
| Workflow runtime | `workflow/runtime.ts` (1 607 lines) |
| FORCED_ASK | `permission/index.ts:188-195` |
| Tool-group aliases and findLast | `permission/index.ts:591-614` |
| Orchestrator’s session tool | `tool/session.ts` (53 KB, `KNOWN_VERBS`) |
| Background-approval routing | `decideAskRouting` in `agent/config.ts` |
| Sub-agent dispatch | `actor/spawn.ts` (1 010 lines) |
| cron scheduling | `cron/scheduler.ts` · `cron-lock.ts` · `sentinel.ts` |
| inbox | `inbox/inbox.ts` |
| Sub-agent prompts | `agent/prompt/{checkpoint-writer,dream,distill,gpt-tools,compaction,explore}.txt` |

## Appendix B · Key-document index

| Document | Why it is worth reading |
|---|---|
| `docs/architecture/codex-microkernel-runtime.md` | Complete explanation of the GPT tool ABI + `exec` microkernel, **including self-admitted unfixed defects** (5 language versions) |
| `docs/harness/MiMo Orchestrator Mode.md` (13 KB) | Complete design of one window managing every task, including the three-way split of background-approval routing |
| `docs/harness/MiMo Token Efficient Mode.md` (9 KB) | Five-layer cleaning pipeline + never-worse gate + 14-regex cheat sheet |
| `docs/harness/Agent Multi-Skill Workflow Orchestration Design.md` | Multi-skill auto-orchestration |
| `docs/harness/Mix of Harness and Hand-off.md` | Mixed mode of harness and hand-off |
| `docs/compose/spec/` · `plans/` · `reports/` | **Self-bootstrap evidence**: this project itself was developed with the compose flow; specs/plans/reports are all in the tree |
| `README.md` (including `README.zh.md`) | Full feature table + config + the role of every flag |
| `USE_RESTRICTIONS.md` | Use restrictions beyond MIT, including “do not autonomously execute high-risk actions without supervision” |
| `AGENTS.md` · `CLAUDE.md` | The repo’s own notes to agents |

---

*This lecture was written against `XiaomiMiMo/MiMo-Code` commit `076b790` (2026-07-27). Every `file:line` citation can be verified back at `参考项目/MiMo-Code/`; statistics (file counts, byte counts, per-directory entry counts) were computed by a full traversal of the GitHub Git Tree API (**5 222 blobs, `truncated: false`**).*
