Mem0 Plugin Pin 技能解析:用 [PINNED] 标记保护关键记忆不被 Dream 整理裁剪
Mem0 插件(mem0-plugin)通过 /mem0:pin 命令将一条记忆标记为高优先级,使其在 /mem0:dream 记忆整理(consolidation)时永远不会被合并或裁剪。本文基于仓库中 pin 技能定义文件 完整拆解其执行流程、MCP 工具调用细节与"文本标记 + 元数据"的双重保护机制,并结合 dream 技能 与插件配置说明该机制如何真正落地生效。读完后你将能够:手动执行 pin/unpin 全流程、理解为什么 pin 走的是 text 前缀而不是 metadata 参数、以及确认哪些记忆值得被 pin。
1. 背景:Mem0 插件的记忆体系与 pin 的定位
Mem0 插件是为 Claude Code、Claude Cowork、Cursor、Codex、OpenCode 和 Antigravity 等编码 Agent 提供的持久记忆插件,插件清单见 plugin.json(Apache-2.0 许可,16+ 斜杠命令 + 生命周期钩子)。它连接 Mem0 远程 MCP 服务(连接配置见 mcp_config.json,认证使用 MEM0_API_KEY 环境变量),提供一套记忆操作工具。与 pin 直接相关的工具有(工具清单来自 插件 README):
| 工具 | 说明 |
|---|---|
add_memory |
为用户/Agent 保存文本或对话历史 |
search_memories |
带过滤条件的语义搜索 |
get_memory |
按 ID 获取单条记忆 |
update_memory |
按 ID 覆盖记忆文本 |
delete_memory |
按 ID 删除单条记忆 |
插件自带 17 个可通过 /mem0: 调用的技能,其中 /mem0:pin 的职责在 README 中概括为一句:"Protect critical memories from pruning"(保护关键记忆免于裁剪)。pin 技能文件头部的描述给出了明确的适用场景:架构决策(architecture decisions)、安全约束(security constraints)、不可变的团队约定(immutable team conventions) 这类"绝不能被删掉"的记忆。
为什么需要"绝不能被删"?因为 dream 技能 会在记忆数量变多时执行整理:合并近重复项、裁决矛盾、按保留策略裁剪过期条目。如果没有保护机制,一条"本系统禁止在测试库写入真实数据"的安全约束,完全可能因为置信度低或过期而被自动清理掉。pin 就是为此设计的显式豁免开关。
2. 完整执行流程:从查找记忆到固定
pin 技能的执行流程分四步,全部基于 MCP 工具完成,无任何本地存储副作用。
2.1 第一步:定位目标记忆
用户输入有两种形态,处理路径不同:
- 提供记忆 ID:直接调用
get_memory(memory_id=<ID>)。 - 提供搜索词:调用
search_memories,参数固定为:
search_memories(
query=<用户的搜索词>,
filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<active_project_id>"}]},
top_k=5,
)
注意两个约束:filters 必须同时限定 user_id 与 app_id(即"当前用户 + 当前项目"作用域,app_id 承载项目维度),top_k=5 限制候选数量。随后向用户展示带编号的内容预览列表,并询问 "Which memory to pin? Enter a number."。
这个"作用域过滤 + 候选列表 + 人工确认"的模式与 forget 技能 保持一致——pin 虽然是写操作,但同样要求用户对具体哪条记忆负责。
2.2 第二步:读取当前内容
确定目标 ID 后,再次调用 get_memory 读取其完整内容,并在内存中保存两个值:
original_text— 记忆的文本内容;original_metadata— 现有的metadata字典。
这一步看似冗余(第一步可能已读到内容),但它是幂等性检查的前提:pin 操作必须基于最新原文判断标记是否已存在,避免重复操作时破坏元数据或产生多余前缀。
2.3 第三步:执行 pin——文本标记机制
这是整个技能最关键、也最容易误解的部分。原文档明确指出:
MCP 的
update_memory工具只接受memory_id、text和source三个参数——它不接受metadata参数。
因此 pin 不能通过 update_memory 写 metadata.pinned=true,而是退而求其次,在文本层面追加标记:
pinned_text = (
"[PINNED] " + original_text
if not original_text.startswith("[PINNED]")
else original_text
)
update_memory(memory_id=<selected_id>, text=pinned_text)
两个实现细节值得注意:
- 幂等性:
startswith("[PINNED]")检查确保重复 pin 同一条记忆时不会变成[PINNED] [PINNED] ...; - 标记格式:固定为
[PINNED](含一个空格)前缀,这决定了后续 unpin 和 dream 识别都依赖这一定长前缀。
对于尚未入库的新文本(用户想把一段话直接固化为已 pin 的记忆),流程改为 add_memory 一步到位:
add_memory(
text="[PINNED] <用户的文本>",
user_id=<active_user_id>,
app_id=<active_project_id>,
metadata={"pinned": True, "type": "decision", "confidence": 1.0},
infer=False,
)
这里有两个参数约定:
metadata中写入pinned: true、type: "decision"、confidence: 1.0——新记忆在元数据层面也被标记,与文本前缀形成双重标记(详见第 3 节);infer=False关闭 Mem0 的自动抽取/推断,保证文本被逐字存储,[PINNED]前缀不会被记忆抽取管线改写。
add_memory 的响应中只返回 event_id 而非记忆 ID,因此技能要求再调用一次 get_event_status(event_id=<event_id>) 换取真正的 memory ID,然后向用户确认。
2.4 第四步:确认输出
pin 完成后,输出格式被严格约束为:
Pinned: "<记忆内容前 80 字符>"
Memory ID: <id>
仅当内容超过 80 字符时才追加 ... 省略号。这种"固定截断长度 + 省略号规则"的约定保证了 Agent 输出的一致性,也方便用户核对 pin 的是不是自己选的那条。
2.5 解除 pin(unpin)
当用户说 "unpin" 时,流程是对称的三步:
get_memory读取当前内容;- 移除文本标记:
unpinned_text = original_text.removeprefix("[PINNED] ")
update_memory(memory_id=<id>, text=unpinned_text)
- 打印
Unpinned: "<内容>..."。
注意 unpin 只移除文本前缀。对于存量记忆(第三步 pin 的旧路径),update_memory 本来就不写 metadata,所以解除后文本与元数据都不带 pinned 痕迹;而对于"新记忆直接 pin"路径写入的 metadata.pinned=true,unpin 流程并未给出对应的清除步骤——从技能文本看,该路径的元数据标记会保留,这是一个可以推断出的实现边界,实际操作中如需彻底解除应自行通过支持 metadata 写入的通道(如 Mem0 SDK 或 REST API)处理。
3. 机制剖析:为什么文本标记能让 Dream 跳过这条记忆
pin 的保护效果不在 pin 技能内部实现,而由 dream 技能的整理规则 兑现。dream 是 Mem0 的记忆整理通道:拉取项目全部记忆(get_memories,page_size=200 分页取完),在内存中分析出三类问题——近重复对(合并候选)、矛盾对(需人工裁决)、过期条目(裁剪候选)——打印 diff 报告并经用户确认后应用修改。
pin 在其中体现为两条硬性规则:
规则一:pin 记忆永不进入裁剪候选。 dream 的 Step 3c 规定,裁剪候选的判定条件是"类型有保留策略且超过保留天数"或"置信度低于 0.3 且不含项目独有信息",但紧接着有一条加粗的强制例外:
Always skip memories where
metadata.pinned == true, regardless of age or confidence.(无论年龄或置信度,凡metadata.pinned == true的记忆一律跳过)
规则二:pin 记忆不参与近重复合并。 dream 的 Step 3a 中,两条记忆构成近重复对需要同时满足三个条件:词法相似度足够高(>60% 实词重合近似余弦 0.9)、metadata.type 相同、且双方均未 pin(metadata.pinned != true)。
把两节串起来看,pin 技能采用了"双通道标记"策略:
| 通道 | 载体 | 写入方式 | dream 如何消费 |
|---|---|---|---|
文本前缀 [PINNED] |
text 字段 |
update_memory / add_memory(MCP 工具唯一可写文本) |
人类与 Agent 可读,search_memories 结果中一眼可辨 |
元数据 pinned: true |
metadata 字段 |
仅 add_memory 路径(新记忆) |
dream 裁剪/合并规则的程序化判定依据 |
之所以需要双通道,根源在于第 2.3 节提到的工具限制:MCP 的 update_memory 不能写 metadata,只能改 text。对存量记忆,pin 只能保证文本前缀这个"软标记",dream 规则中依赖的 metadata.pinned 判定在存量路径下依赖该记忆原本就有的元数据;对新记忆,则两条通道同时落位,保护是完备的。从源码结构看,这种不对称正是技能文档把"新记忆"单列一步、并在其中显式写 metadata={"pinned": true, ...} 的原因。
值得补充的是插件侧的配套机制:hooks.json 中注册了 mem0-enforce-metadata 钩子(PreToolUse 匹配 mcp__mem0__.* 工具调用,执行 scripts/enforce_metadata_defaults.sh),会对记忆写入做元数据补全与约束。也就是说,pin 标记并不是孤立约定,而是嵌在插件"元数据强制 + 分类标签"的整体纪律里——插件 README 说明插件会自动安装 17 类面向编码场景的记忆分类(architecture_decisions、security_constraints、team_norms 等),这些恰好就是 pin 技能描述中点名的适用对象。
4. 使用与验证边界
- 前提:插件已安装且
MEM0_API_KEY已配置(README 要求安装前先完成 API key 配置),MCP 服务可达。 - 作用域:所有搜索与写入都绑定
user_id+app_id(当前用户与当前项目),pin 不会跨项目生效。 - 可验证点:pin 之后可运行
/mem0:tour或/mem0:peek浏览记忆,确认内容以[PINNED]开头;随后执行/mem0:dream,在输出的 Prune/Merges 清单中确认该记忆未被列为候选,即可端到端验证保护链路。 - 与其他命令的边界:
/mem0:forget是定向删除(搜索 + 确认 +delete_memory),它不做 pinned 豁免判断——pin 保护的是 dream 的自动整理,不代表 forget 时不能删;/mem0:memory-reviewer做质量审计(重复、矛盾、过期)但不自动修改,与 pin/dream 配合形成"审计 → 固定/整理"的工作流。
5. 小结
pin 技能展示了 Mem0 插件处理"MCP 工具能力受限"问题的一个典型手法:当 update_memory 不支持 metadata 写入时,用文本前缀 [PINNED] 承载用户可见的固定状态,用 add_memory 的 infer=False + metadata.pinned 承载新记忆的完整标记,最终由 dream 的整理规则("pinned 一律跳过裁剪、不参与合并")兑现保护承诺。对使用者而言,关键结论是:把架构决策、安全约束、不可变的团队约定 pin 起来,是保证它们在长期 Agent 使用与记忆自动整理中不被侵蚀的标准做法;而 unpin 时只清除文本前缀这一实现细节,也提示我们在处理"新记忆直接 pin"路径写入的元数据时需要留意其残留。
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