# 怎么读这份源码

> 仓库布局与目录地图、AGENTS.md 约定、沿用旧版的 Effect 写法、构建期宏、开关与实验配置、新增的数据表、从源码运行，以及几条阅读路线。

- 作者：David（道雾轩）
- 专栏：MiMo Code 源码解读（https://daiw.org/manual/mimo-code.md）
- 最后更新：2026-09-17
- 原文：https://daiw.org/manual/mimo-code/reading-the-source
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

读 MiMo Code 有两道坎。一是继承自 OpenCode 的 Effect 写法，核心模块大多是 Effect 的 Service；二是新功能大多挤在少数几个大文件里：`packages/opencode/src/session/prompt.ts` 有 5647 行，其中约 790 行是注释（上游同文件 1844 行，注释只有十来行）。注释里记着大量设计理由，是读懂代码的好帮手，但也意味着要先搭好地图再下潜。

## 仓库布局

根 `package.json` 的 `workspaces` 声明了 `packages/*`、`packages/console/*`、`packages/sdk/js` 与 `packages/slack`（`package.json:23`），实际带 `package.json` 的工作区包有 18 个。按 git 跟踪的 `.ts`、`.tsx` 数（排除测试），全仓 1433 个文件、302077 行：

| 包 | npm 名 | 文件 | 行数 | 职责 |
| --- | --- | --- | --- | --- |
| `packages/opencode` | `@mimo-ai/cli` | 688 | 142384 | 引擎、服务端与 TUI，本书的主角；`src/` 占 674 个文件、141012 行，其余是构建脚本与配置 |
| `packages/app` | `@mimo-ai/app` | 180 | 52609 | Web 应用界面（不维护） |
| `packages/console/*` | 五个包 | 192 | 36107 | 控制台（console）相关 |
| `packages/ui` | `@mimo-ai/ui` | 179 | 29919 | 界面组件库 |
| `packages/sdk/js` | `@mimo-ai/sdk` | 41 | 22407 | 由服务端 OpenAPI 生成的 JS SDK，TUI 也用它 |
| `packages/web` | `@mimo-ai/web` | 18 | 6949 | Astro 站点 |
| `packages/desktop` | `@mimo-ai/desktop` | 47 | 3445 | 桌面端（不维护） |
| `packages/shared` | `@mimo-ai/shared` | 19 | 1451 | 文件系统、全局目录解析等共享工具 |
| `packages/plugin` | `@mimo-ai/plugin` | 7 | 1422 | 插件 SDK |
| 其余 | — | 62 | 5384 | `enterprise`、`storybook`、`function`、`slack`、`script` 五个包，以及 `packages/containers` 与根目录的 `script/`、`infra/` 等 |

`packages/opencode` 一个包就占了全仓 47% 的行数。本书几乎只读它，偶尔引用 `packages/shared` 与 `packages/plugin`。

## src 目录地图

`packages/opencode/src` 下的一级目录可以按来源分成两类。上游原有的目录（MiMo 在其中不少地方动了大手术）：

