怎么读这份源码

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

作者 David更新于 第 3 篇(共 33 篇)

读 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/cli688142384引擎、服务端与 TUI,本书的主角;src/ 占 674 个文件、141012 行,其余是构建脚本与配置
packages/app@mimo-ai/app18052609Web 应用界面(不维护)
packages/console/*五个包19236107控制台(console)相关
packages/ui@mimo-ai/ui17929919界面组件库
packages/sdk/js@mimo-ai/sdk4122407由服务端 OpenAPI 生成的 JS SDK,TUI 也用它
packages/web@mimo-ai/web186949Astro 站点
packages/desktop@mimo-ai/desktop473445桌面端(不维护)
packages/shared@mimo-ai/shared191451文件系统、全局目录解析等共享工具
packages/plugin@mimo-ai/plugin71422插件 SDK
其余—625384enterprise、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,列值为 SQL NULL 时给 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、workflowCompose 文档目录、Workflow 并发与深度上限Compose、Workflow 运行时
tool工具调用风格(JSON 或命令行式)命令行式工具调用与 Token 精简
auto_worktree改动主工作树后的一次性提醒Git 隔离与安全护栏
vision_model、model_groups、retry视觉模型、模型分组、重试策略模型、提供方与提示词路由
voice语音输入用的模型终端体验

新增的数据表

MiMo 沿用 Drizzle 与 SQLite,新增了 8 个 *.sql.ts:

表定义存什么
actor_registrypackages/opencode/src/actor/actor.sql.ts:6每个(会话,actor)一行:模式、状态、生命周期、上下文水位、轮次与最近活动时间
inboxpackages/opencode/src/inbox/inbox.sql.ts:5发给某个(会话,actor)、还没被取走的消息
task、task_eventpackages/opencode/src/task/task.sql.ts:5树状任务及其事件流水
workflow_runpackages/opencode/src/workflow/workflow.sql.ts:6Workflow 运行状态、当前阶段与成败计数
memory_ftspackages/opencode/src/memory/fts.sql.ts:3记忆文件全文索引的内容表
history_ftspackages/opencode/src/history/fts.sql.ts:3会话部件全文索引的内容表
permission_grantpackages/opencode/src/permission/permission.sql.ts:14Orchestrator 对子会话的授权,重启后仍有效
external_importpackages/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 在哪些地方挂进了自己的机制。

本页目录