OpenClaw × Mem0 记忆整合机制详解:memory-dream 技能的 Dream 协议、触发门槛与源码实现
在 Mem0 为 OpenClaw 智能体提供的长期记忆插件(@mem0/openclaw-mem0)中,memory-dream 是 skills 模式的三大协议技能之一,负责记忆库的周期性"巩固"(consolidation):审查全部已存记忆、合并重复、清除噪声与凭据、重写低质条目,并执行 TTL 过期策略。阅读本文后,你将完整掌握这份 Dream 协议的四阶段流程与质量标准,并能从 dream-gate.ts、skill-loader.ts、index.ts 等源码中理解自动触发的三门槛(时间/会话/记忆数)设计、文件锁防并发机制,以及 openclaw mem0 dream 手动触发命令的工作原理。
1. memory-dream 在记忆生命周期中的定位
OpenClaw 插件默认运行在 skills 模式下,智能体通过三个技能掌控记忆的写入、召回与清理:
- Triage(memory-triage)——从对话中提取值得长期保留的事实;
- Recall——每轮响应前检索相关记忆并注入上下文;
- Dream(memory-dream)——周期性记忆整合:合并重复项、消解冲突、修剪过期条目。
从 skill-loader.ts 的 loadDreamPrompt() 可以看到,dream 技能与 triage 不同:领域叠加层(domain overlay)对它有生效路径,但自定义提取规则(customRules)明确标注为仅适用于 triage,因为"提取规则不适用于 recall/dream"(源码注释)。也就是说,SKILL.md 正文就是 dream 会话的完整协议,插件只会在其上追加用户配置的门槛参数。
该技能的 frontmatter 声明了关键元信息(SKILL.md):
user-invocable: true:用户可直接要求智能体执行记忆清理、整合或审查;description中还说明它"在足够活动量后也会自动触发(可配置)"(Also triggers automatically after sufficient activity (configurable));metadata.openclaw.requires.env要求MEM0_API_KEY、OPENAI_API_KEY、ANTHROPIC_API_KEY三类环境变量之一可用,即整合操作依赖已配置好的 Mem0 后端与 LLM。
Dream 协议的核心隐喻写在正文开头:把原始观察(raw observations)压缩为干净、可长期保留的知识。整个流程被强制划分为四个阶段,"按顺序执行,不得跳过"(Follow these four phases in order. Do not skip phases)。
2. 协议可用的八个记忆工具
Dream 会话中智能体只能使用插件注册的八个记忆工具(与 README 中 Agent Tools 表格一致)。SKILL.md 对每个工具给出了精确的参数契约:
| 工具 | 用途 | 关键参数 |
|---|---|---|
memory_search |
跨全部已存记忆的语义搜索 | query(必填)、limit、userId/agentId(范围覆盖)、scope("all" 默认 / "session" / "long-term")、categories(类别数组过滤) |
memory_add |
向长期记忆写入新事实 | facts(必填,数组,同批次必须同一 category)、category(8 类之一)、importance(0.0–1.0) |
memory_get |
按 ID 取回单条记忆 | memoryId(必填) |
memory_list |
列出某用户/智能体的全部记忆 | userId、agentId、scope("all" 默认) |
memory_update |
原地更新记忆文本,原子操作且保留编辑历史 | memoryId(必填)、text(必填,整体替换旧文本) |
memory_delete |
按 ID、按查询或批量删除 | memoryId、all(需 confirm: true)、userId、agentId |
memory_event_list |
列出最近的后台处理事件(仅平台模式) | — |
memory_event_status |
查询指定后台事件的状态 | event_id(必填) |
两个值得注意的契约细节:
- 类别即保留策略。
memory_add的category参数决定 TTL 与不可变性。从 skill-loader.ts 的DEFAULT_CATEGORIES可看到默认策略:identity(0.95,永久,immutable)、configuration(0.95)、rule(0.9)、preference(0.85)、decision/technical(0.8)、relationship(0.75)、project(0.75,ttl: "90d")、operational(0.6,ttl: "7d")——这直接对应后文 3a 阶段的两条删除时限。 memory_update优先于"删了再加"。SKILL.md 明确指出"prefermemory_updateover forget-then-store because it is atomic and preserves edit history"。插件的oss.historyDbPath配置项(见 types.ts)即为开源模式下 SQLite 编辑历史库(~/.mem0/history.db)。
3. 四阶段协议:从盘点到报告
Phase 1: Orient(定向盘点)
改动之前先摸清记忆现状:
- 调用
memory_list加载全部已存记忆; - 按 category 计数并记录总数;
- 通过时间戳找出最旧与最新的记忆;
- 记录列表中直接可见的问题:重复项、过短条目、缺少时间锚点(temporal anchor)的条目。
本阶段禁止任何修改。目标是理解"你正在处理的是什么"。
Phase 2: Gather Targets(圈定目标)
用工具调查并识别需要处置的记忆。协议要求先用 memory_search 配合 created_at 过滤条件找出自上次整合以来新增的记忆——这些是最可能需要合并或清理的对象。
随后把每个目标分类为三种动作之一:
- DELETE:包含凭据、已过 TTL、纯噪声、原始工具输出、孤立时间戳;
- MERGE:两条及以上记忆用不同措辞表达同一事实,或一系列记忆在追踪同一实体的增量变化;
- REWRITE:表述模糊、缺少时间锚点、用第一人称而非第三人称、类别错误、过于冗长。
Phase 3: Consolidate(执行整合)
按以下优先级顺序执行 Phase 2 圈定的动作。
3a. 删除危险与过期条目——立即用 memory_delete 删除:
- 凭据、API key、token、密码、secret(匹配插件在运行时注入的已知凭据前缀与认证模式);
- 无上下文的纯时间戳;
- 被存为记忆的原始工具输出;
- 心跳(heartbeat)或 cron 执行记录;
- 被存为记忆的泛化确认语("ok"、"got it");
- 超过 7 天的 operational 记忆;
- 超过 90 天的 project 记忆。
这里"运行时注入的凭据模式"有源码实证:skill-loader.ts 中定义了 DEFAULT_CREDENTIAL_PATTERNS:
const DEFAULT_CREDENTIAL_PATTERNS = [
"sk-", "m0-", "ghp_", "AKIA", "ak_",
"Bearer ", "bot\\d+:AA",
"password=", "token=", "secret=",
];
并且 skill-loader.ts 中 loadSkill() 对 memory-dream 与 memory-triage 都会调用 renderTriageKnobs(),把 skills.triage.credentialPatterns(用户可覆盖上述默认值)渲染为 "Credential patterns to scan: …" 段落追加到 dream 协议文本末尾。这解释了 SKILL.md 中"matching known credential prefixes and auth patterns injected by the plugin at runtime"的确切含义:凭据匹配清单不是写死在技能文件里的,而是每次加载时按用户配置动态拼接。
两条 TTL 时限(operational 7 天 / project 90 天)则与 DEFAULT_CATEGORIES 的 ttl: "7d" / ttl: "90d" 一一对应;ttlToExpirationDate()(skill-loader.ts)会把 "7d" 这类 TTL 换算成具体到期日,供 triage 写入时打上过期标记,dream 再据此清理。
3b. 合并重复项——当两条及以上记忆表达同一事实时:
- 选信息最完整的版本作为基底;
- 对最佳版本调用
memory_update,把其他版本的缺失细节吸收进来; - 对冗余条目调用
memory_delete。
合并时须遵守四条规则:
- 保留用户对观点与偏好的原话;
- 保留两个版本中的时间锚点;
- 合并结果不超过 50 词;
- 合并后的记忆必须自包含(在其余条目被删除后依然可独立理解)。
3c. 重写低质条目——当记忆需要改进但不属于重复时,调用 memory_update 提交改进后的文本。触发重写的五种情形:
- 使用第一人称("I prefer")而非第三人称("User prefers");
- 时效敏感信息缺少时间锚点;
- 表述模糊且可具体化("likes python" → "User prefers Python for backend development");
- 类别归属错误;
- 超过 50 词且可在不损失信息的前提下压缩。
Phase 4: Report(报告)
全部操作完成后,按固定模板输出总结:
Consolidation complete.
- Reviewed: [total count]
- Deleted (credentials/secrets): [count]
- Deleted (expired/stale): [count]
- Merged: [count] groups into [count] memories
- Rewritten: [count]
- Final count: [total remaining]
- Issues found: [any notable problems or observations]
4. 质量目标(Quality Targets)
整合完成后,记忆库应达到以下六项验收标准:
- 含凭据或 secret 的记忆数为 0;
- 重复记忆(同一事实的不同措辞)数为 0;
- 所有 project 与 operational 记忆带时间锚点("As of YYYY-MM-DD");
- 所有记忆使用第三人称表述;
- 所有记忆类别正确;
- 每条记忆 15–50 词、自包含、原子化(一条记忆只陈述一个事实)。
5. 自动触发:Dream Gate 三门槛与文件锁
SKILL.md 声明"activity 足够后自动触发(可配置)",其实现完全在 dream-gate.ts 中,状态持久化在插件的 stateDir 下(README 持久化表中对应 <pluginStateDir>/dream-state.json),可跨网关重启存活。
5.1 状态结构与默认门槛
interface DreamState {
lastConsolidatedAt: number; // 毫秒时间戳,0 = 从未整合
sessionsSince: number; // 上次整合以来的交互会话数
lastSessionId: string | null;
}
const DEFAULTS: DreamGateConfig = {
minHours: 24, // 距上次整合至少 24 小时
minSessions: 5, // 至少 5 个交互会话
minMemories: 20, // 记忆总数至少 20 条
};
这三个门槛在 types.ts 的 SkillsConfig.dream 中全部可配,插件 README 的配置参考表还暴露了 skills.dream.enabled 与 skills.dream.auto 两个开关(auto 默认 true,控制是否按活动门槛自动触发)。
5.2 廉价门槛先行:避免无谓 API 调用
门槛检查刻意分为两级,checkCheapGates()(dream-gate.ts)只做本地文件读取——时间门槛(hoursSince < minHours 则拒绝)与会话门槛(sessionsSince < minSessions 则拒绝)都从同一个 dream-state.json 读出;只有廉价门槛通过后才调用 provider.getAll() 拉取记忆总数,交由 checkMemoryGate()(dream-gate.ts)做昂贵的计数检查。index.ts 中的 before_prompt_build 钩子按此顺序执行:"Check CHEAP gates first (local file reads only). Only hit the API for memory count if time + session gates pass."
5.3 锁与完成记录
- 获取锁:
acquireDreamLock()(dream-gate.ts)以wx排他标志原子创建dream.lock(内含 pid 与 startedAt),两个进程竞争时只有一个成功;超过 1 小时(LOCK_STALE_MS)的陈旧锁会被先清除再竞争,防止崩溃后永久死锁; - 会话计数:
incrementSessionCount()在每次交互回合结束时调用,按 sessionId 去重,同一会话内的多轮不会重复计数; - 完成记录:
recordDreamCompletion()把lastConsolidatedAt置为当前时间并把sessionsSince清零,重置整个计数周期。
5.4 注入、验证与失败回滚
在 index.ts 中,当三门槛与锁全部通过,插件把 loadDreamPrompt() 生成的完整协议文本包进 <auto-dream> 标签,通过 prependContext 注入当轮上下文:"Before responding to the user, run a memory consolidation pass. Follow the protocol below, then respond normally."
agent_end 钩子(index.ts)随后做了两件事:
- 仅放行触发会话:
dreamSessionId是会话级的,防止其他会话误报完成("Prevents cross-session false completion"); - 验证真实写入:扫描最后一条 assistant 消息中的工具调用,只有出现
memory_add、memory_update、memory_delete三者之一才认定整合成功(memory_list/memory_search等只读操作不算),然后释放锁并recordDreamCompletion()。若本轮失败、或注入了 dream 但模型没执行任何写操作,则只释放锁、不记录完成——下一个符合门槛的回合会重新触发。
整套门控逻辑由 tests/dream-gate.test.ts 覆盖:时间门槛过近拒绝、会话数不足拒绝、双门槛通过、minMemories 计数边界、新锁获取(含 wx 标志断言)、1 小时内新锁拒绝、2 小时陈旧锁回收,以及 recordDreamCompletion 的计数器重置。
6. 手动触发:openclaw mem0 dream
除自动触发外,README 提供了 CLI 手动入口(cli/commands.ts 中注册):
# 只查看记忆盘点,不做任何修改
openclaw mem0 dream --dry-run
# 生成完整整合提示词
openclaw mem0 dream
实现细节值得注意:
--dry-run会先调用provider.getAll({ user_id, source: "OPENCLAW" })拉取全量记忆,按metadata.category(回退到categories[0],再无则记为uncategorized)分类计数并打印清单,然后停止("Dry run — no changes made.");- 非 dry-run 时,命令并不直接改写记忆,而是把
loadDreamPrompt()的输出包在<dream-protocol>中、再把全部记忆以<all-memories count="N" user="uid">清单形式拼接,在每条记忆前标注[id] (category, importance, created),最后追加"Begin consolidation…"指令,整段提示词写到 stdout,并提示"Paste it into an OpenClaw session to run consolidation"——即把协议与数据交给一个带工具调用能力的智能体会话去真正执行 Phase 1–4; - 所有命令支持
--json输出(如openclaw mem0 dream --dry-run --json返回{ ok, count, categories }),便于脚本或上层 agent 消费。
7. 配置参考:skills.dream 与相关开关
dream 相关的配置面(types.ts、README 配置参考):
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
skills.dream.enabled |
boolean |
true |
是否启用记忆整合技能 |
skills.dream.auto |
boolean |
true |
是否按活动门槛自动触发 |
skills.dream.minHours |
number |
24 |
两次整合之间的最小间隔(小时) |
skills.dream.minSessions |
number |
5 |
触发前所需的最小交互会话数 |
skills.dream.minMemories |
number |
20 |
记忆总数低于此值则不值得整合 |
skills.triage.credentialPatterns |
string[] |
内置 10 种模式 | 覆盖凭据扫描模式,会注入 dream 与 triage 协议文本 |
skills.categories |
Record |
9 个默认类别 | 覆盖各 category 的 importance / ttl / immutable |
典型 openclaw.json 配置(摘自 README):
{
"plugins": {
"slots": { "memory": "openclaw-mem0" },
"entries": {
"openclaw-mem0": {
"enabled": true,
"config": {
"apiKey": "${MEM0_API_KEY}",
"userId": "alice",
"skills": {
"triage": { "enabled": true },
"recall": { "enabled": true },
"dream": { "enabled": true }
}
}
}
}
}
}
从源码结构看,dream 的自动触发还有一条隐含约束:index.ts 中触发条件包含 !isSubagent,即子智能体回合不参与自动 dream;会话计数同样只在交互触发(非 heartbeat/cron 等非交互 trigger)时递增——这与协议 3a 阶段要求删除"heartbeat or cron execution records"相呼应:系统既避免把后台任务产生的噪声写入记忆,也避免它们消耗整合名额。
8. 小结
memory-dream 展示了 Mem0 OpenClaw 插件"以协议文件驱动 agent 行为"的设计范式:SKILL.md 本身是完整的四阶段操作手册(Orient → Gather Targets → Consolidate → Report),定义了凭据零容忍、TTL 时限(operational 7 天 / project 90 天)、合并规则(≤50 词、第三人称、自包含)与量化报告模板;而 dream-gate.ts 用"廉价门槛先行 + 文件锁 + 写操作验证"三件套,让这份协议可以安全地自动运行——不频繁、不并发、不虚报完成。对运维者而言,需要关注的落盘文件是 dream-state.json(整合状态)与 dream.lock(并发锁),需要关注的可观测信号是日志中的 auto-dream triggered / completed / will retry 事件;对开发者而言,扩展清理规则的正确方式是编辑 SKILL.md 的 Phase 规则或经 skills.triage.credentialPatterns、skills.categories 注入覆盖项,而不是修改门控代码。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
