Skip to content

AI 记忆系统

记忆是系统越用越懂你的机制。

MateClaw 里其他所有东西,在你配置完之后就静止了。Agent、工具、知识库——你改它们的时候才改。记忆是唯一一个会自己改变的部分,变化是实际使用过程的副产品。这就是整个设计的核心意图。

它在你睡着的时候做了一个关于你的梦

不是营销词。是 memory/dreaming/ 包里真实跑的代码。

每天凌晨 3 点(默认时间,可改),系统跑一次调度任务,名字就叫 Dreaming:扫一遍今天和你聊天的每个 Agent 的对话痕迹,把零散的线索整合成对你的理解,过滤掉一次性的、矛盾的、过期的,把高频出现的提升进 MEMORY.md,整个"看见了什么、得出了什么、改写了什么"的过程追加进 DREAMS.md——一条人类可读的审计线。

第二天早上你打开它,它从昨天结束的地方继续,不是从零开始。

别的 AI 每天从零开始。MateClaw 从昨天结束的地方继续。

这一页讲组成记忆的四个层、每个 Agent 的记忆文件、以及 Agent 自己怎么在对话中读写这些文件。


四个层

  ┌────────────────────────────────────────────────────────────┐
  │  1. 当下这一回合                                              │
  │     正在说的话、刚刚说过的话、按 token 预算自动裁剪           │
  │     更新时机:每一回合                                        │
  └────────────────────────────────────────────────────────────┘

                            ▼(对话完成之后)
  ┌────────────────────────────────────────────────────────────┐
  │  2. 对话结束后的提取                                          │
  │     从对话里挑出值得记住的事,写进 PROFILE.md / MEMORY.md     │
  │     和当天的日常笔记                                         │
  │     更新时机:每次有意义的对话结束后异步跑                     │
  └────────────────────────────────────────────────────────────┘

                            ▼(默认每天凌晨 3 点,可调)
  ┌────────────────────────────────────────────────────────────┐
  │  3. 夜里整合(Dreaming)                                     │
  │     扫一遍最近的日常笔记,找出反复出现的模式,               │
  │     合并进 MEMORY.md,把过程记到 DREAMS.md                   │
  │     更新时机:定时触发,可手动                                │
  └────────────────────────────────────────────────────────────┘

                            ▼(下一次对话直接用最新版本)
  ┌────────────────────────────────────────────────────────────┐
  │  4. 工作空间文件进入 system prompt                            │
  │     四个 markdown 文件每一回合都被注入                        │
  │     更新时机:底下文件一变,下一回合就生效                     │
  └────────────────────────────────────────────────────────────┘

每一层跑在不同的时间尺度上。当下是这一回合。提取是每一次对话之后。整合是每天夜里。文件注入是每一回合都用当前最新版本。加在一起它们形成一个循环——你说的话变成上下文,上下文变成文件,文件变成 system prompt,system prompt 变成 Agent 明天知道的东西。


记忆认人:per-owner 隔离(1.5.0)

以前一个员工的记忆是共享的:不管是网页登录的你、还是飞书群里的同事、还是第三方 API 接进来的终端用户,聊出来的记忆都堆进同一个 MEMORY.md。一个员工服务多个人时,记忆会串台。

1.5.0 给每条记忆加了主人(owner)可见范围(scope)

统一的 owner_key

不管身份从哪来,都归一成一个带前缀的字符串:

来源owner_key
网页控制台user:<用户id>
IM 渠道(飞书 / 钉钉 / 企微…)<渠道>:<发送者id>
第三方 API(带 endUserId)api:<endUserId>
系统 / cronsystem

三档可见性

scope谁能读典型内容
PERSONAL(个人)只有匹配的 owner对话里抽取出来的记忆默认进这档
TEAM(团队)用这个员工的人都能读员工配置文件(AGENTS.md / SOUL.md / PROFILE.md)、历史回填的数据
GLOBAL(全局)跨员工 / 工作空间始终可见预置事实、系统参考资料

召回偏好个人记忆

system prompt 里只烤进 TEAM/GLOBAL 的共享记忆(可缓存);每轮再按当前 owner_key 预取他个人的记忆注入。所以问"我的项目用什么栈"时,员工优先回忆这个人的私人记忆文件,而不是知识库里的泛泛资料。

