# Dream 与 Distill

> /dream 与 /distill 从哪里触发、自动运行的真实条件、两个隐藏智能体的权限与写入沙箱、提示词要点，以及整理出的记忆和打包出的资产落在哪里。

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

检查点写手只盯着当前会话。跨会话的整理交给两个隐藏智能体：`dream` 回看最近的会话，把经得起核对的知识并进项目 `MEMORY.md`；`distill` 回看更长的时间窗，找出反复出现的手工流程，打包成 skill、子智能体或命令。官方博客把它们称为进化层。拆开看，每一个都只是三样东西的组合：一条斜杠命令、一个带写入沙箱的隐藏 agent、一份很长的提示词。上游 v1.14.19 一样都没有，它的内置命令只有 `init` 和 `review` 两条。

## 怎么用

- 手动：`/dream [关注点]`、`/distill [关注点]`。两条命令登记在 `packages/opencode/src/command/index.ts:130` 和 `index.ts:150`，参数填进模板里的 `$ARGUMENTS`，作为用户给出的关注点或约束。
- 两条命令都声明了 `subtask: false`。按 `SessionPrompt.command` 的规则（`packages/opencode/src/session/prompt.ts:5185`），subagent 模式的 agent 遇到显式的 `false` 就不包成子任务，模板直接作为当前会话的一条用户消息，由 `dream` 或 `distill` 自己处理，整理过程和最终汇总都出现在当前对话里。
- 模型：手动命令优先用 agent 配置里的 `provider/model` 形式的模型，否则沿用当前会话的模型（`prompt.ts:5163`）；自动运行则固定沿用触发那一轮用户消息的模型（`prompt.ts:3779`），显式传入的模型优先于 agent 配置（`prompt.ts:2449`）。
- 自动运行：在配置里写 `"dream": { "auto": true }`、`"distill": { "auto": true }`，间隔用 `interval_days` 调，默认 7 天和 30 天（`packages/opencode/src/session/auto-dream.ts:11`）。
- `memory.disable_write` 为真时两种自动运行都跳过（`auto-dream.ts:113`）。手动命令不受这个开关拦截，但 Dream 写 `MEMORY.md` 会被写入闸门拒绝；Distill 写 `.mimocode/` 不在记忆树里，不受影响，闸门细节见[分层记忆](https://daiw.org/manual/mimo-code/memory)。
- 自动运行建出的会话标题固定为 `Auto Dream`、`Auto Distill`（`auto-dream.ts:15`），会话列表里带一个“自动”徽标（`packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx:201`），`mimo run --continue` 和 TUI 的 `-c` 找“上一个会话”时都会跳过它们（`packages/opencode/src/cli/cmd/run.ts:389`、`packages/opencode/src/cli/cmd/tui/app.tsx:441`）。

<Callout type="warn">
官方博客写 Dream 每 7 天、Distill 每 30 天自动触发，内置文档技能 `mimocode-docs` 的配置表也把 `dream.auto`、`distill.auto` 的默认值写成 true（`packages/opencode/src/skill/builtin/.bundle/mimocode-docs/reference/config.md:136`）。代码里 7 和 30 只是间隔的默认值，开关必须是字面量 `true` 才算打开（`auto-dream.ts:114`、`auto-dream.ts:127`），配置 schema 也注明默认 false（`packages/opencode/src/config/config.ts:375`）。不改配置，两者都不会自动运行。
</Callout>

## 自动触发

检查发生在主循环里：顶层会话（没有父会话）每次启动 runLoop 后的第一步，依次调用 `shouldAutoDream` 与 `shouldAutoDistill`（`prompt.ts:3775`）。配置说明称在新会话开始时触发，实际上每轮第一步都会检查，只是被间隔挡住了。Dream 这一侧的判断（`auto-dream.ts:110`）：

```ts
export function shouldAutoDream(cfg: Config.Info) {
  // Memory writing off → the consolidation pass that rewrites project memory
  // must not run either.
  if (!isMemoryWriteEnabled(cfg)) return Effect.succeed(false)
  const enabled = cfg.dream?.auto === true
  if (!enabled) return Effect.succeed(false)
  const now = Date.now()
  if (now - lastDreamSpawnTime < MIN_SPAWN_GAP_MS) return Effect.succeed(false)
  lastDreamSpawnTime = now
  const intervalDays = cfg.dream?.interval_days ?? DEFAULT_DREAM_INTERVAL_DAYS
  return shouldAutoRun({ enabled, intervalDays, title: AUTO_DREAM_TITLE, label: "dream" })
}
```

```mermaid
flowchart TD
  T["顶层会话，runLoop 第 1 步"] --> W{"记忆写入开着？"}
  W -->|否| N["不触发"]
  W -->|是| A{"dream.auto 为 true？"}
  A -->|否| N
  A -->|是| G{"距本进程上次检查已满 10 秒？"}
  G -->|否| N
  G -->|是| L{"库里有标题为 Auto Dream 的会话？"}
  L -->|有| I{"距最近一次已满间隔？"}
  L -->|没有| Y{"最早的顶层会话已超过间隔？"}
  I -->|否| N
  Y -->|否| N
  I -->|是| S["新建 Auto Dream 会话，以 dream agent 发送 DREAM_TASK"]
  Y -->|是| S
```

`shouldAutoRun`（`auto-dream.ts:45`）里有几处值得注意：

- **会话标题就是运行记录**：上次运行时间取标题等于 `Auto Dream` 的最新会话的创建时间（`auto-dream.ts:56`），没有单独的锁或状态表。会话在 agent 开始干活之前就建好了，所以失败或被中断的一次同样算数。
- **第一次运行要等库够老**：从没运行过时，数据库里最早的顶层会话必须创建于一个间隔之前，否则记一条 `project too young` 日志后跳过（`auto-dream.ts:73`）。
- **范围是整台机器**：两条查询都没有按 `project_id` 过滤，而所有项目共用一个 `mimocode.db`。注释和日志里说的都是项目，实际效果却是：间隔到期后，哪个项目先有会话跑到这一步，就给哪个项目整理一次，其他项目要等下一个间隔。
- **进程内 10 秒闸门**：`MIN_SPAWN_GAP_MS`（`auto-dream.ts:13`）挡住同时启动的多个会话重复触发。时间戳在开关检查通过后就更新，新建的 Auto Dream 会话自己跑到第一步时，也会被这道闸门或间隔检查挡住，不会递归。

两边都通过后，主循环动态导入 `AppRuntime`（避开模块初始化的循环依赖），建会话、以 `dream` agent 发出 `DREAM_TASK`，不等待结果，出错只记日志（`prompt.ts:3784`）。Dream 与 Distill 各判各的，同一轮里可能一起触发。

## 两个隐藏智能体

`dream` 与 `distill` 定义在 `packages/opencode/src/agent/agent.ts:376` 与 `agent.ts:415`，都是 `mode: "subagent"`、`hidden: true`，按 Tab 切换不到。两者的权限和工具表完全相同，区别只在提示词。以 `dream` 为例（`agent.ts:383`）：

```ts
permission: Permission.merge(
  defaults,
  Permission.fromConfig({
    "*": "deny",
    read: "allow",
    write: "allow",
    edit: "allow",
    glob: "allow",
    grep: "allow",
    memory: "allow",
    bash: "allow",
    exec: "allow",
    external_directory: {
      [path.join(Global.Path.data, "memory")]: "allow",
      [path.join(Global.Path.data, "memory", "*")]: "allow",
    },
  }),
  user,
),
```

紧接着的 `toolAllowlist`（`agent.ts:402`）把模型能看到的工具收窄到 `read`、`write`、`edit`、`apply_patch`、`view_image`、`glob`、`grep`、`memory`、`bash`、`exec` 十个，没有 `actor`、`task`、`skill`，也没有联网和 `history`；其中 `apply_patch`、`view_image` 分别归入 `edit`、`read` 两组权限（`packages/opencode/src/permission/index.ts:700`）。和其他内置 agent 一样，配置文件 `agent` 段里同名的 `dream`、`distill` 条目可以改模型、提示词、工具白名单，也可以直接禁用（`agent.ts:456`）。

更硬的约束来自“系统派生 agent”这个身份。`SYSTEM_SPAWNED_AGENT_TYPES` 只有 `checkpoint-writer`、`dream`、`distill` 三个成员（`packages/opencode/src/agent/config.ts:7`），带来三点不同：

1. **权限询问一律非交互**：`decideAskRouting` 对它们直接返回 `interactive: false`（`packages/opencode/src/agent/config.ts:72`），需要询问的操作不弹框，当场以 `DeniedError` 失败（`packages/opencode/src/permission/index.ts:370`）。即便是你在自己会话里手动敲的 `/dream`，它也没法停下来问你。
2. **写入沙箱**：`write`、`edit`、`apply_patch`、`notebook_edit` 只能写记忆树或 `<worktree>/.mimocode/`（`packages/opencode/src/tool/memory-path-guard.ts:9`）。`bash` 不受这道闸门约束，提示词反复要求只做只读查询，靠的是模型自律。
3. **无效输出的纠正提示**：回复只有思考或为空时，追加的纠正提示按 actor 口径写，要求向父 agent 交付结果，而不是面向用户的主 agent 口径（`packages/opencode/src/agent/config.ts:30`、`prompt.ts:3384`）。

## Dream 的提示词

`packages/opencode/src/agent/prompt/dream.txt` 共 155 行，默认回看最近 7 天（`dream.txt:8`），注意这个时间窗与自动触发的间隔是两回事。核心立场是原始轨迹优先（`dream.txt:20`）：

> Raw trajectory is authoritative; memory files are a structured index/cache.

流程分六个阶段：

1. **定位数据**：先用 `memory` 工具搜几个宽泛的词，再从记忆根目录推出数据库 `<DATA>/mimocode.db`，留意 `MIMOCODE_DB` 覆盖；记忆为空且没有本项目会话就直接报告无事可做。
2. **定向**：读项目 `MEMORY.md` 和当前会话的 `notes.md`，Glob 出最近的 `checkpoint.md`，用 SQLite 列出本项目最近的会话，记下 `MEMORY.md` 现有的小节结构以免重复。
3. **从记忆文件收集候选**：检查点里的已发现知识、错误与修复、设计决策，`tasks/*/progress.md`，以及还没并入项目记忆的笔记。
4. **对照原始轨迹核实**：提示词给出了表结构说明和查询模板，让模型在用户发言里搜 always、never、remember、decided、every time 这类词，用户用别的语言时也搜对应的说法；只有明确的用户陈述、清楚的设计决策或跨会话重复出现的证据才能提升（`dream.txt:101`）。
5. **整合**：写入 `MEMORY.md` 的 `## Rules`、`## Architecture decisions`、`## Discovered durable knowledge`、`## Patterns`、`## Gotchas`（`dream.txt:119`），后两节是模板之外新加的。合并重复条目，相对日期换成绝对日期，每条一到三行，末尾保留来源会话 ID，如 `[ses_xxx]`。
6. **修剪与核验**（`dream.txt:137`）：

```text
- Keep `MEMORY.md` under 200 lines and 10KB when possible. Prefer fewer, denser entries over exhaustive notes.
- Remove entries superseded by newer decisions.
- Remove details that mattered only to one session.
- Remove low-signal memory files or entries that are redundant with stronger project memory.
- Clear current `notes.md` entries only when fully integrated.
- Verify mentioned file paths with Glob.
- Verify mentioned function/class names with Grep.
- Mark unverifiable-but-plausible claims `[unverified]`.
```

分工写得很清楚：发现值得打包的流程，最多留一行指向 `/distill` 的提示，不要自己去建 skill 或命令（`dream.txt:26`）。跨项目的偏好可以提升到 `global/MEMORY.md`（`dream.txt:27`）。最后按固定格式汇报新增、更新、删除、跳过的条目，以及项目记忆的行数与大小。

## Distill 的提示词

`packages/opencode/src/agent/prompt/distill.txt` 默认回看最近 30 天（`distill.txt:7`），证据优先级依次是原始轨迹、记忆文件、已有资产。它最强调的是克制（`distill.txt:39`）：

> If nothing has actually been repeated, create nothing. Doing zero packaging is a valid and expected outcome; just say so in the summary rather than manufacturing an asset to justify the run.

流程是先盘点已有资产，再找候选、核实、列清单、选形式、落盘。盘点用的 Glob 模式和 MiMo 的加载器一致：skill 是 `{skill,skills}/**/SKILL.md`（`packages/opencode/src/skill/index.ts:25`），命令是 `{command,commands}/**/*.md`（`packages/opencode/src/config/command.ts:29`），agent 是 `{agent,agents}/**/*.md` 与 `{mode,modes}/*.md`（`packages/opencode/src/config/agent.ts:129`），外加 `.claude`、`.agents`、`.codex`、`.opencode` 这几个外部目录里的 skill。候选至少出现两次，或者明显还会重复且每次代价高，才算数（`distill.txt:126`）。清单里每个候选要附证据和日期（引用会话 ID）、频率与置信度、推荐形式和理由。形式按从小到大挑（`distill.txt:152`）：

| 形式 | 落点 | 要点 |
| --- | --- | --- |
| skill | `.mimocode/skills/<name>/SKILL.md` | frontmatter 写 `name`、`description`，描述要可被检索到 |
| 自定义子智能体 | `.mimocode/agent/<name>.md` | frontmatter 写 `description`，可选 `mode`、`model`、工具与权限，正文是系统提示词 |
| 命令 | `.mimocode/command/<name>.md` | frontmatter 写 `description`，可选 `agent`，正文用 `$ARGUMENTS`、`$1` 占位 |
| 自动化 | `.mimocode/plugins/` 下的插件钩子 | 只有理由确凿时才写，否则打包成可重复执行的命令 |
| 扩展已有 | 原文件 | 改已有的 skill、agent 或命令，不新建近似副本 |
| 跳过 | 无 | 太一次性、太模糊、太敏感或证据不足 |

落盘后要用 Glob、Grep 核对引用的路径和符号，不做建账号、发消息、改权限这类不可逆的外部操作。

## 提示词与代码对不上的地方

两份提示词开头都写着（`dream.txt:10`、`distill.txt:10`）：

> This command is manual. The user intentionally started it and is watching.

但自动运行用的是同一个 agent、同一份系统提示词，只是用户消息换成了写着 automatic 的 `DREAM_TASK` 或 `DISTILL_TASK`（`auto-dream.ts:27`），这时并没有人在看。

两份提示词的表结构说明都写着（`dream.txt:76`、`distill.txt:102`）：

> Empty `agent_id` means main agent; non-empty `agent_id` means subagent.

而 `message.agent_id` 早已是非空列、默认值 `main`（`packages/opencode/src/session/session.sql.ts:91`），迁移时还把旧数据里的 NULL 统一回填成 `main`（`packages/opencode/migration/20260526000000_agent_id_main/migration.sql:1`）。照字面去查 `agent_id` 为空的消息，一条主 agent 消息也查不到。内置技能 `memory-search` 的说明倒是对的（`packages/opencode/src/skill/builtin/.bundle/memory-search/SKILL.md:49`）。

Distill 的自动化一节说 mimocode 没有内置调度器（`distill.txt:163`），可定时任务与循环调度默认开着，关掉要显式设 `MIMOCODE_EXPERIMENTAL_CRON=false`（`packages/opencode/src/flag/flag.ts:366`），见[定时任务与循环](https://daiw.org/manual/mimo-code/cron)。它还说用户要求全局范围时可以不写项目 `.mimocode/`（`distill.txt:177`），但写入沙箱只放行记忆树和工作区里的 `.mimocode/`，用 `write`、`edit` 往全局配置目录写会被拒绝。

## 产物落在哪里

| 来源 | 产物 | 位置 | 之后谁会读到 |
| --- | --- | --- | --- |
| Dream | 项目记忆条目 | `<data>/memory/projects/<pid>/MEMORY.md` | 重建注入、检查点写手、`memory` 检索 |
| Dream | 跨项目偏好 | `<data>/memory/global/MEMORY.md` | 重建注入 |
| Dream | 清空已并入的笔记 | 当前会话的 `notes.md` | 无 |
| Distill | skill、子智能体、命令、插件钩子 | 工作区根目录的 `.mimocode/` 下 | 对应的加载器 |

两者的产物都是普通文件：Dream 改的记忆下次重建时被注入，Distill 建的资产和手写的没有区别，可以审阅、修改、提交进仓库。自动运行不会征求确认，打开 `auto` 之前最好先手动跑一两次，看看它整理出来的东西合不合心意。

## evolve：在工作中随手进化

内置技能包里有个 `evolve` 技能（`packages/opencode/src/skill/builtin/.bundle/evolve/SKILL.md:3`），思路相近而形态不同。它不是定期批量回看，而是一条常驻指令：主 agent 在工作中看到信号就立刻改造自己，比如同一串 bash 或 API 调用跑了 3 次以上就包成工具（`SKILL.md:24`），同样的纠正反复出现就加钩子，值得长期保留的项目知识写成 skill，产物同样落在 `.mimocode/` 下。Distill 事后从轨迹数据库里找证据，受写入沙箱约束；`evolve` 靠模型当下的判断，用的是主 agent 的普通权限。技能包整体见[内置技能包与技能检索](https://daiw.org/manual/mimo-code/skills)。

下一篇：[Goal：让独立裁判决定何时停](https://daiw.org/manual/mimo-code/goal)——进入第 3 部分，看 `/goal` 怎样让另一次模型调用判定任务是否真的完成。
