--- title: AI 记忆系统 — 四层记忆生命周期(提取-整合-Dreaming-召回) description: MateClaw 的四层记忆生命周期:即时上下文、对话后提取、工作空间持久化(PROFILE.md/MEMORY.md)、定时 Dreaming 整合。让 AI 越用越懂你,不再每天从零开始。 head: - - meta - name: keywords content: 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 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:` | | 系统 / 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。多租户实例保持开启,单人部署可以关掉。 底层:迁移 `V137` 给 `mate_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 来说,**默认的就够了**,这一节可以跳过。如果你在做某种专用的东西——需要记住上千条事实并用向量搜索、需要图结构记忆——这里就是插入点。开发细节看 [架构说明](./architecture)。 --- ## 每个 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=true`,`sort_order=0`。 - **SOUL.md**——Agent 从根上是谁。自我意识、演化指引、隐私与边界原则。想在深层修改 Agent 的性格时编辑它。种子:`enabled=true`,`sort_order=1`。 - **PROFILE.md**——Agent 学到的关于你的东西。名字、职业、技术栈、沟通偏好。对话里出现值得保留的东西时记忆提取器会更新它。全覆盖写入。种子:`enabled=true`,`sort_order=2`。 - **MEMORY.md**——Agent 认为重要到值得留下的东西。活跃项目、未决定的事、打开的线索、你让它记住的东西。提取器和整合器都会更新它。种子:`enabled=true`,`sort_order=3`。 ::: tip 1.3.0 新增:工作流可以写记忆 v1.3.0 起,[工作流](./workflow) 的 `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-pairs` 和 `protect-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_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` 为 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 记忆的尺寸控制 ::: tip 新增 每一回合都注入 system prompt 的那些记忆(`user` / `feedback` 结构化条目、`PROFILE.md`、`MEMORY.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) 搞混:Dreaming 把日常笔记整合进 `MEMORY.md`(写"重要的东西");巩固只负责把 `user` / `feedback` 结构化条目去重瘦身。两件事,两个调度。 ### 文件上限(重写时的确定性兜底) `PROFILE.md` 和 `MEMORY.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`) | ### 关键词搜索自己的记忆 ::: tip 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` --- ## 记忆快照导出 / 导入 ::: 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** 两个按钮 - 导入时先弹出**差异对比**(哪些新建、哪些覆盖、哪些跳过),确认后才真正写入 --- ## 配置参考 ### 记忆提取 & 整合 ```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 集成(可选) ::: warning 非默认栈 Mem0 集成是**可选的社区贡献项**,不在 MateClaw 的默认安装里。它需要你**自己部署一份 Mem0 服务**(FastAPI + pgvector + 可选 Neo4j)。MateClaw 的"本地优先、零外部依赖"定位不变——这个插件只是给愿意多跑一套服务的人一个**叠加的语义召回通道**。 ::: [Mem0](https://github.com/mem0ai/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=true` 且 `ownerKey` 非空时,调 `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 的结果。 --- ## 下一步 - [Agent 引擎](./agents)——Agent 在一个回合里怎么用记忆 - [LLM Wiki](./wiki)——**刻意的**知识层,和被动的记忆对照 - [工具系统](./tools)——记忆读写工具是众多工具之一 - [配置说明](./config)——完整配置参考 - [架构说明](./architecture)——后端代码组织、SPI 扩展点