关于结构化"事实"层:事实召回查询本身支持 owner 可见性过滤(PERSONAL 仅 owner 可见,TEAM/GLOBAL 共享)。但当前的自动事实投影主要从共享记忆文件构建、插入时不写 ownerKey/scope——也就是说个人化更多体现在个人记忆文件的预取上,事实层的 per-owner 化还在补齐中。

第三方 API 透传终端用户身份

/api/v1/chat/api/v1/chat/stream 的请求体新增可选字段 endUserId(字符串,保大整数精度)。一个 PAT 认证的接入方代表一个 MateClaw 用户,但可以为每个终端用户传不同的 endUserId,记忆按终端用户自动隔离。

这是一个可开关的特性

总开关是 mate.memory.lifecycle-mediator-enabled

默认值要看清楚

Java 属性的裸默认值是 false,但随发行版打包的 application.yml 把它设成了 true——也就是说默认安装下 per-owner 隔离是开着的。要回到旧的共享行为(所有写入走 TEAM),在你的配置里显式设为 false

打开后:对话抽取写入 owner 的 PERSONAL 记忆,召回按 owner_key 过滤;关闭后所有写入回退到共享 TEAM。多租户实例保持开启,单人部署可以关掉。

底层:迁移 V137mate_workspace_file / mate_memory_recall / mate_fact 三张表加了 owner_key + scope 列,历史行回填为 TEAM(保证升级后没有记忆被藏起来)。remember 等记忆工具会按当前请求上下文解析 owner_key,开关打开时写进该 owner 的 PERSONAL 记忆,关闭时回退共享写入。


多层记忆 + 可插拔 Provider

记忆这一层不是一个硬编码的实现。它是一个接口——多层架构允许你堆叠 provider

  • 默认 Provider 就是这页后面讲的基于工作空间文件的记忆。MateClaw 出厂就带这个,对大多数人来说这一个就够了。
  • 自定义 Provider 可以插入用于专用检索——基于向量的长期记忆、图结构记忆、外部记忆服务。
  • 分层意味着同一个 Agent 可以同时和多个 provider 对话。短期 provider 返回最近上下文;语义 provider 返回相关记忆;Wiki provider 返回权威引用。它们在读取时组合。

对大多数 Agent 来说,默认的就够了,这一节可以跳过。如果你在做某种专用的东西——需要记住上千条事实并用向量搜索、需要图结构记忆——这里就是插入点。开发细节看 架构说明


每个 Agent 都有的四个文件

每个 Agent 都有自己的工作空间。四个 markdown 文件是长期记忆的骨架:

workspace/{agentId}/
├── AGENTS.md          # Agent 怎么用记忆 —— 行为指南
├── SOUL.md            # Agent 是谁 —— 核心身份、人格、边界
├── PROFILE.md         # 你是谁 —— 用户画像、偏好、背景
├── MEMORY.md          # 什么重要 —— 关键决策、项目上下文、待办
└── memory/
    ├── 2026-04-09.md  # 日常笔记 —— 今天发生了什么,追加模式
    ├── 2026-04-10.md
    └── 2026-04-11.md

前四个会在每一回合注入到 system prompt(只要 enabled=true)。每日笔记不会——它们喂给整合服务用。

每个文件是干什么的

  • AGENTS.md——Agent 自己的使用说明书。什么时候该写记忆、每个地方放什么、有哪些工具可以操作记忆。种子:enabled=truesort_order=0
  • SOUL.md——Agent 从根上是谁。自我意识、演化指引、隐私与边界原则。想在深层修改 Agent 的性格时编辑它。种子:enabled=truesort_order=1
  • PROFILE.md——Agent 学到的关于你的东西。名字、职业、技术栈、沟通偏好。对话里出现值得保留的东西时记忆提取器会更新它。全覆盖写入。种子:enabled=truesort_order=2
  • MEMORY.md——Agent 认为重要到值得留下的东西。活跃项目、未决定的事、打开的线索、你让它记住的东西。提取器和整合器都会更新它。种子:enabled=truesort_order=3

1.3.0 新增:工作流可以写记忆

