怎么读这份源码
仓库布局与目录地图、AGENTS.md 约定、沿用旧版的 Effect 写法、构建期宏、开关与实验配置、新增的数据表、从源码运行,以及几条阅读路线。
读 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/ | 启动链路、HTTP 服务端、终端界面 |
| 会话引擎 | session/、agent/、tool/、permission/、question/、command/ | 主循环 SessionPrompt、工具契约与注册表、权限系统 |
| 模型与扩展 | provider/、mcp/、plugin/、skill/、lsp/、format/ | 提供方适配层、MCP 集成、插件系统 |
| 基础设施 | effect/、bus/、sync/、storage/、config/、flag/、global/、project/、snapshot/、worktree/、util/ 等 | 事件与同步、配置系统、快照与撤销 |
MiMo 新增的十个目录:
| 分组 | 目录 | 本书篇目 |
|---|---|---|
| 多智能体 | actor/、inbox/、team/ | Actor、收件箱、团队与进度检查 |
| 自主运行 | task/、cron/ | 树状任务、定时任务与循环 |
| 记忆 | memory/、history/ | 分层记忆、历史轨迹与全文检索 |
| 编排 | workflow/ | Workflow 运行时 |
| 服务与统计 | llm-server/、metrics/ | 内置 LLM 服务 |
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 属于迁移。
先看哪些文档
- 根
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,列值为 SQLNULL时给null,只关心“有没有值”时用带类型标注的?? undefined抹平,判断时用真值或== null,不要用=== undefined(AGENTS.md:107)。 - 测试尽量不 mock,不能在仓库根跑(根
package.json的test脚本直接报错退出),类型检查在包目录里跑bun typecheck。
Effect:还是 v1.14.19 那一套
基本形状和《OpenCode 源码解读》的“怎么读这份源码”一致: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):
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):
// 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):
// 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 上限、任务归档天数 | 周期、上下文重建与预算化注入 |
memory、history | 记忆写入开关、轨迹全文索引 | 分层记忆、历史轨迹与全文检索 |
dream、distill | 是否自动运行、最少间隔天数 | Dream 与 Distill |
compose、workflow | Compose 文档目录、Workflow 并发与深度上限 | Compose、Workflow 运行时 |
tool | 工具调用风格(JSON 或命令行式) | 命令行式工具调用与 Token 精简 |
auto_worktree | 改动主工作树后的一次性提醒 | Git 隔离与安全护栏 |
vision_model、model_groups、retry | 视觉模型、模型分组、重试策略 | 模型、提供方与提示词路由 |
voice | 语音输入用的模型 | 终端体验 |
新增的数据表
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):
"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 | 一条消息的旅程、复读检测与重试 |
| 长程上下文 | 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 部分三篇 |
下一篇:一条消息的旅程——跟着一条用户消息穿过 TUI、服务端与主循环,看 MiMo 在哪些地方挂进了自己的机制。