Mem0 插件 /mem0:forget 技能详解:基于确认机制的记忆删除、撤销与会话统计源码解析
本篇围绕 mem0 插件中的 /mem0:forget 技能(SKILL.md)展开:它定义了 AI 编码代理(Claude Code、Codex、Cursor 等)删除持久记忆时“按查询或 ID 检索、逐条确认、删除后汇报”的完整执行协议,以及如何通过会话统计脚本撤销最近写入的记忆。读完本文,你将掌握该技能的五步执行流程与“撤销最近写入”的底层数据流,并能结合 session_stats.py 源码理解 recent_ids 的写入、上限与读取机制,以及对应的测试依据。
/mem0:forget 的定位:mem0 插件记忆生命周期中的删除入口
mem0 插件为 AI 代理提供跨会话的持久语义记忆,通过 Mem0 Platform 的 MCP 服务器暴露 add_memory、search_memories、get_memory、update_memory、delete_memory 等工具(见 README.md 的 MCP Tools 表格)。插件共提供 17 个 /mem0: 前缀的斜杠命令技能,其中 /mem0:forget 的定位是:通过搜索查询或记忆 ID 删除记忆,且删除前必须经过用户确认。
从 SKILL.md 的 frontmatter 可以看到该技能的官方描述:
- name:
forget - description: “Deletes memories by search query or memory ID with confirmation before removal. Use when removing outdated decisions, incorrect memories, sensitive data, or cleaning up after experiments. Also handles undo of recent additions.”
也就是说,该技能覆盖两类典型场景:
- 主动清理:删除过时的决策记录、错误的记忆、敏感数据,或实验结束后的残留记忆;
- 撤销写入:用户说 “undo last N memories” / “undo last write” 时,回滚本会话刚刚写入的记忆。
技能还明确了两条硬性约束:没有确认绝不删除(This is destructive);所有检索都限定在当前用户与项目作用域内(通过 user_id + app_id 过滤器)。
使用前提
运行该技能的前提是插件已完成安装并连通 Mem0 Platform:
- 已设置
MEM0_API_KEY(以m0-开头的平台密钥); - MCP 服务器已注册,工具调用形如
mcp__mem0__delete_memory(hooks.json 中钩子匹配器即使用mcp__mem0__.*|mcp__plugin_mem0_mem0__.*这类正则识别 mem0 工具调用); - 会话中已确定
active_user_id与project_id(分别对应记忆作用域的user_id与app_id)。
技能文档中的 SCRIPT_DIR 解析也体现了多宿主兼容:
SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts"
即依次回退 Claude、Codex、Cursor 三类宿主注入的插件根目录变量。
五步执行流程:从输入解析到删除汇报
下面完整继承 SKILL.md 的 Execution 部分,并结合 MCP 工具语义逐项说明。
Step 1:解析输入
用户可能提供两种形式的参数:
- 搜索查询:
/mem0:forget auth module decisions - 记忆 ID:
/mem0:forget <memory_id>(形如 UUID 或十六进制字符串)
如果未提供参数,代理必须反问:“What should I forget? Provide a search query or memory ID.”——不允许在缺参时做任何猜测性删除。
Step 2:定位记忆
分支 A:提供了记忆 ID
- 用
get_memory工具按 ID 拉取该记忆,验证其确实存在; - 向用户展示摘要:
Found: "<memory content first 120 chars>" (created <date>)——只展示内容前 120 个字符,避免长文本刷屏。
分支 B:提供了搜索查询
- 调用
search_memories,参数约定为:query=<用户查询>filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<project_id>"}]}——AND 语义保证只检索“当前用户 + 当前项目”作用域内的记忆,防止误删其他项目/用户的记忆;top_k=10——一次最多召回 10 条候选。
- 以编号列表展示结果,固定格式:
Found <N> memories matching "<query>":
1. <content, 120 chars> (type: <type>, created: <date>) [ID: <short_id>]
2. ...
每行同时给出内容摘要、类型(type)、创建时间和短 ID,让用户可以按编号或 ID 精确指定删除对象。
Step 3:确认(技能的强制安全门)
- 对搜索列表,询问:“Delete which memories? Enter numbers (e.g., 1,3,5), 'all', or 'cancel'.”——支持多选编号、全选
all或取消cancel; - 对单一记忆 ID,询问:“Delete this memory? [y/N]”——默认否定(N 大写),需要用户显式输入 y;
- 技能文档以加粗强调:Never delete without confirmation. This is destructive.
这一步是破坏性操作与用户意图之间的唯一闸门,任何自动化流程(包括代理的“自主模式”)都不应跳过。
Step 4:执行删除
对每一条被确认的记忆,调用 delete_memory 工具并传入该记忆 ID。注意是逐条调用,而不是批量清空——批量清空属于另一个独立工具 delete_all_memories(见 README.md 的工具表),不在本技能的职责范围内。
Step 5:汇报结果
- 成功时输出:
Deleted <N> memories. - 若部分删除失败,必须逐条报告哪条失败、失败原因,不允许静默吞掉错误。
从仓库钩子代码还可以看到删除操作的旁路观测:on_post_tool_use.sh 的 case 分支中,*__delete_memory 命中后只调用 telemetry.py tool_use --tool=delete_memory 上报事件,不写入本地统计(__add_memory 和 __search_memories|__get_memories 才会调用 session_stats.py add/search)。这说明删除是低频、高后果操作,被单独纳入遥测而不计入会话读写计数。
撤销最近写入:基于会话统计的 undo 机制
SKILL.md 的 “Undo recent writes” 章节定义了另一条入口:当用户说 “undo last N memories” 或 “undo last write” 时:
- 读取本会话最近写入的记忆 ID——执行只读探查命令:
SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}/scripts"
python3 "$SCRIPT_DIR/session_stats.py" peek
- 解析 JSON 输出中的
recent_ids数组,每个元素含三个字段:id(记忆 ID)、category(分类)、ts(写入时间戳); - 展示最近 N 条(默认 N=1)并请求确认;
- 对确认的条目逐条调用
delete_memory删除。
如果 recent_ids 为空,则提示用户:“No recent memory IDs tracked this session. Try /mem0:tour to browse recent memories, or /mem0:forget <search query> to find specific ones.”——把用户引导到浏览或搜索路径,而不是直接报错。
源码解析:recent_ids 从哪来
上述撤销机制的数据源头是 session_stats.py。该脚本按用户维护一个临时统计文件:
STATS_FILE = f"/tmp/mem0_session_stats_{os.environ.get('USER', 'default')}.json" # L21
MAX_RECENT_IDS = 50 # L45
关键函数行为(以 session_stats.py 源码为准):
init():新会话开始时重置全部字段,recent_ids清空;record_add(category, memory_id):adds计数 +1、累加category_counts,并在提供了 memory_id 时向recent_ids追加{"id", "category", "ts"},超出MAX_RECENT_IDS(50)时只保留最新的 50 条(滑动窗口裁剪);peek():直接json.dumps返回当前统计,不删除统计文件(与report()清理文件的语义区分开)——这正是 forget 技能第一步peek命令能安全读取的原因。
写入链路:生命周期钩子如何填充统计
recent_ids 的数据由插件的生命周期钩子(hooks.json)写入,链路如下:
| 钩子事件 | 脚本 | 行为 |
|---|---|---|
SessionStart |
on_session_start.sh | 调用 session_stats.py init,每用户每会话一个统计文件 |
PostToolUse |
on_post_tool_use.sh | 从 stdin JSON 解析 tool_name,命中 *__add_memory 时提取 metadata.type/category 后执行 session_stats.py add "$CATEGORY";命中 *__search_memories|*__get_memories 时执行 session_stats.py search |
PreToolUse(mem0 工具) |
enforce_metadata_defaults.sh | 在强制 user_id/app_id 元数据默认值的同时,旁路追加 session_stats.py add / search 记录 |
需要注意一个实现细节:从源码结构看,on_post_tool_use.sh 当前只向 session_stats.py add 传入分类一个参数,CLI 解析中 memory_id 取自第三个位置参数(session_stats.py),因此经由该钩子路径写入的 record_add 不会携带记忆 ID,recent_ids 保持为空;auto_capture.py 中的 session_stats.record_add("auto_capture") 同样未传 ID。这解释了 SKILL.md 为何专门设计了 recent_ids 为空时的兜底话术——撤销流程在“本会话未记录到记忆 ID”的场景下会优雅降级到 /mem0:tour(浏览)或 /mem0:forget <query>(搜索)两条路径,而不是失败。若后续某条写入路径开始向 record_add 传入真实记忆 ID,undo 流程即可直接按 ID 删除,无需搜索。
测试证据
test_session_stats.py 对 undo 机制依赖的每个行为都有针对性断言:
test_recent_ids_tracked:连续两次带 ID 的record_add后,recent_ids按序保存id与category;test_recent_ids_capped:写入 60 条后recent_ids恰好保留 50 条,且最早保留项为第 10 条(id-{10}),验证了滑动窗口裁剪;test_recent_ids_empty_without_memory_id:不带 memory_id 的record_add不会向recent_ids追加任何条目;test_peek_returns_json_without_clearing与test_cli_peek:peek以 JSON 形式返回完整统计且不删除文件,CLI 子进程调用同样可用;test_cli_init/test_cli_report_no_data:验证init、report两个子命令的退出码与无数据兜底输出。
这些用例与 SKILL.md 的 undo 步骤一一吻合,可以作为该技能行为契约的回归依据。
与相邻技能的协作关系
/mem0:forget 不是孤立存在的,它在 17 个技能中承担“删除”这一环,与检索、保护类技能形成闭环(命令表见 README.md 的 Available Skills 一节):
| 场景 | 推荐命令 | 说明 |
|---|---|---|
| 找不到要删的记忆 | /mem0:tour |
按分类浏览全部记忆 |
| 快速定位候选 | /mem0:peek |
单行摘要式快速搜索 |
| 删除明确目标 | /mem0:forget <query 或 ID> |
本文主题,删除前强制确认 |
| 防止关键记忆被清理 | /mem0:pin |
为重要记忆加保护 |
| 记忆冗余/矛盾治理 | /mem0:dream、/mem0:memory-reviewer |
合并重复、审计过期记忆,是删除前的“体检”环节 |
实践上较稳妥的顺序是:先用 /mem0:memory-reviewer 或 /mem0:dream 找出重复、矛盾、过时的记忆,再用 /mem0:forget 精确删除,最后用 /mem0:stats 核对作用域内的记忆数量变化。
小结
/mem0:forget以“检索(get_memory/search_memories+AND过滤器)→ 编号/ID 确认 → 逐条delete_memory→ 结果汇报”的五步协议,把破坏性删除约束在“当前用户 + 当前项目”作用域内,并以确认门杜绝误删;- 其 undo 能力依赖 session_stats.py 的
recent_ids(上限 50 条、滑动裁剪),由SessionStart/PostToolUse等钩子链路写入,peek子命令提供无副作用读取; - tests/test_session_stats.py 对 ID 记录、50 条上限、空 ID 不落盘、peek 不清文件等行为均有断言,可作为理解该机制的权威参照。
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 StartedRust0622
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