首页
/ mem0 Export Skill 解析:把 AI Agent 项目记忆导出为可移植 Markdown 的完整实现(mem0-plugin)

mem0 Export Skill 解析:把 AI Agent 项目记忆导出为可移植 Markdown 的完整实现(mem0-plugin)

2026-09-04 18:34:40作者:秋泉律Samson

本篇围绕 mem0 插件的 export Skill(/mem0:export 命令背后)展开,完整还原其"解析身份 → 分页拉取全量记忆 → 格式化为 YAML frontmatter 块 → 写入导出文件 → 打印汇总"的五步流程,并结合插件配套脚本 parse_export_file.py、Python SDK 的 get_all() 分页实现与测试用例,解释导出文件为何必须严格遵守该格式,以及它如何与 import Skill 构成备份—迁移闭环。读完后你可以掌握:如何在 Agent 会话中正确执行项目记忆导出、逐字段理解导出文件格式契约,以及如何用仓库自带的解析器验证导出结果的可导入性。

一、Mem0 Export 是什么:定位与适用场景

export 是 mem0-plugin 内置的 17 个 /mem0: 技能之一,其技能定义位于 export/SKILL.md,技能头部的 frontmatter 声明了它的用途:

Exports all project memories to a portable Markdown file for backup or migration. Use when backing up memories, migrating to another project, sharing memory state with teammates, or archiving before cleanup.

即:把当前项目在 Mem0 平台上的全部记忆导出为一个可移植的 Markdown 文件插件 README 的技能表中将其描述为 "Export memories to portable Markdown",对应的命令为 /mem0:export

适用场景(原文档明确列出):

  • 备份(backing up memories):清理或删除记忆前先留档;
  • 跨项目迁移(migrating to another project):与 /mem0:import 配合,把 A 项目的记忆状态搬到 B 项目;
  • 团队共享(sharing memory state with teammates):Markdown 文件可直接进版本库或随 PR 流转;
  • 归档(archiving before cleanup):批量删除前的快照。

需要说明的前提:该 Skill 运行在已安装 mem0 插件的 AI 编程环境(Claude Code / Cursor / Codex 等)中,通过插件注册的远程 MCP Server 提供 get_memoriesadd_memory 等工具(见 mcp_config.json,认证头为 Token ${MEM0_API_KEY})。也就是说,导出动作由 Agent 调用 get_memories 工具完成,而不是本地直连存储——这也是"可移植"的关键:文件本身不依赖任何本地状态。

二、执行流程全解:五步导出

以下流程完整继承自 export/SKILL.md 的 "Execution" 章节,并逐步补充仓库内的实现佐证。

Step 1:解析身份(Resolve identity)

导出前先确定"导出谁在哪个项目的记忆",涉及两个标识符:

  • user_id:取 MEM0_USER_ID 环境变量;未设置则取 $USER;再没有则用 "default"
  • project_id(在 API 中作为 app_id 使用):取 MEM0_PROJECT_ID 环境变量,否则走项目解析器(project resolver)。

这条解析规则并非 Skill 的孤立约定,而是整个插件的统一身份体系。在 _identity.py 中可以确认 resolve_user_id() 的实现与文档完全一致:

def resolve_user_id() -> str:
    explicit = os.environ.get("MEM0_USER_ID", "").strip()
    if explicit:
        return explicit
    return os.environ.get("USER") or "default"

而 project resolver 的完整优先级定义在 _project.pyresolve_project_id() 中,按顺序为:

  1. MEM0_PROJECT_ID 环境变量(显式覆盖);
  2. ~/.mem0/project_map.json 按 cwd 查表;命中失败时还会按 git remote URL 的 SHA-256 哈希做"自愈式"兜底(文件夹改名/移动后仍能找回旧映射);
  3. git remote slug:剥离协议前缀与 .git 后缀,取 owner-repo(如 git@github.com:mem0ai/mem0.gitmem0ai-mem0),兼容 HTTPS、SSH、ssh://git:// 及自定义 host 别名等多种 URL 形态;
  4. 兜底:当前工作目录的 basename。

从源码结构看,同一文件里的 resolve_branch() 会执行 git branch --show-current,失败返回 "unknown"——这正好对应导出 frontmatter 中的 branch 字段的典型来源。理解了这条解析链,就能解释为什么 MEM0_PROJECT_ID 是导出前最值得检查的变量:它决定了导出文件名中的 <project_id> 以及拉取记忆时 app_id 过滤条件的取值。

