Dream 与 Distill

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

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

检查点写手只盯着当前会话。跨会话的整理交给两个隐藏智能体: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/ 不在记忆树里,不受影响,闸门细节见分层记忆。
  • 自动运行建出的会话标题固定为 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)。

官方博客写 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)。不改配置,两者都不会自动运行。

自动触发

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

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" })
}
图表加载中…

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):

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):
- 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.mdfrontmatter 写 name、description,描述要可被检索到
自定义子智能体.mimocode/agent/<name>.mdfrontmatter 写 description,可选 mode、model、工具与权限,正文是系统提示词
命令.mimocode/command/<name>.mdfrontmatter 写 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),见定时任务与循环。它还说用户要求全局范围时可以不写项目 .mimocode/(distill.txt:177),但写入沙箱只放行记忆树和工作区里的 .mimocode/,用 write、edit 往全局配置目录写会被拒绝。

产物落在哪里

来源产物位置之后谁会读到
Dream项目记忆条目<data>/memory/projects/<pid>/MEMORY.md重建注入、检查点写手、memory 检索
Dream跨项目偏好<data>/memory/global/MEMORY.md重建注入
Dream清空已并入的笔记当前会话的 notes.md无
Distillskill、子智能体、命令、插件钩子工作区根目录的 .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 的普通权限。技能包整体见内置技能包与技能检索。

下一篇:Goal:让独立裁判决定何时停——进入第 3 部分,看 /goal 怎样让另一次模型调用判定任务是否真的完成。

本页目录