v1.3.0 起,工作流write_memory step 可以在流程跑完时把结果直接写进某位员工的 MEMORY.md(或任意启用的 memory 文件),支持 4 种合并策略:append / replace_section / upsert_kv / overwrite。这意味着记忆不再只能由对话提取或 Dreaming 写入——一条业务流程的产物也可以被沉淀。

每日笔记

对话亮点按日期归档,追加模式——同一天里的多次对话全部累加到同一个文件。这些不会注入到 system prompt(enabled=false)。它们存在是为了让整合器凌晨两点跑的时候有东西可扫。


短期:上下文窗口

每一次 LLM 调用之前,MateClaw 都会构造真正送出去的那个 prompt:

[System Prompt]                        ← 永远在最前
[工作空间文件注入]                      ← AGENTS / SOUL / PROFILE / MEMORY
[对话上下文摘要]                        ← 只有在早期轮次被压缩过时才有
[Message 1: user]
[Message 2: assistant]
...
[当前用户消息]                          ← 永远在最后

工作空间文件按 sort_order 排序拼进 system prompt,格式:

--- AGENTS.md ---
(内容)

--- SOUL.md ---
(内容)

--- PROFILE.md ---
(内容)

--- MEMORY.md ---
(内容)

只注入 enabled=true 的文件。

上下文爆了怎么办

三层防御:

第一层:主动压缩。 估算总 token 超过预算的 75%(默认窗口 12.8 万 token),系统让 LLM 总结早期轮次。尾部基于 token 预算动态保留最近若干条(下限由 preserve-recent-pairsprotect-last-min-messages 两个参数取最大值决定,默认至少保留 10 条)。结果缓存 30 分钟。

第二层:紧急恢复。 如果 LLM 仍然返回上下文超限,系统不再调 LLM,直接丢掉更早的消息、保留最后 2 轮、重试一次。

第三层:硬截断。 总结之后还是超,从前往后继续丢消息直到 prompt 装得下。最近 2 条永远不动。

安全设计——摘要以用户消息形式注入,不是系统消息。刻意的:防止早期用户输入的压缩版本被提升成系统级指令,关掉一条注入攻击路径。

配置

yaml
mate:
  agent:
    conversation:
      window:
        default-max-input-tokens: 128000
        compact-trigger-ratio: 0.75
        preserve-recent-pairs: 2
        summary-max-tokens: 300

对话后提取

一次对话结束之后,系统会异步地把值得记住的东西提取出来、写进 PROFILE.md、MEMORY.md、当天的日常笔记。发生在用户响应路径之外——永远不会阻塞下一个回合

什么时候触发

一个回合完成之后,系统在后台线程处理这次对话。需要满足几个条件才会真的跑提取:

  • 自动总结开关打开
  • 不是定时任务自己触发的对话(防止递归)
  • 消息数达到下限(默认 4 条)
  • 最后一条用户消息够长(默认至少 10 字符)

全部通过,开始提取。

并发控制

  • 冷却——同一个 Agent 在默认 5 分钟内不会重复提取
  • 按 Agent 加锁——同一个 Agent 已经有一个提取任务在跑,新任务直接跳过

LLM 实际在做什么

  1. 从对话历史里加载消息
  2. 读当前的 PROFILE.md、MEMORY.md、今天的日常笔记
  3. 构造 transcript:最多 30 条消息,每条截断到 2000 字符
  4. 用记忆总结的 prompt 模板调 LLM
  5. 解析 JSON 响应
  6. 执行写入

LLM 响应 schema

字段类型作用
should_updateboolean记忆是否需要更新
reasonstring原因(用于审计)
daily_entrystring追加到今天日常笔记的内容
memory_updatestringMEMORY.md 的全新全量内容
profile_updatestringPROFILE.md 的全新全量内容

文件写入规则

  • PROFILE.md——全覆盖,只在 profile_update 非空时写
  • MEMORY.md——全覆盖,只在 memory_update 非空时写
  • memory/YYYY-MM-DD.md——追加,文件不存在时用日期标题新建

整合与 Dreaming

第三层按计划跑。它的工作是看着日常笔记堆起来,周期性地问自己:这里的模式是什么?哪些东西应该被提升进核心记忆?哪些东西过期了应该被遗忘?

它做什么

  1. 列出 Agent 所有 memory/*.md 文件,取最近 7 天
  2. 读这些日常笔记 + 当前 MEMORY.md
  3. 用整合 prompt 模板调 LLM
  4. LLM 返回 {should_update, reason, memory_content}
  5. 如果 should_update 为 true,MEMORY.md 被 memory_content 全覆盖

触发方式

  • 自动——每个 Agent 在系统定时任务里有一行,每天凌晨 3 点跑一次
  • 手动——POST /api/v1/memory/{agentId}/emergence

为什么不会递归

整合跑起来的时候会通过 Agent 触发一次"对话"。没有保护的话,那次对话会再触发对话后记忆提取监听器,循环下去。

事件上带触发源标记,提取监听器看到是定时任务触发的就直接跳过。

DREAMS.md —— 整合日记

每次整合跑完会往 workspace/{agentId}/DREAMS.md 追加一条短记录:

  • 它看了什么
  • 它找到了什么模式
  • 因此 MEMORY.md 变了什么
  • 日期

这给你一条人类可读的审计线——你可以打开 DREAMS.md,看记忆是怎么一步步走到当前状态的。这个文件也有自我增长的上限,超过阈值会对旧记录做总结。

打分式 Emergence + 召回追踪

整合不是盲目地总结。它会追踪:

  • 哪些记忆条目在最近的对话里真的被主动召回——Agent 的读取模式反过来影响整合对"什么重要"的判断
  • 打分式 emergence——候选模式按频率 + 近期性 + 显式召回打分,只有高分的才能进 MEMORY.md
  • 多闸门过滤——低信号的提取(一次性提及、矛盾、用户后来主动纠正过的)会在变成记忆之前被过滤掉
  • Dreaming 状态 API——GET /api/v1/memory/{agentId}/dreaming/status

完整生命周期(开关控制)

记忆从"夜里梦一次"升级成完整的逐轮生命周期。这套行为落在开关后面——开源版默认关,生产构建打开。

它做的事:

  • 每一轮都被记账 —— 每一回合开始和结束时系统都在记笔记,不只是夜里整合的时候
  • 事实投影 —— 对话被拆成结构化的"事实"行,Agent 可以查询。带信任度评分 + 衰减。
  • 结构化的夜间报告 —— 整合产出一份完整报告,可以按主题手动重做
  • 晨报卡片 —— 第二天第一次对话浮出昨天的报告;逐条 Confirm / Edit / Forget
  • 矛盾收件箱 —— 新事实和老事实冲突时给一个决策队列,而不是悄悄覆盖
  • 显式遗忘 —— 你说"忘掉",它就真的忘掉,从所有地方
  • 反馈打分 —— 检索到的事实点👍/👎,反馈进入信任度评分
  • SOUL 自动演化 —— Agent 的人格档会从累积的事实里自我重写
  • 月度归档 —— 老报告滚进压缩的月度归档,时间线里能查
  • 记忆浏览器 —— 时间线、事实、矛盾、变更对比、信任度面板

application.yml 启用(这些开关都在 mate.memory 下,分三个 Phase):

yaml
mate:
  memory:
    # Phase 1:逐轮生命周期总线
    lifecycle-mediator-enabled: true
    dream:
      focused-enabled: true        # 聚焦 dream 端点
      archive-enabled: true        # 月度归档轮转
      archive-keep-days: 30
      max-candidates-per-dream: 100
    # Phase 2:SOUL 自动演化
    soul-update-interval: 20       # 每 20 次写入触发一次 SOUL.md 重写(0 = 关)
    # Phase 3:事实投影
    fact:
      projection-enabled: true
      projection-rebuild-cron: "0 */30 * * * ?"
      contradiction-check-enabled: false   # 矛盾检测(实验,默认关)
      trust-half-life-days: 60
      forget-enabled: true         # UI 上的「遗忘」按钮

晨报卡片是一个端点(GET /api/v1/memory/{agentId}/dream/morning-card),不是单独的开关——只要事实投影 + dream 这套生命周期开着就有数据。


always-on 记忆的尺寸控制

新增

每一回合都注入 system prompt 的那些记忆(user / feedback 结构化条目、PROFILE.mdMEMORY.md)有个隐患——只增不减。条目越攒越多,每轮 token 一路膨胀。这一组机制给"常驻记忆"装上确定性的体积上限。

