mateclaw/mateclaw-server/src/main/resources/docs/zh/memory.md
Lcos 396cdb175b
feat(memory): add Mem0 as an optional external plugin memory provider
Extend the plugin memory SPI with a three-arg prefetch(agentId, userQuery, ownerKey) default method and forward ownerKey through PluginMemoryBridge, enabling per-owner isolated recall for external providers. Ship mateclaw-plugin-mem0: an optional, zero-extra-dependency plugin that bridges a self-hosted Mem0 service (semantic recall via /memories/search/, async turn sync via /memories/) with full fault isolation — not part of the default stack. Includes 42 tests and bilingual user docs.
2026-07-26 10:52:01 +08:00

32 KiB
Raw Blame History

title description head
AI 记忆系统 — 四层记忆生命周期(提取-整合-Dreaming-召回) MateClaw 的四层记忆生命周期即时上下文、对话后提取、工作空间持久化PROFILE.md/MEMORY.md、定时 Dreaming 整合。让 AI 越用越懂你,不再每天从零开始。
meta
name content
keywords AI记忆,记忆系统,Dreaming,PROFILE.md,MEMORY.md,记忆生命周期,长期记忆,记忆提取,记忆整合

AI 记忆系统

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

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

::: tip 它在你睡着的时候做了一个关于你的梦 不是营销词。是 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 promptsystem 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>
系统 / cron system

三档可见性

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

::: warning 默认值要看清楚 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

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

每日笔记

对话亮点按日期归档,追加模式——同一天里的多次对话全部累加到同一个文件。这些不会注入到 system promptenabled=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 条永远不动。

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

配置

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_update boolean 记忆是否需要更新
reason string 原因(用于审计)
daily_entry string 追加到今天日常笔记的内容
memory_update string MEMORY.md 的全新全量内容
profile_update string PROFILE.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 为 trueMEMORY.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

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 2SOUL 自动演化
    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 记忆的尺寸控制

::: tip 新增 每一回合都注入 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(默认 4000PROFILE.md 硬上限;0 = 不限
  • mate.memory.memory-md-max-chars(默认 8000MEMORY.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

关键词搜索自己的记忆

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

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

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

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

示例

列表:

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

读取:

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

编辑:

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

安全约束

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

记忆快照导出 / 导入

::: tip 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/export Viewer 导出 ZIP——只读权限也能做备份
POST .../workspace/memory/import/preview Member 干跑:解析 ZIP逐文件给出 create / update / skip 分类,不写任何东西
POST .../workspace/memory/import Member 应用导入,原子写入

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

安全护栏

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

UI

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

配置参考

记忆提取 & 整合

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

上下文窗口

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 集成(可选)

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

Mem0 是一个独立的记忆服务,做的是 LLM 记忆的提取、去重、向量化召回。MateClaw 的 mateclaw-plugin-mem0 模块把它作为一个插件式 memory provider 接进来——内部 4 个 providerBuiltin / 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) syncEnabled=true 时,异步把这一轮的 user/assistant 消息推到 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_abc user_id 透传,原样作为 user_id
agentId agent_id 数字员工 ID

只有 prefetch 的三参版能拿到 ownerKey。两参版(无 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、调其他参数。重启或重载插件。

配置项

字段 类型 必填 默认 说明
baseUrl string Mem0 REST API 地址,如 http://localhost:8080
apiKey string Bearer token作为 Authorization 头发给 Mem0
searchEnabled boolean true 是否在 prefetch 时调 /memories/search/ 做语义召回
syncEnabled boolean true 是否在 syncTurn 时把每轮对话推到 /memories/
maxResults integer 5 每次召回返回的记忆条数上限
timeoutMs integer 3000 HTTP 超时毫秒recall 和 sync 共用

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

已知限制v1

  • syncTurn 拿不到 ownerKey:插件 SPI 的 syncTurn 签名只有 (agentId, conversationId, userMessage, assistantReply),所以推送 Mem0 时只能用 agentId 作为 user_id 降级。这比 prefetch有 ownerKey的隔离粒度粗。如果你需要严格的 per-owner 同步,把 syncEnabled=false,只依赖 prefetch 做召回,由你自己的 Mem0 客户端负责写入。
  • 没有 token 预算控制prefetch 返回的 [Mem0 Recall] 块直接拼进上下文,不受 system-block-max-chars 那套注入预算约束(那套只管 user/feedback 结构化条目)。maxResults 是唯一的尺寸闸门。
  • 没有 Agent 工具v1 不暴露 mem0_search / mem0_add 之类的工具给 Agent 主动调用。Agent 只能被动接收 prefetch 的结果。

下一步