| 分组 | 目录 | 对应的 OpenCode 篇目 |
| --- | --- | --- |
| 入口与界面 | `index.ts`、`cli/`（TUI 在 `cli/cmd/tui/`）、`server/`、`acp/` | [启动链路](https://daiw.org/manual/opencode/startup)、[HTTP 服务端](https://daiw.org/manual/opencode/server)、[终端界面](https://daiw.org/manual/opencode/tui) |
| 会话引擎 | `session/`、`agent/`、`tool/`、`permission/`、`question/`、`command/` | [主循环 SessionPrompt](https://daiw.org/manual/opencode/prompt-loop)、[工具契约与注册表](https://daiw.org/manual/opencode/tool-contract)、[权限系统](https://daiw.org/manual/opencode/permission) |
| 模型与扩展 | `provider/`、`mcp/`、`plugin/`、`skill/`、`lsp/`、`format/` | [提供方适配层](https://daiw.org/manual/opencode/provider-transform)、[MCP 集成](https://daiw.org/manual/opencode/mcp)、[插件系统](https://daiw.org/manual/opencode/plugins) |
| 基础设施 | `effect/`、`bus/`、`sync/`、`storage/`、`config/`、`flag/`、`global/`、`project/`、`snapshot/`、`worktree/`、`util/` 等 | [事件与同步](https://daiw.org/manual/opencode/events-sync)、[配置系统](https://daiw.org/manual/opencode/config)、[快照与撤销](https://daiw.org/manual/opencode/snapshot-revert) |

MiMo 新增的十个目录：

| 分组 | 目录 | 本书篇目 |
| --- | --- | --- |
| 多智能体 | `actor/`、`inbox/`、`team/` | [Actor](https://daiw.org/manual/mimo-code/actors)、[收件箱、团队与进度检查](https://daiw.org/manual/mimo-code/inbox-team) |
| 自主运行 | `task/`、`cron/` | [树状任务](https://daiw.org/manual/mimo-code/tasks)、[定时任务与循环](https://daiw.org/manual/mimo-code/cron) |
| 记忆 | `memory/`、`history/` | [分层记忆](https://daiw.org/manual/mimo-code/memory)、[历史轨迹与全文检索](https://daiw.org/manual/mimo-code/history) |
| 编排 | `workflow/` | [Workflow 运行时](https://daiw.org/manual/mimo-code/workflow-runtime) |
| 服务与统计 | `llm-server/`、`metrics/` | [内置 LLM 服务](https://daiw.org/manual/mimo-code/llm-server) |

`session/` 虽是上游目录，新文件却最多，按文件名前缀就能认出归属：`checkpoint*.ts` 与 `tail-digest.ts` 属于第 1 部分的检查点与重建，`prune.ts`、`prefix-snapshot.ts`、`llm-request-prefix.ts` 管修剪与请求前缀，`goal.ts`、`max-mode.ts`、`classify.ts`、`try-best-detector.ts` 与 `prompt/loop-streak.ts` 属于第 3 部分，`claude-import.ts`、`codex-import.ts`、`opencode-import.ts` 属于[迁移](https://daiw.org/manual/mimo-code/imports)。

## 先看哪些文档

- 根 `AGENTS.md`（`CLAUDE.md` 是指向它的符号链接）与 `packages/opencode/test/AGENTS.md`（测试夹具 `tmpdir` 的用法）。
- `docs/harness/` 下四篇设计文档，每篇有五种语言，不带语言后缀的是中文版；`docs/architecture/` 下是面向 GPT 模型的“Codex 微内核运行时”概述与提供方重试协调器的设计；`docs/compose/spec/` 下 22 篇功能规格，比如 `loop-streak-recovery.md`、`checkpoint-writer-safety.md`、`plan-enter-removal.md`。
- 文档和代码冲突时以代码为准。另外要知道：`agent.ts`、`prompt.ts`、`checkpoint.ts` 等 6 个源文件的注释引用了 `docs/superpowers/specs/` 下的设计文档，这个目录不在开源仓库里。

仓库根还有一个 `.mimocode/` 目录，是开发 MiMo 自己时用的项目配置：`mimocode.jsonc` 禁止编辑 `packages/opencode/migration/*`，`skills/drive-mimo` 教智能体用 `mimo run` 或 tmux 驱动另一个 mimo 进程做测试。

## AGENTS.md 里的约定

风格部分与上游一脉相承：能放在一个函数里就不拆，少用 `try`/`catch`、`any` 与 `else`，不做不必要的解构，优先 `const`、三元与提前返回，Drizzle 字段用 snake_case。MiMo 版本多了几条值得注意的：

- 依赖只用 `bun ci` 安装，不许 `bun install`，避免改动 `bun.lock`（`AGENTS.md:11`）。
- 运行时分层：TUI 里可以放心用 `Bun.file()` 这类 Bun API；但从 `packages/opencode/src/node.ts` 能走到的核心代码还要经 `packages/opencode/script/build-node.ts` 打包，在纯 Node 上运行，所以有 Node 等价物时优先用 Node 的（`AGENTS.md:25`）。
- 读可空列的专门规则：`.get()` 没查到行时给 `undefined`，列值为 SQL `NULL` 时给 `null`，只关心“有没有值”时用带类型标注的 `?? undefined` 抹平，判断时用真值或 `== null`，不要用 `=== undefined`（`AGENTS.md:107`）。
- 测试尽量不 mock，不能在仓库根跑（根 `package.json` 的 `test` 脚本直接报错退出），类型检查在包目录里跑 `bun typecheck`。

## Effect：还是 v1.14.19 那一套

基本形状和[《OpenCode 源码解读》的“怎么读这份源码”](https://daiw.org/manual/opencode/reading-the-source)一致：`Context.Service` 定义接口，`Layer.effect` 提供实现，对外方法用 `Effect.fn("Domain.method")` 命名，按目录隔离的状态放进 `InstanceState`（`packages/opencode/src/effect/instance-state.ts:38`，同样基于 `ScopedCache`），文件末尾一行 `export * as X from "./x"` 自我重导出。搜 `Effect.fn("SessionPrompt.` 仍是找主循环方法的好办法。

但那本书描述的 v1.18.30 已经换了依赖组装方式，MiMo 没有跟进：代码里找不到 `LayerNode`、`serviceUse` 与 `RuntimeFlags`。每个服务自己导出 `defaultLayer`，用 `Layer.provide` 手工接好依赖（`packages/opencode/src/agent/agent.ts:636`）：

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

export * as Agent from "./agent"
```

应用级的 `AppLayer` 再用 `Layer.mergeAll` 把五十多个服务的 layer 并起来交给 `ManagedRuntime.make`（`packages/opencode/src/effect/app-runtime.ts:66`）。其中 MCP、Command、SessionPrompt 与 Actor 四个有状态服务按所有权单独串成一条链，保证各自只被提供一次（`app-runtime.ts:120`）：

```ts
    // MCP, Command, SessionPrompt, and Actor form one ownership chain. Their
    // standalone default layers remain convenient for focused tests, while
    // the application graph deliberately provides each stateful service once.
    Actor.appLayer.pipe(
      Layer.provideMerge(SessionPrompt.appLayer.pipe(
        Layer.provideMerge(Command.appLayer.pipe(Layer.provideMerge(MCP.defaultLayer))),
      )),
    ),
```

服务之间真正成环时怎么办？MiMo 的办法是“迟绑定引用”：一个模块级的 `{ current: ... }` 对象，通常由服务的 layer 在初始化时填入，调用方运行时再去读，从类型上切断 Layer 依赖。新增的 6 个 `*-ref.ts` 都是这类模块级引用，比如 `SessionCheckpoint` 要拉起检查点写手，而 Actor 又间接依赖 `SessionCheckpoint`，于是改读 `spawnRef`（`packages/opencode/src/actor/spawn-ref.ts:1` 的注释写明了这条环）。读代码时遇到 `xxxRef.current` 为空就跳过的分支，多半是这个原因。

## 构建期宏

内置技能包和内置 Workflow 脚本不是运行时从磁盘读的，而是用 Bun 的宏在打包时内联进二进制。三个宏文件都在 `packages/opencode/src` 下：`skill/builtin/bundle.macro.ts`、`skill/compose/bundle.macro.ts` 与 `workflow/builtin.macro.ts`，导入处带 `with { type: "macro" }`（`packages/opencode/src/workflow/builtin.ts:3`）：

```ts
// A macro, not an import, so these function bodies never enter the module graph and no ESM
// parser can reach them. docs/compose/spec/bun-text-import-esm-collision.md explains why.
import { loadBuiltinScripts } from "./builtin.macro" with { type: "macro" }
import { loadBuiltinScripts as loadBuiltinScriptsDev } from "./builtin.macro"
```

同一个函数导入两次，是因为 `bun test` 会去掉宏导入却不替换调用点，此时调用抛 `ReferenceError`，`safeLoadBuiltinScripts` 捕获后退回普通导入（`builtin.ts:19`）。技能包的处理更进一步：打进二进制的是文件内容，技能服务加载技能时再解压到数据目录的 `builtin_skills/<版本>/skills` 下（`packages/opencode/src/skill/builtin/extract.ts:71`），用 `.extracted` 标记文件避免重复解压；`MIMOCODE_DISABLE_OFFICIAL_SKILLS` 会跳过办公类技能，`claude-code`、`codex` 两个技能只在本机装了对应命令时才解压（`extract.ts:20`）。compose 技能则解压到 `compose/<版本>/skills`（`packages/opencode/src/skill/compose/extract.ts:32`）。

## 开关与实验配置

环境变量开关集中在 `packages/opencode/src/flag/flag.ts`，直接读 `process.env`。多数键在模块加载时求值；少数写成 getter（注释里常见“Read lazily”），是为了让测试或进程内嵌入方在运行时改环境变量后立刻生效，例如 `MIMOCODE_DISABLE_CHECKPOINT`（`packages/opencode/src/flag/flag.ts:142`）。几点规律：

- 总开关 `MIMOCODE_EXPERIMENTAL`（`flag.ts:37`）会一并打开这几项（均省略 `MIMOCODE_` 前缀）：`EXPERIMENTAL_ICON_DISCOVERY`、`ENABLE_EXA`、`EXPERIMENTAL_OXFMT`、`EXPERIMENTAL_LSP_TOOL`、`EXPERIMENTAL_MCP_TOOL_SEARCH`、`EXPERIMENTAL_ORCHESTRATOR`、`EXPERIMENTAL_WORKFLOW_TOOL`、`EXPERIMENTAL_WORKSPACES`。
- 大多数新功能默认关，但有几个用 `!falsy(...)` 写成了默认为真：使用统计 `MIMOCODE_ENABLE_ANALYSIS`（`flag.ts:108`）、定时任务 `MIMOCODE_EXPERIMENTAL_CRON`（`flag.ts:366`）、所有渠道共用一个数据库的 `MIMOCODE_DISABLE_CHANNEL_DB`（`flag.ts:390`），只有显式设为 `false` 或 `0` 才会变成假。
- 不是所有开关都进了 `Flag`：比如启动时导入 Claude Code 会话的 `MIMOCODE_DISABLE_CLAUDE_IMPORT` 就在 `packages/opencode/src/index.ts:163` 直接读环境变量。

配置文件这边，`packages/opencode/src/config/config.ts` 的 `experimental` 段在上游的基础上加了四项：`try_best`（`packages/opencode/src/config/config.ts:418`）、`loop_streak_recovery`（`config.ts:434`）、`predict_next_prompt`（`config.ts:450`）与 `maxMode`（`config.ts:454`）。顶层还新增了一批配置段：

| 配置段 | 管什么 | 篇目 |
| --- | --- | --- |
| `checkpoint` | 检查点阈值与预留量、重建上下文各部分的 token 上限、任务归档天数 | [周期](https://daiw.org/manual/mimo-code/cycles)、[上下文重建与预算化注入](https://daiw.org/manual/mimo-code/context-rebuild) |
| `memory`、`history` | 记忆写入开关、轨迹全文索引 | [分层记忆](https://daiw.org/manual/mimo-code/memory)、[历史轨迹与全文检索](https://daiw.org/manual/mimo-code/history) |
| `dream`、`distill` | 是否自动运行、最少间隔天数 | [Dream 与 Distill](https://daiw.org/manual/mimo-code/dream-distill) |
| `compose`、`workflow` | Compose 文档目录、Workflow 并发与深度上限 | [Compose](https://daiw.org/manual/mimo-code/compose)、[Workflow 运行时](https://daiw.org/manual/mimo-code/workflow-runtime) |
| `tool` | 工具调用风格（JSON 或命令行式） | [命令行式工具调用与 Token 精简](https://daiw.org/manual/mimo-code/token-efficient-shell) |
| `auto_worktree` | 改动主工作树后的一次性提醒 | [Git 隔离与安全护栏](https://daiw.org/manual/mimo-code/git-guards) |
| `vision_model`、`model_groups`、`retry` | 视觉模型、模型分组、重试策略 | [模型、提供方与提示词路由](https://daiw.org/manual/mimo-code/providers) |
| `voice` | 语音输入用的模型 | [终端体验](https://daiw.org/manual/mimo-code/tui) |

## 新增的数据表

MiMo 沿用 Drizzle 与 SQLite，新增了 8 个 `*.sql.ts`：

| 表 | 定义 | 存什么 |
| --- | --- | --- |
| `actor_registry` | `packages/opencode/src/actor/actor.sql.ts:6` | 每个（会话，actor）一行：模式、状态、生命周期、上下文水位、轮次与最近活动时间 |
| `inbox` | `packages/opencode/src/inbox/inbox.sql.ts:5` | 发给某个（会话，actor）、还没被取走的消息 |
| `task`、`task_event` | `packages/opencode/src/task/task.sql.ts:5` | 树状任务及其事件流水 |
| `workflow_run` | `packages/opencode/src/workflow/workflow.sql.ts:6` | Workflow 运行状态、当前阶段与成败计数 |
| `memory_fts` | `packages/opencode/src/memory/fts.sql.ts:3` | 记忆文件全文索引的内容表 |
| `history_fts` | `packages/opencode/src/history/fts.sql.ts:3` | 会话部件全文索引的内容表 |
| `permission_grant` | `packages/opencode/src/permission/permission.sql.ts:14` | Orchestrator 对子会话的授权，重启后仍有效 |
| `external_import` | `packages/opencode/src/session/external-import.sql.ts:6` | 从 Claude Code、Codex、OpenCode 导入会话的去重记录 |

上游的 `session.sql.ts` 也改了：新表 `session_prefix_snapshot` 按提供方、模型、智能体、权限等组合冻结系统提示词与工具 schema（`packages/opencode/src/session/session.sql.ts:63`）；`session` 表多了 `context_from`、`context_watermark`、`last_checkpoint_message_id` 等列，`message` 表多了默认值为 `main` 的 `agent_id` 列（`session.sql.ts:91`）——子智能体与主智能体共用会话 ID，靠这一列把消息切成各自的片。两个 FTS5 虚表 `memory_fts_idx`、`history_fts_idx` 及其同步触发器不在 Drizzle schema 里，而是直接写在迁移 SQL 里。`packages/opencode/migration/` 共有 42 个迁移目录，时间戳晚于 v1.14.19 发布日期的有 30 个。

## 从源码跑起来

根 `package.json` 的脚本（`package.json:9`）：

```json
    "dev": "bun run --cwd packages/opencode script/dev.ts",
    "build:local": "bun run --cwd packages/opencode build:local",
    "dev:desktop": "bun --cwd packages/desktop dev",
    "dev:web": "bun --cwd packages/app dev",
```

- `bun ci` 装依赖，`bun run dev` 启动。`packages/opencode/script/dev.ts` 默认把 `MIMOCODE_HOME` 指到仓库根的 `.dev-home`（`packages/opencode/script/dev.ts:29`），开发时不会碰到本机真实的配置与数据；它收到的命令行参数会原样转给 `packages/opencode/src/index.ts`（`dev.ts:26`）。
- 这个脚本和 `script/build.ts` 都会检查检出目录旁边有没有 `../../mimoapi/packages/opencode/src/ext` 覆盖层，有就拷进 `src/ext`（`dev.ts:12`）。开源检出里没有它，脚本照常运行；`src/ext` 本身被 `.gitignore` 忽略。
- `bun run build:local` 以 `local` 渠道和版本号、带 `--single` 参数调用 `script/build.ts`，只构建当前平台的二进制；`bun turbo typecheck` 做全仓类型检查；`dev:web`、`dev:desktop` 还在，但按 `AGENTS.md` 的说法这两个界面目前不维护。

## 推荐阅读路线

路径均相对 `packages/opencode/src`：

| 路线 | 依次读 | 对应篇目 |
| --- | --- | --- |
| 主循环 | `session/prompt.ts`（先找 `Effect.fn("SessionPrompt.run")`）、`session/classify.ts`、`session/processor.ts`、`session/llm.ts` | [一条消息的旅程](https://daiw.org/manual/mimo-code/message-lifecycle)、[复读检测与重试](https://daiw.org/manual/mimo-code/loop-recovery) |
| 长程上下文 | `session/prune.ts`（`fireCheckpoints`）、`session/checkpoint.ts`、`session/message-v2.ts`（`filterCompacted`）、`session/tail-digest.ts` | 第 1 部分四篇 |
| 多智能体 | `tool/actor.ts`、`actor/spawn.ts`、`actor/registry.ts`、`inbox/inbox.ts`、`tool/session.ts` | 第 4 部分三篇 |
| 编排 | `tool/workflow.ts`、`workflow/runtime.ts`、`workflow/sandbox.ts`、`workflow/builtin/*.js`、`tool/tool-script.ts` | 第 5 部分 |
| 记忆与进化 | `memory/paths.ts`、`memory/service.ts`、`tool/memory.ts`、`history/service.ts`、`session/auto-dream.ts` | 第 2 部分三篇 |

下一篇：[一条消息的旅程](https://daiw.org/manual/mimo-code/message-lifecycle)——跟着一条用户消息穿过 TUI、服务端与主循环，看 MiMo 在哪些地方挂进了自己的机制。