三个层次各管一段:

注入预算(注入时截断,不动磁盘)

user / feedback 两类结构化条目注入 system prompt 时,按条目的 Updated: 日期排序(LRU),只保留最新的若干条,超出部分在注入时丢弃——磁盘文件不动,并在块尾披露省略了多少条。

  • mate.memory.system-block-max-chars(默认 4000):常驻结构化块的总字符上限,超了就按时间从最老的开始丢;0 = 不限
  • mate.memory.system-block-max-entries-per-type(默认 40):每类(user / feedback)最多注入多少条;0 = 不限

夜间巩固(在存储层缩文件)

注入预算只在注入时截断,磁盘文件本身还在长。巩固是在存储层做合并:每晚定时(默认 03:30,独立于 Dreaming 的开关和时间表)遍历每个员工的共享桶 + 各 per-owner 桶,条目数超过阈值时调 LLM 把近重复 / 过时的条目合并写回。

有一条安全不变量:巩固后的条目数只能减不能增——模型若幻觉出更多条目,这次写入直接跳过。

  • mate.memory.structured-consolidation-enabled(默认 true):关掉就只剩注入截断、没有存储侧合并
  • mate.memory.structured-consolidation-min-entries(默认 8):桶里条目少于此值跳过 LLM 调用省钱
  • mate.memory.structured-consolidation-cron(默认 "0 30 3 * * ?"):独立调度,不碰 dreaming
  • mate.memory.structured-consolidation-max-owners-per-run(默认 50):每个员工每次最多处理多少个 owner 桶,剩下的下次再来;0 = 不限

手动触发:POST /api/v1/memory/{agentId}/structured-consolidation,返回 ownersConsolidated / updated / entriesBefore / entriesAfter 等统计。

别和 Dreaming 搞混:Dreaming 把日常笔记整合进 MEMORY.md(写"重要的东西");巩固只负责把 user / feedback 结构化条目去重瘦身。两件事,两个调度。

文件上限(重写时的确定性兜底)

PROFILE.mdMEMORY.md 由 LLM 全量重写。prompt 里要求它简洁,但没有硬约束,仍可能越写越大。文件上限是写回时的确定性兜底:内容超预算就在最后一个能放下的 ## 二级标题边界截断(保留文件头部的核心段),并追加一行截断标记。

  • mate.memory.profile-max-chars(默认 4000):PROFILE.md 硬上限;0 = 不限
  • mate.memory.memory-md-max-chars(默认 8000):MEMORY.md 硬上限;0 = 不限

Agent 自己读写自己的记忆

记忆不是单向地"发生在 Agent 身上"的事。Agent 自己在对话过程中可以主动读写自己的文件——通过一组工作空间记忆工具:

