Skip to content

feat(memory): add Mem0 as an optional external plugin memory provider - #546

Merged
mateaix merged 1 commit into
mateaix:devfrom
Lcos-000:enhance/adapt-for-mem0
Jul 26, 2026
Merged

feat(memory): add Mem0 as an optional external plugin memory provider#546
mateaix merged 1 commit into
mateaix:devfrom
Lcos-000:enhance/adapt-for-mem0

Conversation

@Lcos-000

@Lcos-000 Lcos-000 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

摘要

Mem0 接入 MateClaw 作为可选的插件式 memory provider。Mem0 会叠加在现有 4 个内置 provider(Builtin / Structured / Session / Fact)之上运行,不改动、不替代任何内置实现,也不在默认栈中——mateclaw-server 不依赖新模块,需要用户自行构建 JAR 放到 plugins/ 目录。

设计定位

MateClaw 的定位是本地优先、零外部依赖。Mem0 需要自托管服务栈(FastAPI + pgvector + 可选 Neo4j),与这一定位存在权衡。因此本 PR 把 Mem0 定位为社区贡献的可选扩展:想获得独立语义召回能力的用户可以启用,默认安装不受影响。

主要改动

1. 补齐插件 SPI(mateclaw-plugin-api

PluginMemoryProvider 新增三参 default 方法:

String prefetch(Long agentId, String userQuery, String ownerKey)

默认实现退化到两参版。这使得插件可以接收当前请求解析出的 ownerKey,实现 per-owner 隔离召回。没有该扩展,Mem0 这类需要按用户隔离的外部 provider 无法正确工作。

2. Bridge 透传 ownerKey(mateclaw-server

PluginMemoryBridge override 三参 prefetch,将 ownerKey 原样转发给插件。若缺失该 override,接口 default 会静默丢弃 ownerKey,导致所有插件 provider 的 per-owner 召回失效。

3. 新增 mateclaw-plugin-mem0 模块

职责
Mem0Config 配置载体与校验
Mem0Client JDK HttpClient 封装 Mem0 REST API(POST /memories/POST /memories/search/
Mem0Provider 实现 PluginMemoryProvider:prefetch 语义召回、异步 syncTurn、不注入 system prompt、v1 不暴露工具
Mem0Plugin 插件入口:读取配置、组装 provider、注册到 PluginContext
Mem0Exception 统一运行时异常

依赖策略:零额外依赖。Jackson、SLF4J 均为 provided 作用域,HttpClient 使用 JDK 自带实现。

故障隔离:recall / sync 过程中的任何异常都会被捕获并记日志,Mem0 服务不可用不会破坏平台本地 memory 链路

4. per-owner 隔离映射

MateClaw Mem0 说明
ownerKey(如 user:42feishu:sender_abc user_id 原样透传
agentId agent_id 数字员工 ID

5. 文档

docs/zh/memory.mddocs/en/memory.md 各新增一节:

  • 定位说明(可选、非默认、需自托管 Mem0)
  • 行为表(各 SPI 钩子的实现)
  • 安装步骤
  • 配置项参考
  • 已知限制

配置项

字段 类型 必填 默认 说明
baseUrl string Mem0 REST API 地址
apiKey string Bearer token(manifest 中标记为 secret)
searchEnabled boolean true 是否启用语义召回
syncEnabled boolean true 是否异步推送对话
maxResults integer 5 召回条数上限
timeoutMs integer 3000 HTTP 超时

测试

共 42 个测试,全部通过:

模块 测试类 数量 说明
mateclaw-plugin-mem0 Mem0ConfigTest 5 配置解析与默认值
mateclaw-plugin-mem0 Mem0ClientTest 7 JDK HttpServer mock Mem0,验证 payload、auth header、错误处理
mateclaw-plugin-mem0 Mem0ProviderTest 15 故障隔离、配置门控、异步 syncTurn、ownerKey 透传
mateclaw-plugin-mem0 Mem0PluginTest 4 插件生命周期
mateclaw-server PluginMemoryBridgeTest 6 ownerKey 透传、退化行为、tool-bean 规范化
mateclaw-server MemoryManagerPluginPrefetchTest 5 prefetchAll → bridge → plugin 三参 prefetch 端到端路径

验证命令:

# JDK 17+
mvn -pl mateclaw-plugin-mem0 -am test

# JDK 21
mvn -pl mateclaw-server test \
  -Dtest=PluginMemoryBridgeTest,MemoryManagerPluginPrefetchTest \
  -Dsurefire.failIfNoSpecifiedTests=false

本地已在 JDK 17.0.19 与 JDK 21.0.11 下验证通过。

已知限制

  1. syncTurnownerKey:当前 SPI 签名只有 (agentId, conversationId, userMessage, assistantReply),推送 Mem0 时只能用 agentId 作为 user_id 降级。需要严格 per-owner 同步时,可设 syncEnabled=false
  2. prefetch 结果无 token 预算控制[Mem0 Recall] 块直接拼入上下文,不受 system-block-max-chars 约束;maxResults 是当前唯一尺寸阀门。
  3. 不暴露 Agent 工具:v1 未提供 mem0_search / mem0_add 等工具。

以上限制均已写入中英文用户文档。

不包含的内容

  • 纯叠加策略,不做来源标注,不对 prefetchAll 加预算控制;
  • 不增加 onMemoryWrite 钩子(该接口方法当前无调用方);
  • CI 中不跑真实 Mem0 E2E(官方 Docker 镜像当前仅 arm64 manifest),协议面由 L1 mock 测试覆盖。

改动文件

mateclaw-plugin-api/src/main/java/vip/mate/plugin/api/memory/PluginMemoryProvider.java
mateclaw-server/src/main/java/vip/mate/plugin/bridge/PluginMemoryBridge.java
mateclaw-server/src/main/resources/docs/en/memory.md
mateclaw-server/src/main/resources/docs/zh/memory.md
pom.xml
mateclaw-plugin-mem0/                              # 新增模块
mateclaw-server/src/test/java/vip/mate/memory/MemoryManagerPluginPrefetchTest.java
mateclaw-server/src/test/java/vip/mate/plugin/bridge/PluginMemoryBridgeTest.java

共 18 个文件,1755 行新增。

Review 关注点

  • PluginMemoryProvider 的新三参 default 方法是否会对现有插件产生不兼容影响;
  • PluginMemoryBridge 三参 override 是否遗漏了异常处理或 null 处理;
  • mateclaw-server 是否意外引入了对 mateclaw-plugin-mem0 的依赖;
  • Mem0Provider 的异步 syncTurn 是否可能在应用关闭时泄漏线程;
  • 文档中的"已知限制"是否足够清晰。

Mem0 (https://github.com/mem0ai/mem0) is a standalone memory service for
LLM extraction, dedup, and vector recall. This PR plugs it in as a
community-supported, opt-in plugin that stacks on top of the 4 builtin
providers (Builtin / Structured / Session / Fact) — none of which are
touched. The plugin is NOT in the default stack: mateclaw-server does
not depend on the new module, users build the JAR and drop it into
plugins/ themselves.

Why: MateClaw is local-first with zero external dependencies. Mem0
requires a self-hosted service stack (FastAPI + pgvector + optional
Neo4j). That's a reasonable trade-off for people who want a
purpose-built semantic recall channel, but it stays opt-in.

What's included:

SPI extension (mateclaw-plugin-api):
- PluginMemoryProvider.prefetch(Long, String, String ownerKey) default
  method — degrades to the two-arg variant. Without this, plugins
  can't receive the resolved ownerKey and per-owner recall is
  impossible.

Bridge (mateclaw-server):
- PluginMemoryBridge overrides the three-arg prefetch to forward
  ownerKey verbatim. Without the override, the interface default
  would silently drop ownerKey for every plugin provider.

New module mateclaw-plugin-mem0:
- Mem0Client: JDK HttpClient wrapper for POST /memories/ and
  POST /memories/search/. Zero external deps (Jackson + SLF4J only,
  both provided-scope).
- Mem0Provider: implements PluginMemoryProvider. prefetch calls
  search for semantic recall; syncTurn pushes each turn asynchronously
  via a single-thread daemon executor; systemPromptBlock returns empty
  to avoid per-turn token bloat; getToolBeans returns empty (v1).
- Mem0Plugin: entrypoint. Reads 6 config fields, wires the provider,
  registers via context.registerMemoryProvider.
- Fault isolation: any HTTP error / exception is swallowed and
  logged — Mem0 being down never breaks the platform's local memory.

Per-owner isolation mapping:
- MateClaw ownerKey ("user:42", "feishu:sender_abc", ...) → Mem0
  user_id (verbatim).
- MateClaw agentId → Mem0 agent_id.

Known limitations (documented in docs/{zh,en}/memory.md):
- syncTurn's SPI signature has no ownerKey, so push falls back to
  agentId as user_id. Coarser than prefetch. Set syncEnabled=false
  to rely on prefetch-only recall.
- The [Mem0 Recall] block returned by prefetch is NOT subject to
  the system-block-max-chars injection budget (that budget only
  governs user/feedback structured entries). maxResults is the
  only size knob.
- v1 does not expose mem0_search / mem0_add style agent tools.

Documentation:
- docs/zh/memory.md and docs/en/memory.md each get a new section
  "Mem0 集成(可选)" / "Mem0 Integration (Optional)" covering
  positioning, behavior table, per-owner mapping, install steps,
  config reference, and known limitations.

Tests (42 total, all green):
- mateclaw-plugin-mem0 module (31 tests, runs on JDK 17+):
  Mem0ConfigTest (5), Mem0ClientTest (7, uses JDK HttpServer as a
  mock Mem0), Mem0ProviderTest (15), Mem0PluginTest (4).
- mateclaw-server module (11 tests, requires JDK 21):
  PluginMemoryBridgeTest (6) — verifies ownerKey forwarding and
  graceful degradation when the plugin doesn't override three-arg.
  MemoryManagerPluginPrefetchTest (5) — verifies the full
  prefetchAll → bridge → plugin three-arg path, including fault
  isolation and unavailable-plugin filtering.

Verified with:
- mvn -pl mateclaw-plugin-mem0 -am test (JDK 17): 31/31 green.
- mvn -pl mateclaw-server test -Dtest=PluginMemoryBridgeTest,MemoryManagerPluginPrefetchTest (JDK 21): 11/11 green.
@Lcos-000

Copy link
Copy Markdown
Contributor Author

@mateaix hello,已经过去好几天了,请问是否可以有继续推进的可能呢

@mateaix

mateaix commented Jul 25, 2026

Copy link
Copy Markdown
Owner

@Lcos-000 感谢贡献,明天评估下合并。最近在主力更新agent team的一些基础功能。

@Lcos-000

Copy link
Copy Markdown
Contributor Author

好的非常感谢

@mateaix

mateaix commented Jul 26, 2026

Copy link
Copy Markdown
Owner

太感谢这个 PR 了 🙏 这是 memory 插件生态方向上第一个完整的社区 provider 实现。

这个洞是真实存在的:平台内部 MemoryManager.prefetchAll(agentId, query, ownerKey) 一路把 ownerKey 传到 MemoryProvider.prefetch 三参版(MemoryManager.java:165-169),但 PluginMemoryBridge 只 override 了两参 prefetch —— ownerKey 走到 bridge 边界就被接口 default 静默丢弃,任何外部插件 provider 都做不了 per-owner 隔离召回。你对缺口的诊断和修法(SPI 三参 default 方法 + bridge 透传)完全正确,而且对存量插件零破坏。

几个实现上写得特别好的地方:

  1. MemoryManagerPluginPrefetchTest 的定位精准 —— "This is the test that catches the silent regression where the bridge forgets to override the three-arg variant" 这条测试守的正是最容易在未来重构中悄悄退化的路径,比单测 bridge 本身更有价值;
  2. 依赖策略克制 —— Jackson/SLF4J 全部 provided、HTTP 用 JDK 自带 HttpClientmateclaw-server 不依赖新模块,完全符合"可选插件不进默认栈"的定位;
  3. 文档里的"已知限制"写得诚实 —— syncTurn 拿不到 ownerKey 的降级行为、[Mem0 Recall] 块不受注入预算约束,都如实写进了中英文档,没有藏。

这类"补 SPI 缺口 + 完整参考实现 + 双语文档"的工作平时很少有人愿意做全套 —— 单独提一个 SPI 改动很难说服人,单独提一个插件又跑不通,你把闭环做完了。

已合并,会随下个版本发布带出去。有几个小项我们会在 maintainer 侧 follow-up 处理,不需要你再动:syncTurnagentId 降级作 user_id 会导致写入的记忆与 prefetchownerKey 的查询对不上(计划照同样的 default-method 模式给 syncTurn 补 ownerKey 参数),另有两处注释语言/import 风格与仓库规范的对齐。

如果还有兴趣继续,这几个方向都是现成的下手点:

  • Mem0 agent 工具(v2):给 agent 暴露 mem0_search / mem0_add 主动调用工具 —— getToolBeans() 通道已经在 bridge 里打通,rg -n "getToolBeans" mateclaw-server/src/main/java 能看到平台侧的收集路径;
  • 其他外部 memory provider:同样的 SPI 现在对 Honcho / 自建向量库都是开放的,rg -n "PluginMemoryProvider" mateclaw-plugin-api 是全部契约面;
  • channel 插件:PluginChannelAdapter SPI 同样缺一个社区参考实现,mateclaw-plugin-sample 是最小骨架。

期待再次看到你的 PR ✨ 也欢迎来 issue 区聊聊 Mem0 v2 的工具设计。

@mateaix
mateaix merged commit 396cdb1 into mateaix:dev Jul 26, 2026
@mateaix

mateaix commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Follow-up 已落地 ✅ 之前提到的 syncTurn 隔离粒度问题,现在按你在 prefetch 上用的同一套 default-method 模式补齐了:2ed0d7d

  • 两个 SPI(PluginMemoryProvider / 内部 MemoryProvider)新增五参 syncTurn(agentId, conversationId, userMessage, assistantReply, ownerKey),default 退化到四参,对存量插件零破坏;
  • ownerKeyMemoryLifecycleMediatorMemoryManager.syncAll → retry/metrics decorator → PluginMemoryBridge 全链路透传(decorator 层也要覆盖五参,否则会像当初 bridge 一样在 default 处静默丢 key——这是你这个 PR 的测试注释点出来的同款陷阱);
  • Mem0Provider 现在以 user_id = ownerKey 写入——和召回查询同一个标识,你文档里如实标注的"写入用 agentId 降级、召不回"的限制就此消除;拿不到 ownerKey 的轮次直接跳过,不再产生不可达数据。

你的 MemoryManagerPluginPrefetchTest 风格被原样搬到了 sync 侧(端到端透传 + null 契约 + default 退化各一条),现在 memory/plugin 全包 134 个测试全绿。感谢你把限制写得足够清楚,让这个 follow-up 的形状在合并当天就是明确的 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants