过去两天,我们集中修复和重构了 OpenClaw 的梦境记忆系统。表面看,这是一次 memory search、embedding、Dreaming cron 的排障;但更准确地说,这是一次对“AI 长期记忆应该如何形成、如何存放、如何治理”的重新定规矩。
这次修复的结论很明确:梦境记忆不是简单地“把聊天记录塞进长期记忆”,也不是让系统自动替人判断一切。它应该是一套分层系统:底层负责检索与提炼,中层负责归档与回放,顶层的长期记忆则只保存原则性、架构性、长期有效的信息。
一、问题的起点:Dreaming 在跑,但 recall 不稳
最早的问题出现在语义检索和 Dreaming 晋升链路上。Dreaming 的定时任务本身可以运行,但 memory_search / recall 一度不可用。最开始容易误判为模型输出为空、配置完全错误,或者 Dreaming 本身坏了;实际根因更工程化:首次或脏索引批量 embedding 时,撞上了 NewAPI / 上游的 RPM 限流。
这里有一个容易踩坑的细节:embedding API 的账单里 output tokens 为 0 并不代表 embedding 没有生成。对 embeddings 来说,真正应该检查的是向量维度、非零元素、范数和索引 identity,而不是用 chat completion 的输出 token 逻辑判断。
我们做过验证:远端 embedding 返回过 1024 维、非零、norm 约等于 1 的向量,说明模型本身能工作。问题在于首次索引规模较大,memory 文件约 256 个,批量请求触发 RPM 限流,导致 MCP 的 memory_search 15 秒超时。即使把 NewAPI 限制从 20 RPM 提到 200,真实上游天花板仍约 100 RPM,首次 bootstrap 依旧不稳定。
这暴露出 Dreaming 当前工程形态的一个现实问题:理念是好的,但对普通用户还不算完全开箱即用。缺少索引节流、断点后台索引、清晰进度提示和错误提示时,很多人会卡在 embedding 限流或索引初始化阶段。
二、本地 embedding:把核心能力拉回本机
为绕开外部 embedding RPM,我们开始部署本地 OpenAI-compatible embedding 服务。最初尝试 Infinity 原版,但遇到依赖和兼容问题,例如 huggingface_hub 的接口变化。最后改为更直接的方案:FastAPI + sentence-transformers,自写 /v1/embeddings 接口。
最终落地配置如下:
- 服务目录:
/home/percy/.openclaw/services/infinity-emb/ - systemd user service:
openclaw-local-embedding.service - 模型:
BAAI/bge-small-zh-v1.5 - 接口:
http://127.0.0.1:7997/v1/embeddings - 向量维度:512
- 实测:短文本两条约 100–200ms,向量 norm 约等于 1
首次全量索引时,本地 CPU 压力并不小:CPU 曾在 250%–270%,服务 RSS 约 1.1GB,available memory 约 1.4GB。但主机最终扛住了,没有 OOM,没有崩溃。
最终通过 openclaw memory status --deep --json 验证,main / hubu / gongbu / libu 四个 agent 均达到:
dirty=falseindexIdentity.status=validembeddingProbe.ok=true- model 均为
BAAI/bge-small-zh-v1.5 - dims=512
memory_search 也恢复到 1–3 秒可返回结果。至此,梦境记忆的底层检索能力从“依赖外部限流资源”转为“本机稳定服务”。
三、回归 memory-core:不要为 Dreaming 引入不必要复杂度
排障过程中,我们还追溯了 memory-lancedb 的来源。通过配置备份和文件系统考古确认,它最早出现在 openclaw.json.bak.4,时间约为 2026-06-27 22:34,初始就搭配 jina-embeddings-v5-omni-small,并设置了 plugins.slots.memory = memory-lancedb。
但进一步确认后,我们得出结论:memory-lancedb 对当前梦境记忆主路径不是必要项。Dreaming 属于官方内置 memory-core,而 memory-lancedb 并不提供 Dreaming 本身。为了减少复杂度,最终配置回归官方内置 memory-core:
- 移除
memory-lancedbactive slot - 保留
plugins.entries.memory-core.config.dreaming.enabled=true agents.defaults.memorySearch使用本地 OpenAI-compatible embedding 服务
这也是一次重要的工程教训:遇到系统配置问题时,不能急着“多装一个插件解决问题”。插件越多,路径越复杂;核心链路必须先回到官方文档和主路径。
四、补齐企微自然聊天的每日归档
另一个关键发现是:我们日常主要通过企业微信自然聊天,不会频繁使用 /new 或 /reset。而官方 session-memory hook 的保存逻辑主要依赖 /new 或 /reset,它会在会话切换时保存到 memory/YYYY-MM-DD-HHMM.md。
这意味着:如果一直在同一个企微会话里自然聊天,session-memory 虽然 enabled / ready,但对当前工作流并不能稳定形成每日笔记。Daily reset 负责的是会话生命周期,不等于自动写 daily note;Dreaming 是提炼系统,也不是完整日记系统。
因此我们新增了一个专门适配当前工作流的 cron:daily-wecom-session-journal。
它每天 23:50 Asia/Shanghai 独立运行,读取 agent:main:wecom:direct:zhangpei 最近历史,追加写入当天 memory/YYYY-MM-DD.md,供 03:00 Dreaming 提炼。它不 reset 会话,不修改 MEMORY.md,也不向用户发送消息。
这个补丁看似小,但它解决了一个根本问题:Dreaming 需要可读的短期素材。没有稳定的 daily note,后面的 light / REM / deep 阶段就没有足够干净的原料。
五、重新定义 MEMORY.md:长期记忆不是流水账
7 月 4 日凌晨,Dreaming cron 正常运行,但 deep 晋升为 0 条。进一步检查发现,light 阶段候选大量是 recalls=0、confidence 约 0.58,REM 阶段没有强模式,Deep 阶段没有候选可晋升。
这说明信息没有丢,具体内容仍在 daily note 和 dreaming 产物里;但自动晋升机制对“当天刚产生、尚未被多次检索命中”的高价值内容并不敏感。它更偏向多次 recall、多 query 命中的旧信息。
这时我们重新讨论了 MEMORY.md 的定位。最终定下原则:MEMORY.md 不应该是具体事项的堆积场,也不应该记录每一次排查、每一个项目进度、每一段带 source 行号的原文片段。
MEMORY.md 应该只记录长期有效的原则和架构,例如:
- 帝国律法和安全红线
- 多 Agent 架构与调度链路
- CPA 降级架构
- 记忆系统和数据源配置原则
- 文件索引
- 跨项目通用经验教训
具体项目进度、排障步骤、功能技术细节,应放到 memory/YYYY-MM-DD.md、项目文件或专门归档里。
这一步非常重要:长期记忆如果被具体事件淹没,AI 以后检索到的就会是噪音。长期记忆必须是“规则层”和“架构层”,而不是“日志层”。
六、让 Dreaming 继续写,但写完自动搬走
完全禁用 Dreaming promotion 并不理想,因为 deep phase 的晋升结果仍然有价值。最终采用的方案是:允许 Dreaming 继续按官方机制写入 MEMORY.md,但写完后自动搬迁到单独归档文件,主记忆只保留说明链接。
为此新增了搬迁脚本:
/home/percy/.openclaw/scripts/relocate_promoted.py
脚本逻辑是:
- 扫描
MEMORY.md中## Promoted From Short-Term Memory (YYYY-MM-DD)段落; - 将新增段落增量追加到单一归档文件:
memory/promoted/dreaming-promoted.md; - 在
MEMORY.md中只保留### 梦境晋升归档索引块; - 保证幂等性,已归档的日期不会重复写入。
配套 cron 为 dreaming-promotion-relocate,每天 03:10 运行。这样凌晨链路变成:
03:00 Memory Dreaming Promotion → deep phase 按官方逻辑写 MEMORY.md
03:10 dreaming-promotion-relocate → 将 promoted 段落搬到单一归档文件
归档不再按日期分散成多个文件,而是统一追加到 memory/promoted/dreaming-promoted.md。这样既保留了 Dreaming 的自动产物,又避免污染主长期记忆。
七、现在的记忆分层
这次修复之后,记忆系统形成了更清晰的分层:
MEMORY.md 原则、架构、长期有效规则
memory/YYYY-MM-DD.md 每日具体事项、排障记录、工作日志
DREAMS.md 梦境日记,人类可读的反思文本
memory/dreaming/<phase>/ light / REM / deep 阶段产物
memory/promoted/dreaming-promoted.md deep phase 自动晋升归档
memory_search 统一语义检索入口
这套结构的重点不是“让 AI 记更多”,而是“让 AI 记得更有层次”。流水账可以保留,但不能占据宪法的位置;自动提炼可以运行,但不能直接改写长期原则。
八、经验教训
这次修复留下了几条非常实用的经验:
第一,embedding 的健康检查不能看 output tokens,要看向量维度、非零元素、范数和索引 identity。
第二,首次全量索引是独立工程问题,需要考虑 RPM、断点、节流、锁、CPU 和内存,而不是简单地归咎于模型。
第三,Dreaming 是提炼系统,不是完整日志系统。对于长期自然聊天的工作流,必须先有稳定 daily note。
第四,MEMORY.md 需要人为治理。自动晋升可以提供素材,但不能替代长期记忆的原则判断。
第五,配置变更要查官方文档,不能边猜边改。尤其 memory / plugin / Gateway 这类核心配置,随手改会造成连锁反应。
结语
这两天的修复,不只是把一个坏掉的 memory search 修好,而是把记忆系统从“能跑”推进到“可治理”。
一个 AI 助手真正有用的长期记忆,不应该是无限堆积聊天记录,而应该像一套分层档案:日记记录事实,梦境提炼线索,归档保存素材,长期记忆只承载原则。
这就是这次梦境记忆修复最大的收获:让系统继续做梦,但梦醒之后,要有人整理案卷。