方法作用
list_workspace_memory_files列出 Agent 的文件,可按文件名前缀过滤,按 sort_order 排序
read_workspace_memory_file读某个文件的内容
write_workspace_memory_file创建或覆盖一个文件(全覆盖)
edit_workspace_memory_file按精确查找替换编辑(增量更新,支持 replaceAll

关键词搜索自己的记忆

1.4.0 新增

员工不止能读整个文件——它在对话中可以按关键词搜索自己工作空间里的全部记忆文件,直接定位到某一行。

这是一个 Agent 运行时能力:员工给一个关键词,系统在它自己的工作空间记忆文件里做检索:

  • 分词——中文按 2 字滑动窗口切,拉丁文按空格切,两种语言都能命中
  • 按文件加权打分——AGENTS.md / MEMORY.md / PROFILE.md 这类核心文件的命中权重高于每日笔记
  • 返回结果——每条命中给出:文件名 + 行号 + 80 字上下文片段(命中词高亮) + 相关性分数
  • 扫描范围——最多扫约 50 个候选文件,按分数从高到低排序

适用场景:员工想确认"我之前是不是记过这件事"、跨多天笔记找回某个具体决定,而不需要把整份文件读进上下文。

示例

列表:

json
// 输入
{"agentId": 1, "filenamePrefix": "memory/"}
// 输出
{"agentId": 1, "count": 3, "files": [
  {"filename": "memory/2026-04-09.md", "enabled": false, "fileSize": 512},
  ...
]}

读取:

json
// 输入
{"agentId": 1, "filename": "MEMORY.md"}
// 输出
{"agentId": 1, "filename": "MEMORY.md", "enabled": true, "content": "..."}

编辑:

json
// 输入
{"agentId": 1, "filename": "MEMORY.md", "oldText": "旧内容", "newText": "新内容"}
// 输出
{"agentId": 1, "filename": "MEMORY.md", "replacements": 1}

安全约束

  • 只允许 .md 文件
  • 不允许绝对路径,不允许 .. 目录穿越
  • write 是全覆盖——在乎已有内容就先 read
  • 新建的文件默认 enabled=false

记忆快照导出 / 导入

1.4.0 新增

一个员工积累的整份记忆可以打包成一个 ZIP 带走——备份、迁移到另一套部署、或者克隆一个"已经认识你"的同事。

快照把一个员工的核心记忆打包成单个 ZIP:

  • AGENTS.md / MEMORY.md / PROFILE.md / SOUL.md / KNOWLEDGE.md
  • 每日笔记(memory/YYYY-MM-DD.md
  • 一份 manifest.json(记录包里有什么、来自哪个员工)

三个端点

方法路径权限作用
GET/api/v1/agents/{agentId}/workspace/memory/exportViewer导出 ZIP——只读权限也能做备份
POST.../workspace/memory/import/previewMember干跑:解析 ZIP,逐文件给出 create / update / skip 分类,不写任何东西
POST.../workspace/memory/importMember应用导入,原子写入

先 preview 看清差异,确认后再 import——导入前你永远知道会改动什么。

安全护栏

  • 白名单——只接受上面列出的那几类文件,其余忽略
  • 防 zip 炸弹——条目数 ≤ 500、单条解压 ≤ 1 MB、总计 ≤ 16 MB,超了直接拒绝
  • 不序列化 UI 开关状态——enabled / sortOrder 不进快照;导入到新员工时由目标端按种子规则决定,不会把源端的开关状态强加过来

UI

  • Agent Context 页面右侧面板Export / Import 两个按钮
  • 导入时先弹出差异对比(哪些新建、哪些覆盖、哪些跳过),确认后才真正写入

配置参考

记忆提取 & 整合

yaml
mate:
  memory:
    # --- 自动提取 ---
    auto-summarize-enabled: true
    min-messages-for-summarize: 4
    min-user-message-length: 10
    skip-cron-conversations: true
    summary-max-tokens: 1000
    max-transcript-messages: 30

    # --- 并发 ---
    cooldown-minutes: 5

    # --- 整合 / dreaming ---
    emergence-enabled: true
    emergence-day-range: 7

    # --- per-owner 记忆隔离(1.5.0)---
    # 随发行版打包的默认值是 true(开):对话抽取写入 owner 的 PERSONAL 记忆,召回按 owner_key 过滤。
    # 设为 false 回到旧的共享行为(所有写入走 TEAM)。Java 属性裸默认值为 false。
    lifecycle-mediator-enabled: true

    # --- always-on 记忆尺寸控制 ---
    # 注入预算:常驻 user/feedback 结构化块(注入时 LRU 截断,0 = 不限)
    system-block-max-chars: 4000
    system-block-max-entries-per-type: 40
    # 夜间巩固:在存储层合并去重 user/feedback 条目(独立于 dreaming)
    structured-consolidation-enabled: true
    structured-consolidation-min-entries: 8
    structured-consolidation-cron: "0 30 3 * * ?"
    structured-consolidation-max-owners-per-run: 50
    # 文件上限:PROFILE.md / MEMORY.md 重写时的硬截断(节边界,0 = 不限)
    profile-max-chars: 4000
    memory-md-max-chars: 8000

配置前缀:mate.memory

上下文窗口

yaml
mate:
  agent:
    conversation:
      window:
        default-max-input-tokens: 128000
        compact-trigger-ratio: 0.75
        preserve-recent-pairs: 2
        summary-max-tokens: 300

API 接口

方法路径用途
POST/api/v1/memory/{agentId}/emergence手动触发整合
POST/api/v1/memory/{agentId}/summarize/{conversationId}对某次对话手动触发提取
POST/api/v1/memory/{agentId}/structured-consolidation手动触发 user/feedback 结构化条目巩固
GET/api/v1/memory/{agentId}/dreaming/status查询上次运行、下次计划、最新 DREAMS.md 条目

Mem0 集成(可选)

非默认栈

Mem0 集成是可选的社区贡献项,不在 MateClaw 的默认安装里。它需要你自己部署一份 Mem0 服务(FastAPI + pgvector + 可选 Neo4j)。MateClaw 的"本地优先、零外部依赖"定位不变——这个插件只是给愿意多跑一套服务的人一个叠加的语义召回通道

Mem0 是一个独立的记忆服务,做的是 LLM 记忆的提取、去重、向量化召回。MateClaw 的 mateclaw-plugin-mem0 模块把它作为一个插件式 memory provider 接进来——内部 4 个 provider(Builtin / Structured / Session / Fact)一个都不动,Mem0 作为第 5 个外部 provider 叠加上去,互不替代

它做什么

钩子行为
systemPromptBlock返回空——常驻 system prompt 不动,避免每轮 token 膨胀
prefetch(agentId, query, ownerKey)searchEnabled=trueownerKey 非空时,调 POST {baseUrl}/memories/search/,返回一个 [Mem0 Recall] 块拼进本轮上下文
syncTurn(agentId, conversationId, userMessage, assistantReply, ownerKey)syncEnabled=trueownerKey 非空时,异步把这一轮的 user/assistant 消息以 user_id = ownerKey 推到 POST {baseUrl}/memories/ —— 与召回查询用同一个标识。失败只记日志、不阻塞响应
getToolBeans空列表——v1 不暴露 Agent 可调用的工具

故障隔离:recall 或 sync 任何一边抛异常,插件自己吞掉、写日志,平台继续走其他 provider。Mem0 挂了不会影响 MateClaw 的本地记忆。

per-owner 隔离的映射

Mem0 用 user_id + agent_id 做隔离。MateClaw 的映射:

MateClaw 字段Mem0 字段说明
ownerKey(如 user:42 / feishu:sender_abcuser_id透传,原样作为 user_id
agentIdagent_id数字员工 ID

prefetchsyncTurn 都能从平台拿到 ownerKey,写入和召回用同一个 user_id。拿不到 ownerKey 的变体会直接跳过(召回返回空 / 放弃写入)——Mem0 要求 user_id,没它无法隔离。

安装步骤

  1. 部署 Mem0 服务:参考 Mem0 官方文档,自托管一份(FastAPI + pgvector + 可选 Neo4j)。记下它的 base URL,比如 http://localhost:8080
  2. 构建插件 JAR:在 MateClaw 仓库根目录跑 mvn -pl mateclaw-plugin-mem0 -am package,得到 mateclaw-plugin-mem0/target/mateclaw-plugin-mem0-*.jar
  3. 放 JAR:把 JAR 丢进 MateClaw 的 plugins/ 目录。
  4. 配置:在插件管理 UI 里填 baseUrl(必填),按需填 apiKey、调其他参数。重启或重载插件。

配置项

字段类型必填默认说明
baseUrlstringMem0 REST API 地址,如 http://localhost:8080
apiKeystringBearer token,作为 Authorization 头发给 Mem0
searchEnabledbooleantrue是否在 prefetch 时调 /memories/search/ 做语义召回
syncEnabledbooleantrue是否在 syncTurn 时把每轮对话推到 /memories/
maxResultsinteger5每次召回返回的记忆条数上限
timeoutMsinteger3000HTTP 超时(毫秒),recall 和 sync 共用

配置只在插件加载时读一次——改了要重载插件才会生效。

已知限制(v1)

  • 没有解析出 owner 的轮次不会同步syncTurn 要求 ownerKey;平台解析不出 owner 的轮次(如系统触发的运行)会直接跳过,而不是用一个召回永远查不到的降级标识写入。
  • 没有 token 预算控制:prefetch 返回的 [Mem0 Recall] 块直接拼进上下文,不受 system-block-max-chars 那套注入预算约束(那套只管 user/feedback 结构化条目)。maxResults 是唯一的尺寸闸门。
  • 没有 Agent 工具:v1 不暴露 mem0_search / mem0_add 之类的工具给 Agent 主动调用。Agent 只能被动接收 prefetch 的结果。

下一步