OpenClaw × Mem0 记忆整合机制详解:memory-dream 技能的 Dream 协议、触发门槛与源码实现
【免费下载链接】embedchainThe Memory Layer for AI Agents - Drop-in memory infrastructure for AI agents and apps. Context that persists. Built for production.项目地址: https://gitcode.com/GitHub_Trending/em/embedchain
在 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 条 };(dream-gate.ts)
这三个门槛在 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注入覆盖项,而不是修改门控代码。
【免费下载链接】embedchainThe Memory Layer for AI Agents - Drop-in memory infrastructure for AI agents and apps. Context that persists. Built for production.项目地址: https://gitcode.com/GitHub_Trending/em/embedchain
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考