Step 2:分页拉取全部记忆(Fetch all memories)

Skill 指定调用 get_memories 工具,参数为:

  • filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<active_project_id>"}]}
  • page_size=200

分页终止条件(原文档明确要求):若响应是分页的——即结果中包含 next cursor,或本条数等于 page_size——就继续翻页,直到取完所有记忆

这一点在 Python SDK 的实现中得到印证。插件走的是远程 MCP 的 get_memories 工具,其底层语义与 SDK 的 get_all() 一致,见 main.py

def get_all(self, options: Optional[GetAllMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
    """Retrieve all memories, with optional filtering.
    ...
    Returns:
        A paginated dict: {"count": int, "next": str | None, "previous": str | None, "results": [...]}
    """
    # Reject top-level entity params - must use filters instead
    invalid_keys = ENTITY_PARAMS & set(kwargs.keys())
    if invalid_keys:
        raise ValueError(
            f"Top-level entity parameters {invalid_keys} are not supported in get_all(). "
            f"Use filters={{'user_id': '...'}} instead."
        )

两个关键事实可以直接指导导出实现:

  1. 返回体是标准分页字典{"count", "next", "previous", "results"}。因此 Skill 中的"结果包含 next cursor"判断与 SDK 契约吻合;请求会 POST/v3/memories/,并把 pagepage_size 作为 query 参数附带。
  2. 身份参数必须放在 filters:SDK 显式拒绝顶层的 user_id/app_id 等实体参数(抛出 ValueError 并提示改用 filters={'user_id': '...'})。这正是 export Skill 把身份写进 filtersAND 数组而非顶层的原因。

分页参数本身由 types.pyGetAllMemoryOptions 定义:filterspagepage_size,另支持 start_date/end_date(ISO 8601 时间窗)、categoriesshow_expiredlatest_only。export 场景只用到 filters + page_size,但这也提示:如果只需要导出某批分类的记忆,可以在此基础上追加 categories 过滤(属于原文档流程之外的可选扩展,官方 Skill 默认拉全量)。

Step 3:把每条记忆格式化为 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>

各字段含义与来源:

字段 取值来源 缺失时写法
id memory.id,平台侧记忆主键
created_at memory.created_at(时间戳,如 2024-01-15T10:00:00Z 空串
type memory.metadata.type(记忆类型标签) 空串
confidence memory.metadata.confidence(置信度) 空串
branch memory.metadata.branch(捕获时的 git 分支) 空串
files memory.metadata.files 列表,用 ", " 拼成单行 空串
categories memory.categories 列表,用 ", " 拼成单行 空串

原文档的格式注意事项(必须严格遵守):

  • --- 分隔符必须独占一行且行尾无多余空白
  • filescategories 以逗号分隔的形式写在同一行
  • 每条记忆正文之后、下一条 --- 之前留一个空行(为了可读性);
  • 字段缺失或为 null 时写空字符串,绝不能写 "null"

Step 4:写入导出文件

输出文件名规则(原文档原文):

mem0-export-<project_id>-<YYYY-MM-DD>.md

其中 <YYYY-MM-DD>当天 UTC 日期。文件通过 Write 工具(或等效手段)写入当前工作目录

命名中包含 project_id 与日期有两个实际意义:其一,import 侧是靠文件名里的 mem0-export 子串来发现候选文件的(见后文),project_id 让多项目导出互不混淆;其二,UTC 日期保证同一天的重复导出文件名稳定,便于覆盖式刷新。

Step 5:打印汇总

Exported <N> memories to <filename>

<N> 为实际写入的记忆块总数。

错误处理(原文档 Error Handling 全量继承)

  • get_memories 返回错误或零条记忆,输出:
    No memories found for project <project_id>. Nothing exported.
    
  • 若写文件失败,向用户报告错误。

值得注意的设计是:空结果被视为"正常分支"而非报错——导出零记忆不会中断流程,只是明确告知未导出任何内容,这对自动化脚本友好。

三、格式契约的底层原理:为什么必须"精确"

export 文件不是给人看的普通笔记,而是 import 侧解析器的结构化输入。仓库里的 parse_export_file.py 就是这条契约的反向实现,读它可以从"消费者"视角理解每个格式细节为什么不能含糊:

  1. 分块边界:解析器用正则 (?m)^---\s*$ 按整行 --- 切分全文,奇偶下标配对"frontmatter + 正文"。这解释了 export 侧"--- 必须独占一行"的原因——一旦分隔符混入正文或行尾多出不规则字符,切分结构即被破坏。
  2. frontmatter 取值规则_parse_frontmatter()^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$ 匹配 key: value只按第一个冒号切分,值里可以合法包含冒号(因此 created_at: 2024-01-01T10:00:00Z 这类时间戳能完整保留)。
  3. 列表字段filescategories_parse_list_field() 按逗号拆分并去除空白,空值归一为 []——对应 export 侧"逗号分隔单行"的写法。
  4. 空内容跳过:正文为空或纯空白的块直接丢弃;CLI 入口保证任何异常(缺参、文件不存在)都输出 [] 且退出码为 0,让上层 Skill 可以安全地"解析失败即 []"地分支处理。

测试用例 test_parse_export_file.py 中的 test_parse_blocks_round_trip 专门验证了按 export Skill 规则生成的块能被解析器无损还原——即"导出格式 → 解析"是一个被单测守护的往返契约(round trip):

block = (
    "---\n"
    f"id: {memory_id}\n"
    f"created_at: {created_at}\n"
    ...
    f"files: {', '.join(files)}\n"
    f"categories: {', '.join(categories)}\n"
    "---\n"
    f"{memory_content}\n"
    "\n"
)
records = parse_blocks(block)
assert r["files"] == files
assert r["content"] == memory_content

此外还有针对多行正文、缺失可选字段、含冒号值、空输入等边缘情况的测试。实践含义是:手工编辑导出文件时,宁可少写字段也不能改坏 --- 结构与逗号分隔写法,否则对应记录会在导入阶段被静默跳过。

四、导出之后:与 import Skill 的备份—迁移闭环

导出的真正价值在 import/SKILL.md 中兑现,两者共用同一套文件格式。/mem0:import <filename> 的流程是:

  1. 按文件名包含 mem0-export.md 文件定位导出文件(多个时询问用户);
  2. 运行 python3 "<PLUGIN_ROOT>/scripts/parse_export_file.py" <file> 得到 JSON 记录数组,每条含 idtypeconfidencebranchfilescategoriescontent
  3. 对每条记录调用 add_memorytext=<content>user_id/app_id 取当前活跃身份、metadata 回填 type/confidence/branch/files(非空才传),并附加 "source": "import"infer=False(原文档明确要求不传原始 id——平台会分配新 ID);
  4. 单条失败不中断,最后输出 Imported <N>/<total> memories into project <project_id>

由此形成完整的运维闭环:/mem0:export 留档 → 文件随版本库/IM 流转 → 目标项目 /mem0:import 恢复。由于导入是幂等可重跑的(去重由平台处理),导出文件可以放心地多次使用。

五、实操清单与验证方法

在 Agent 会话中执行导出的最小前提与步骤:

  1. 确认身份MEM0_API_KEY 已配置(插件 MCP 认证依赖它);MEM0_USER_IDMEM0_PROJECT_ID 按需显式设置,否则按前文的解析链回退到 $USER/default 与 git remote slug。

  2. 发起导出:在支持该插件的客户端中运行 /mem0:export(或在对话中要求"导出本项目的全部记忆",Agent 将按 export/SKILL.md 的五步执行)。

  3. 核对产物:当前工作目录应出现 mem0-export-<project_id>-<YYYY-MM-DD>.md,终端输出 Exported <N> memories to <filename>

  4. 验证可导入性(可选):用仓库自带解析器做一次"试解析",确认块数量与字段:

    python3 integrations/mem0-plugin/scripts/parse_export_file.py ./mem0-export-<project_id>-<YYYY-MM-DD>.md
    

    输出为 JSON 数组;输出 [] 说明文件格式不符合契约(多半是 --- 分隔行被改动),应重新导出而非手工修补。

六、小结

mem0-plugin 的 export Skill 用一份 5 步指令定义了 Agent 记忆的"可移植载体":身份解析决定导出范围(user_id + app_idAND 过滤),page_size=200next cursor 保证全量拉取,YAML frontmatter 块格式则是一条被 parse_export_file.pytest_parse_export_file.py 双向守护的结构化契约。理解这条"导出格式 → 解析 → 重新入库"的链路后,你就能把 Mem0 的项目记忆当作普通代码资产一样备份、评审与迁移——这正是该 Skill 在 插件 README 中列出的 backup / migration / sharing / archiving 四个场景能够落地的全部基础。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384