Pi Agent 记忆整合指南:Mem0 Dream 技能的五步工作流程与源码级触发机制
本文围绕 mem0/pi-agent-plugin 中的 Dream 记忆整合技能(skills/dream/SKILL.md)展开,完整解析其"拉取全部记忆 → 分析 → 差异报告 → 用户确认 → 应用"的五步工作流、近重复/矛盾/陈旧记忆三类问题的判定启发式与 [PINNED] 保护机制,并结合 src/dream/、src/entry.ts、src/commands.ts 等源码,说明自动触发门控、进程锁与完成记录的真实实现。读完你能掌握在 Pi Agent 中手动与自动执行记忆整合的完整方法,以及每个配置参数(minHours、minSessions、minMemories)在源码中的生效位置。
Dream 是什么:为什么记忆层需要"整理"
Dream 是 pi-agent-plugin 提供的记忆整合(memory consolidation)能力。根据 SKILL.md 的 frontmatter 定义,它用于:
Consolidates stored memories by merging duplicates, resolving contradictions, and pruning stale entries. Use when memory count is high, search results feel noisy or repetitive, or periodic cleanup is needed to maintain memory quality.
即:当记忆条数变多、搜索结果变得嘈杂或重复时,通过一次"整合理顺"来合并重复项、裁决矛盾项、清理陈旧条目。整个插件的完整背景(8 个斜杠命令、mem0_memory agent 工具、三级作用域 project/session/global)见 README。
SKILL.md 中对流程有一条硬性约束,值得单独强调:
Execute steps strictly in order (1 → 2 → 3 → 4 → 5). Each step depends on the previous one. Do NOT run steps in parallel or skip ahead.
即五步严格串行,不允许并行执行或跳步——这是 agent 执行该技能时的纪律性要求,下文逐步展开。
五步工作流程:逐步解析
Step 1:拉取全部记忆
第一步要求 agent 使用 mem0_memory 工具且 action="get_all" 取出当前作用域内的全部记忆。这个工具在 tools.ts 中注册,get_all 分支会调用 mem0.getAll({ filters }),其中 filters 由作用域解析(project 作用域使用 user + app_id 过滤);输出超过 200 行或 50KB 会被截断并附提示。
边界情况:如果拉取结果为空,输出
No memories found. Nothing to consolidate.
然后立即停止,不进入后续步骤。
Step 2:分析 —— 找出问题(只读,不修改)
第二步要求完全在内存中分析,此时不修改任何记忆。先将记忆按 category 分组,再识别三类问题。这里涉及插件的 10 个默认记忆分类(identity、preferences、goals、projects、decisions、technical、relationships、routines、lessons、work),定义在 types.ts 的 DEFAULT_CUSTOM_CATEGORIES 中,/mem0-remember 和 mem0_memory 的 add 操作都会带上这套分类。
2a. 近重复对(合并候选)
判定标准:两条记忆表达同一个事实但措辞不同(例如 "Prefers morning meetings" 与 "Likes scheduling meetings early")。原文给出三条启发式规则,同时满足才视为近重复:
- 超过 60% 的重要名词/关键词重叠;
- 两条记忆处于同一 category;
- 两条记忆都未被钉住(内容不以
[PINNED]开头)。
对每一对满足条件的记忆,起草一个比两条原文都更完整的合并版本。
2b. 矛盾对
两条记忆就同一主题断言了相反的事实(例如 "Prefers cats" 与 "Allergic to cats, prefers dogs")时构成矛盾。裁决规则是较新的记忆胜出(the more recent memory wins),但两条记忆的 ID 和内容都要记录下来供用户复核,而不是自动删除。
2c. 清理候选(prune)
满足任意一条即为清理候选:
- 超过 180 天且近期未被访问过;
- 内容极其模糊(少于 5 个有意义的词)。
同时有一条无条件的保护规则:
Always skip memories where content starts with
[PINNED], regardless of age.
[PINNED] 前缀由 pin 技能维护,其机制是向记忆文本头部加 [PINNED] 标记来告诉 dream 在整理时跳过该条目(见 pin/SKILL.md)。补充一个实现细节:/mem0-pin 命令的实际实现(commands.ts)是用 mem0.update(target.id, { text: "[PINNED] ..." }) 在原 ID 上改文本,因此钉住操作保留记忆 ID(README 中该命令标注 "preserves ID")。
Step 3:打印差异报告
分析完成后、任何修改执行之前,必须打印一份结构化的 diff 报告。SKILL.md 给出的报告模板是:
## dream — consolidation report
Merges (<N>):
[mem0:<id1>] + [mem0:<id2>] -> "<merged content, 100 chars>"
Conflicts (<N>):
[mem0:<idA>] vs [mem0:<idB>] — "<topic>" [A/B/skip]
Prune (<N>):
[mem0:<id>] — <category>, <age>d old
Proposed: <N> merges, <N> prunes, <N> conflicts. Apply? [Y/n]
如果三类提案总数为零,输出:
Dream complete. No duplicate, contradictory, or stale memories found.
并停止。这个"先出 diff、再动手"的设计意味着整合过程对用户完全可审计、可回退决策。
Step 4:等待用户输入并应用
分为两个子阶段:
- 4a 矛盾裁决:对每一对冲突记忆,等待用户选择保留 A、保留 B 或 skip;
- 4b 最终确认:收集完所有冲突裁决后,提示
Apply? [Y/n]。用户拒绝则输出Cancelled. No changes made.并停止。
确认后按下表应用所有变更,全部通过 mem0_memory 工具执行:
| 变更类型 | 操作 |
|---|---|
| Merges(合并) | 删除两条原始记忆(action="delete"),再用 action="add" 写入合并版本 |
| Contradictions(矛盾,已裁决) | 用 action="delete" 删除落败一方 |
| Prunes(清理) | 逐条用 action="delete" 删除 |
注意 merge 的实现方式是"删二加一",因此合并后的新记忆会获得新的 ID 和创建时间——这是从技能定义可以直接读出的行为,也是它和 update(保留 ID)路径的本质区别。
Step 5:打印摘要
Dream complete — merged: <N>, pruned: <N>, conflicts resolved: <N>, skipped: <N>
Auto 模式:--auto 的非交互执行
以 --auto 调用(例如 /mem0-dream --auto)时跳过交互:
- Merges:自动应用;
- Prunes:自动应用;
- Contradictions:一律跳过——"they require human judgment"。
随后打印紧凑摘要:
[mem0-dream --auto] merged=<N> pruned=<N> conflicts_skipped=<N>
可见自动模式的边界划得很清楚:机械可判定的合并与清理可以无人值守,涉及事实取舍的矛盾必须留给人。
源码解析:手动命令与自动触发的两条路径
上面是 agent 侧的技能流程。再看插件代码如何驱动它——/mem0-dream 命令与自动 dream 走的是不同入口。
手动路径:/mem0-dream 命令
commands.ts 中注册的 mem0-dream 命令逻辑很短:
- 先调
acquireDreamLock(CONFIG_DIR)抢锁,抢不到就提示 "A dream consolidation is already in progress." 并返回; - 抢锁成功后,用
pi.sendMessage({ customType: "mem0-dream", content: DREAM_PROTOCOL, display: false }, { triggerTurn: true })把 dream 协议注入并触发新一轮 agent 回合; - 向用户发送 "Dreaming — reviewing your memories..." 的反馈。
也就是说,命令本身不直接操作记忆,而是把一个完整的执行协议交给 agent 去跑。
注入的协议文本:DREAM_PROTOCOL
这个协议定义在 dream/prompt.ts,与 SKILL.md 的五步流程同属"整合"语义,但面向的是无人值守场景,步骤压缩为四段:
-
ORIENT:
action="get_all"列出全部记忆,按 category 计数,标记最旧/最新; -
GATHER TARGETS:把每条记忆分类为
DELETE:敏感信息(API key、密码、token)、过期/陈旧条目、噪声、冗余操作细节;MERGE:同一事实的不同表述,保留措辞更好的一条,删掉另一条;REWRITE:模糊、第一人称或分类不当的条目,先add改进版再delete旧条目;KEEP:其余一切。
同样要求跳过所有以
[PINNED]开头的记忆; -
CONSOLIDATE:执行删除、合并(合并后删除两条原文)、重写(先加后删);
-
REPORT:汇总 reviewed / deleted / merged / rewritten 数量与最终总数。
协议结尾还给出了质量目标(quality targets):
zero sensitive data stored, zero duplicates, all entries are atomic (one fact each), 15-50 words each.
即整理完成后应达到:不存敏感数据、零重复、每条记忆原子化(一条记忆只含一个事实)、每条 15–50 词。这比 SKILL.md 的启发式更激进——自动协议把"敏感信息清除"也纳入了整合职责。
自动路径:门控 + 锁 + 完成记录
自动 dream 的全部状态机代码在 dream/index.ts,由 entry.ts 在生命周期钩子中编排。
状态与锁文件。状态保存在 ~/.pi/agent/(CONFIG_DIR,见 config/index.ts)下:
mem0-dream-state.json:{ lastConsolidatedAt, sessionsSince, lastSessionId },即 types.ts 中的DreamState;mem0-dream.lock:{ pid, startedAt },即DreamLock。
acquireDreamLock(dream/index.ts)的语义是:若已有锁且距 startedAt 不足 60 分钟(LOCK_STALE_MS = 60 * 60 * 1000),返回 false;超过 60 分钟视为陈旧锁,先 unlink 再用 flag: "wx"(排他创建)写入新锁,失败则返回 false。releaseDreamLock 与 recordDreamCompletion 分别负责删锁和重置状态(把 lastConsolidatedAt 置为当前时间、sessionsSince 清零)。
三级门控。自动触发要依次通过三道闸:
| 门 | 函数 | 默认阈值 | 语义 |
|---|---|---|---|
| 时间闸 | checkCheapGates |
minHours: 24 |
距上次整合不足 24 小时则拒绝,理由形如 time: 3.0h < 24h |
| 会话闸 | checkCheapGates |
minSessions: 5 |
上次整合以来的会话数不足 5 则拒绝 |
| 记忆量闸 | checkMemoryGate |
minMemories: 20 |
当前作用域记忆数不足 20 条则拒绝(entry.ts 中先 mem0.getAll 数数) |
前两个门是纯本地文件读取(所以叫 "cheap gates"),第三个门需要一次远程 API 调用。session_start 钩子(entry.ts)在 config.dream.enabled 时调用 incrementSessionCount,按 sessionId 去重累加 sessionsSince——同一会话多次事件只计一次。
触发与收尾。在 before_agent_start(entry.ts)中,当 dream.enabled && dream.auto 且本会话尚未触发/检查过时,依次通过 cheap gates → memory gate → 抢锁,全部通过才把 DREAM_PROTOCOL 追加进 system prompt 并打 pi.dream.triggered 遥测;任何一步异常都静默吞掉留待下一回合重试。agent_end 钩子(entry.ts)会检查本回合消息里是否出现过 mem0_memory 工具的写操作(add / delete / delete_all),有则调用 recordDreamCompletion 打 pi.dream.completed,无论如何都释放锁并复位 dreamTriggered;session_shutdown 则兜底释放仍持有的锁。
测试佐证。tests/dream.test.ts 用 vitest mock 掉 node:fs,验证了门控的四种行为:无状态文件(零会话)时拒绝且原因含 "sessions"、3 小时前刚整合过时拒绝且原因含 "time"、会话数不足时拒绝、双门齐过时放行;并验证 checkMemoryGate(5) 拒绝、checkMemoryGate(25) 通过。这些用例与上文源码逐一对应。
配置与运行
配置文件
dream 相关参数位于 ~/.pi/agent/mem0-config.json 的 dream 块(加载逻辑见 config/index.ts,缺失字段逐项回落到 DEFAULT_DREAM):
{
"apiKey": "m0-your-key-here",
"userId": "your-username",
"autoCapture": true,
"defaultScope": "project",
"searchThreshold": 0.2,
"dream": {
"enabled": true,
"auto": true,
"minHours": 24,
"minSessions": 5,
"minMemories": 20
}
}
| 参数 | 默认值 | 作用 |
|---|---|---|
enabled |
true |
总开关;关闭后不累计会话数,也不检查自动触发(session_start 与 before_agent_start 均以它为前提) |
auto |
true |
是否允许在 before_agent_start 时自动注入 DREAM_PROTOCOL;/mem0-dream 手动命令不受此开关影响 |
minHours |
24 |
两次整合的最小时间间隔(小时) |
minSessions |
5 |
两次整合间最少的新会话数 |
minMemories |
20 |
触发自动整合所需的最少记忆条数,低于它视为"不够脏,不值得整理" |
环境变量 MEM0_API_KEY、MEM0_USER_ID 覆盖配置文件中的 apiKey、userId;无 API key 时插件整体禁用(entry.ts 打印警告并直接 return)。安装与启用步骤(pi install npm:@mem0/pi-agent-plugin)见 README。
运行方式汇总
| 方式 | 触发 | 行为 |
|---|---|---|
| 手动命令 | 在 Pi Agent 中输入 /mem0-dream |
抢锁后注入 DREAM_PROTOCOL,agent 执行 ORIENT → GATHER → CONSOLIDATE → REPORT |
| 技能驱动 | 让 agent 使用 dream 技能 | 按 SKILL.md 五步流程执行,出 diff 报告并等用户确认 |
| 自动触发 | 每次 before_agent_start 检查 |
三门齐过 + 抢锁成功才注入协议,agent_end 检测到写操作后记录完成 |
与相邻技能、命令的协作
dream 不是孤立工作的,SKILL.md 的 See also 指向 /mem0-forget(定向删除特定记忆)和 /mem0-status(健康检查)。结合仓库可以看到完整的分工:
- forget(forget/SKILL.md):按查询或 ID 精确删除,强调"Never delete without confirmation",对应命令实现
mem0.delete(target.id)(commands.ts); - pin(pin/SKILL.md):通过
[PINNED]前缀保护关键记忆免遭 dream 清理——这正是 dream 三类判定中反复出现的跳过条件; - tour / status:
/mem0-tour [scope]按 category 浏览全部记忆,/mem0-status显示连接状态、身份、记忆数与Dream: enabled/disabled,适合在整合前后各查一次做对照。
小结
Dream 技能用"全量拉取 → 启发式分析(60% 关键词重叠判重复、新记忆赢矛盾、180 天/少于 5 词判陈旧)→ diff 报告 → 人工确认 → 删旧加新"的受控流程,把记忆库的质量维护变成可审计的操作;[PINNED] 标记则为不可触碰的记忆提供了统一豁免。源码层面,pi-agent-plugin 用两个本地状态文件(state + lock)、三级门控(24h / 5 sessions / 20 memories)和 60 分钟陈旧锁超时,把自动整合收敛为低频、互斥、可重试的后台行为,并通过遥测事件(pi.dream.triggered / pi.dream.completed)暴露整条链路。理解这套机制后,你可以按 dream 配置块调频(例如提高 minMemories 减少触发),用 /mem0-pin 保护关键记忆,再配合 /mem0-status 与 /mem0-tour 观察每次整合的实际效果。
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