mem0 import 技能实战:从导出文件、第三方 AI 工具与 MEMORY.md 迁移记忆到当前项目
mem0-plugin 的 import 技能负责把已有知识灌入当前项目的 Mem0 记忆空间,覆盖三种来源:mem0 自己导出的 Markdown 备份文件、其他 AI 编码工具(Cursor / Copilot / Cline / Continue)的项目配置,以及 Claude Code 原生的 MEMORY.md。读完本文,你能完整掌握 /mem0:import 的五步执行流程、--tools 迁移子命令、导出文件的块级格式与解析器实现细节,并能通过测试用例验证导入结果的正确性。
三种导入来源与适用场景
import 技能的定义位于 SKILL.md,其 frontmatter 中的 description 明确了四个使用场景:从另一个项目迁移、从备份恢复、导入 Claude Code 原生 MEMORY.md 内容、为已有知识初始化新项目。具体到导入来源,分三条路径:
| 来源 | 触发方式 | 底层脚本 |
|---|---|---|
mem0 导出文件(mem0-export-*.md) |
/mem0:import <filename> 或无参数自动发现 |
parse_export_file.py |
| 第三方 AI 工具配置 | /mem0:import --tools |
import_competing_tools.py |
Claude Code 原生 MEMORY.md |
传入路径,或会话启动钩子检测到后用户选择导入 | 直接调用 add_memory(无独立脚本) |
导出文件的"另一半"由姊妹技能 export 生成,格式定义在 export/SKILL.md 中,命名约定为 mem0-export-<project_id>-<YYYY-MM-DD>.md(日期取 UTC)。两者构成一对可逆的"备份—恢复"通道,这也是本文解析器部分的重点。
标准导入流程:五步解析与写入
Step 1:确定要导入的导出文件
若用户通过 /mem0:import <filename> 显式指定了文件名,直接使用该文件。否则在当前目录列出文件名包含 mem0-export 的 .md 文件:
ls -1 *.md 2>/dev/null | grep mem0-export || echo "No export files found"
- 找到多个文件时,询问用户选择哪一个导入;
- 一个都没找到时,输出提示并引导用户:
No mem0-export files found in the current directory.
Run /mem0:export first, or provide the filename: /mem0:import <path-to-file>
Step 2:解析导出文件为 JSON
先确定插件根目录,按当前平台使用对应变量:
- Claude Code:
${CLAUDE_PLUGIN_ROOT} - Codex:
${CODEX_PLUGIN_ROOT} - Cursor:
${CURSOR_PLUGIN_ROOT}
然后运行解析器脚本,将 Markdown 导出文件提取为 JSON 记录数组:
python3 "<PLUGIN_ROOT>/scripts/parse_export_file.py" "<path-to-export-file>"
输出的 JSON 数组中,每个元素包含以下字段:
id— 原始记忆 ID(仅用于参考;导入时会分配新 ID)type— 元数据类型confidence— 元数据置信度branch— 元数据分支files— 关联文件列表categories— 分类列表content— 记忆正文文本
脚本失败或输出 [] 时,打印 Failed to parse <filename> or file contains no valid memory blocks. 并停止。
源码层解析实现:parse_export_file.py 的核心是 parse_blocks(L33-L94)。导出文件由恰好为 --- 的行分隔成块,结构上奇数索引是 frontmatter、偶数索引是正文。解析逻辑有三个值得注意的细节:
- 成对扫描:从索引 1 开始按
frontmatter + content成对取值(i += 2),frontmatter 用_parse_frontmatter(L97-L113)逐行匹配key: value,且只用第一个冒号作分隔符,因此时间戳等含冒号的值不会被截断——测试test_parse_blocks_value_with_colon专门验证了这一点(test_parse_export_file.py)。 - 列表字段拆分:
files和categories在文件中是逗号分隔的单行值,_parse_list_field(L116-L123)将其拆成去除空白的列表,空值返回[]。 - 空内容过滤:
content为空的块被直接跳过(对应 SKILL.md 中"跳过 content 为空的记录"的防御性要求),标量字段缺失时默认为空字符串、列表字段缺失时默认为空列表。
main 入口(L126-L146)保证"总是以 0 退出"的约定:参数缺失、文件不存在或读取失败时,都向 stderr 输出错误信息、向 stdout 输出 [] 并退出码为 0,方便上游以统一方式判断"无有效记录"。
Step 3:解析身份(user_id 与 project_id)
确定活动身份:
user_id:MEM0_USER_ID环境变量 →$USER→"default"三级回退;project_id(作为app_id使用):MEM0_PROJECT_ID环境变量,或由项目解析器推导。
源码级回退链: _identity.py 中 resolve_user_id(L71-L75)实现了上述 user_id 回退;而 project_id 的"项目解析器"在 _project.py 的 resolve_project_id(L21-L73)中,实际优先级更长:
MEM0_PROJECT_ID显式覆盖;~/.mem0/project_map.json按当前工作目录查表,未命中时按 git remote URL 的 SHA-256 前 16 位(remote:<hash>)做自愈查表——文件夹被移动/重命名后仍能命中,并回写新 CWD 键;- 从
git remote get-url origin推导 slug(如git@github.com:mem0ai/mem0.git→mem0ai-mem0),支持 HTTPS / SSH /ssh:///git://等写法; - 兜底为当前目录名。
另外,API key 的解析(resolve_api_key,L55-L68)还有一条 Desktop 场景专属路径:桌面应用不继承 shell profile 的环境变量,因此会直接从 ~/.zshrc、~/.bashrc 等文件中用正则提取 MEM0_API_KEY=... 的值。
Step 4:逐条调用 add_memory 导入
对解析出的每条记录调用 add_memory:
text="<record.content>"user_id=<active_user_id>app_id=<active_project_id>metadata={"type": "<record.type>"(非空时)"confidence": "<record.confidence>"(非空时)"branch": "<record.branch>"(非空时)"files": <record.files>(非空列表时)"source": "import"}
infer=False
三条关键约定:
- 不传原始
id——平台会为导入的记忆分配新 ID; - 跳过
content为空的记录(解析器已过滤,但调用侧仍需防御); - 单条失败不中断,继续导入并统计成功数。
infer=False 意味着写入的记忆不会被 LLM 重新提取/改写,而是原样落库——这对备份恢复语义至关重要,保证导入内容与导出内容一致。从 import_competing_tools.py 的 post_memory(L68-L99)可以看到 add_memory 在平台 API 上的真实形态:POST https://api.mem0.ai/v3/memories/add/,请求体为 messages(user 角色包裹 content)、user_id、app_id、metadata、infer 五个字段,鉴权使用 Authorization: Token <MEM0_API_KEY>,成功判定为 HTTP 200/201。
Step 5:打印结果
Imported <N> memories into project <project_id>
若存在失败,输出 Imported <N>/<total> memories into project <project_id> (<failed> failed)。
导出文件块格式:import 与 export 的可逆性
理解 import 的前提是理解它解析的对象。export 技能(export/SKILL.md)规定每条记忆输出为一个精确格式的 YAML-frontmatter 块:
---
id: <memory.id>
created_at: <memory.created_at>
type: <memory.metadata.type or "">
confidence: <memory.metadata.confidence or "">
branch: <memory.metadata.branch or "">
files: <memory.metadata.files joined with ", " or "">
categories: <memory.categories joined with ", " or "">
---
<memory.memory or memory content string>
格式约束:--- 分隔符独占一行且无多余空白;files、categories 写成逗号分隔的单行;缺失字段写空字符串而非 "null";正文后留空行。export 侧通过 get_memories 分页拉取(filters={"AND": [{"user_id": ...}, {"app_id": ...}]},page_size=200),写入 mem0-export-<project_id>-<YYYY-MM-DD>.md。
测试文件 test_parse_export_file.py 中的 test_parse_blocks_round_trip 正是按 export 技能的格式构造块并断言解析结果完全还原(含 files、categories 列表、created_at 附加字段),从工程上锁定了"export → import" 的往返一致性。该文件还覆盖了多块解析、缺失可选字段默认值、空内容过滤、多行正文、无分隔符内容返回空列表、CLI 无参数/缺文件输出 [] 且退出码为 0 等边界场景。
从第三方 AI 工具迁移:--tools 子命令
以 --tools 调用时(如 /mem0:import --tools),技能会检测并导入其他 AI 编码工具的项目配置文件。
支持的工具
| Tool | 文件/目录 |
|---|---|
| Cursor | .cursorrules |
| GitHub Copilot | .github/copilot-instructions.md |
| Cline | memory-bank/(.md 文件目录) |
| Continue | .continue/rules.md |
T1:检测
test -f .cursorrules && echo "cursor: .cursorrules"
test -f .github/copilot-instructions.md && echo "copilot: .github/copilot-instructions.md"
test -d memory-bank/ && echo "cline: memory-bank/"
test -f .continue/rules.md && echo "continue: .continue/rules.md"
T2:询问用户
列出发现的文件,请用户以编号(逗号分隔)或 "all" 选择要导入的项。若什么都没找到,输出:
No competing tool configuration files found.
Checked: .cursorrules, .github/copilot-instructions.md, memory-bank/, .continue/rules.md
T3:执行导入
对每个选中的工具运行:
python3 "<PLUGIN_ROOT>/scripts/import_competing_tools.py" <tool> --path <file>
<tool> 取值为 cursorrules、copilot、cline、continue,与脚本中 COMMANDS 分发表(L274-L279)一一对应。
T4:报告
Imported <N> memories into <project_id> (cursor: <N>, copilot: <N>)
源码剖析:分块策略与幂等去重
import_competing_tools.py 的每个子命令都遵循"读文件 → 分块 → 过滤截断 → 逐块写入"的流水线,且各工具的切分策略不同:
- cursorrules / copilot:按
##二级标题切分(split_by_headers),标题行保留在每个块开头;若文件没有二级标题,整文件作为一个块(见测试test_split_by_headers_cursorrules、test_split_by_headers_no_headers,test_import_competing_tools.py); - cline:
memory-bank/目录下每个.md文件整体作为一个块(按文件名排序逐个处理); - continue:
.continue/rules.md可能用---水平线或##标题分隔,故用split_by_hr_or_headers(两者都作为分隔点,但---后不带标题行,##保留标题行)。
所有分块随后经过 _chunking.py 的 filter_and_truncate(L66-L75):短于 MIN_CHUNK_CHARS = 50 的块被丢弃,长于 MAX_CHUNK_CHARS = 10_000 的块被硬截断——这正对应 SKILL.md 中"sections <50 chars skipped,chunks >10k chars truncated" 的说明。
写入侧有两个工程细节:
- metadata 打标:每块以
metadata.type="project_profile"、metadata.source=<tool>-import(如cursor-import、copilot-import)、以及当前 git 分支写入,infer=False原样落库; - 幂等去重:
import_chunks(L102-L126)将同一次导入的所有块拼接后做 SHA-256,与~/.mem0/import_hashes.json中以project_id:source:path为键的历史哈希比对,内容未变化则整体跳过并打印Already imported (unchanged) -- skipping: ...。这就是"safe to re-run" 的依据——重复运行--tools不会产生重复记忆。
导入 Claude Code 原生 MEMORY.md
当传入 Claude Code 原生 MEMORY.md 的路径(通常为 ~/.claude/projects/<proj-key>/memory/MEMORY.md),或者 on_session_start.sh 检测到原生 auto-memory 且用户选择导入时,执行以下流程:
- 读取文件。其内容为按行分隔的记忆条目(一行一个事实,可能带
-前缀); - 按非空行切分,每一行成为一条记忆;
- 跳过短于 20 字符的行,以及纯标题行(
#开头); - 对每行调用
add_memory:text="<line>"user_id=<active_user_id>app_id=<active_project_id>metadata={"type": "task_learning", "source": "memory-md-import", "confidence": 0.8}infer=False
- 报告:
Imported <N> memories from MEMORY.md into project <project_id>; - 建议关闭原生 auto-memory,避免双记忆系统:
To avoid duplicate memory systems, add to ~/.claude/settings.json:
"autoMemoryEnabled": false
这条路径解决的是冷启动空窗问题:用户一直在用 Claude Code 内置记忆、切换到 mem0 时,既有知识不会丢失,而是以 task_learning 类型、0.8 置信度一次性迁移过来。
错误处理与故障排查
- 解析脚本缺失:若
<PLUGIN_ROOT>/scripts/parse_export_file.py不存在,打印错误并停止(通常意味着插件未完整安装); - add_memory 持续失败:例如认证错误(
MEM0_API_KEY未设置或失效)导致连续失败时,应报告问题并提前终止,而不是把剩余记忆全部打一遍失败请求。API key 的来源优先级与环境变量设置方式,可参考插件 README 的 Step 1(README.md); - 解析器总是退出码 0:如前所述,
parse_export_file.py的失败语义靠 stdout 的[]表达,上游(技能)需要以"输出是否为空数组"作为停止条件,而不是依赖退出码。
相关能力对照
import 只是 mem0-plugin 记忆生命周期的一环,可结合以下脚本理解完整链路:
- export/SKILL.md:生成 import 所消费的备份文件,两者构成往返可逆的备份对;
- auto_import.py:会话启动时后台自动导入
CLAUDE.md、AGENTS.md、.cursorrules等项目声明文件,同样采用 SHA-256 哈希跳过未变化文件(哈希存储于~/.mem0/file_hashes.json),并用文件锁防止并发重复执行——与import_competing_tools.py的幂等设计同源; - parse_mem0_config.py、_identity.py:身份与配置解析的公共依赖。
小结
/mem0:import 是 mem0-plugin 中把"外部知识资产"转成平台记忆的统一入口:标准路径通过 parse_export_file.py 把 --- 分隔的 frontmatter 块还原为带 type/confidence/branch/files/categories 元数据的记录,以 infer=False 原样写回,保证备份恢复不失真;--tools 路径按各工具的文件组织方式分块(50 字符下限、10k 字符截断),并以内容哈希实现幂等重跑;MEMORY.md 路径则以固定元数据(task_learning / 0.8 置信度)弥合从 Claude Code 原生记忆迁移过来的冷启动空窗。三条路径共享同一套身份解析(_identity.py + _project.py 的 project_id 四级回退链),测试文件 test_parse_export_file.py 与 test_import_competing_tools.py 则为上述解析、分块与往返行为提供了可复现的验证依据。
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