一个为 OpenClaw 智能体同时提供会话内上下文压缩(分层摘要 DAG)与跨会话长期召回(向量存储 + 分层检索)的插件 —— 打包为单一即插即用的运行时扩展。
🚀 快速开始 • 🌟 概述 • 🏗️ 架构 • 🧠 检索管线 • 📈 评测结果 • 🛠️ 工具 • ⚙️ 配置 • 🔬 基准测试
- [2026-04-12] 🈯 中文支持与可选向量去重正式发布。 新增 CJK 偏好提取(12 条新正则模式,覆盖喜好、习惯、目标、传记事实和怀旧表达),基于
cl100k_base经验校准的 CJK Token 估算(0.87 字符/Token —— 修复了中文文本约 4.6 倍的低估问题,此前会导致压缩触发严重滞后),以及写入时的可选余弦相似度向量去重(vectorDedupEnabled,默认关闭)。LongMemEval-50 上的英文基准测试数字保持完全一致 —— 这是经验证实的结果,而非仅从代码构造上推导。 - [2026-04-11] 🚀 v1.0.0 正式发布! 时序推理关键修复随首个正式版本一同发布:在索引的会话文档中添加
[Date: …]头部,并将asOf传递给 LLM 评判器,使 LoCoMo 时序得分从 42.4% → 80.4%(+38.0 pts),LongMemEval-500 从 72.7% → 80.9%(+8.2 pts)。
MemCC 是一个 OpenClaw 插件 —— 它通过 OpenClaw 的插件发现机制加载,并同时注册为上下文引擎和记忆能力。无需独立运行时,无需单独服务器。
# 📥 克隆并安装
git clone <repo> memcc
cd memcc
npm install
# ▶️ 将 OpenClaw 指向该插件
export OPENCLAW_PLUGINS=/path/to/memcc
# 或在 openclaw 配置中添加该包路径
# 🔑 配置嵌入 + LLM 重排序所用的 OpenAI Key
export OPENAI_API_KEY=sk-...
# 🧪 运行完整测试套件
npm test
# 📊 运行基准测试套件(详见 §基准测试)
npm run bench加载完成后,OpenClaw 将解析上下文引擎 memcc(也别名为 default),并向智能体暴露四个工具:memory_search、memory_get、lcm_grep、lcm_expand_query。无需额外设置 —— 插件在首次启动时会自动迁移自身的 SQLite 数据库结构,并延迟初始化 LanceDB 存储。
跨多轮次、多会话运行的 LLM 智能体面临两个本质上不同的问题:
- 当前对话过长,无法放入上下文窗口。 你需要在不丢失精确细节可恢复能力的前提下,压缩旧的轮次。
- 在上一个会话中学到的内容,智能体重启后即告消失。 你需要一个能够理解语义召回而非仅依赖文本匹配的持久化存储。
大多数记忆系统只解决其中一个问题。MemCC 在一个插件中同时解决两者,关键在于两者之间存在通信:当上下文引擎压缩一段对话时,生成的摘要会直接传递给长期记忆运行时进行嵌入。当上下文引擎在下一次组装提示词时,它会向记忆运行时请求相关的召回内容,并将其拼接回与压缩摘要共存的同一上下文中。
|
会话内分层压缩 基于 SQLite 的对话存储,实现 目标:在不损失可恢复性的前提下保持在 Token 预算之内。 |
跨会话原文向量召回 LanceDB 向量存储(OpenAI 目标:在重启后依然存活的语义召回。 |
💡 两个子系统通过
MemoryRuntimeBridge接口解耦 —— 上下文引擎从不直接导入记忆代码。这意味着记忆运行时可以异步初始化(嵌入客户端启动),而不会阻塞上下文引擎接收轮次。
| 组件 | 文件 | 职责 |
|---|---|---|
| 插件壳 | src/plugin/index.ts |
向 OpenClaw 注册上下文引擎、记忆能力和工具;在异步初始化完成后接线 MemoryRuntimeBridge。 |
| ContextEngine | src/context/engine.ts |
实现 OpenClaw 的 ContextEngine 接口:ingest、ingestBatch、assemble、afterTurn、compact、bootstrap。拥有压缩控制权。 |
| ConversationStore | src/context/store/conversation-store.ts |
SQLite + 可选 FTS5,用于存储原始消息。 |
| SummaryStore | src/context/store/summary-store.ts |
存储 leaf / condensed 摘要 DAG;追踪每个对话的上下文条目列表。 |
| CompactionEngine | src/context/compaction.ts |
决定何时压缩(0.75 × budget),通过配置的模型生成摘要,保留 freshTailCount=4 条消息。 |
| ContextAssembler | src/context/assembler.ts |
遍历 DAG + 新鲜尾部 + 记忆注入区段,生成受 Token 预算限制的消息列表。 |
| LargeFileHandler | src/context/large-files.ts |
将超大 Blob 外化并在污染摘要树之前替换为指针。 |
| Bootstrap | src/context/bootstrap.ts |
在首次命中时重放会话 JSONL 文件,使重启的会话不丢失 DAG。 |
| MemoryRuntime | src/memory/runtime.ts |
管理记忆侧:ingestSummary、retrieve、injectIntoContext、importLegacy。 |
| VectorStore | src/memory/store/vector-store.ts |
LanceDB 后端 —— 通过 OpenAI 客户端嵌入,并以 wing/room 元数据进行 upsert。支持写入时可选的余弦相似度去重(详见 §配置)。 |
| MemoryStore | src/memory/store/memory-store.ts |
SQLite 元数据存储:记忆记录、知识图谱三元组、FTS 索引。 |
| 检索层 | src/memory/layers/*.ts |
l0-identity.ts、l1-essential.ts、l2-on-demand.ts、l3-deep-search.ts —— 见 §检索管线。 |
| 搜索 | src/memory/search/*.ts |
hybrid-search.ts(BM25 风格 + 时序提升)、llm-rerank.ts、knowledge-graph.ts。 |
| 分类 | src/memory/classify.ts |
基于实体/主题检测,将文本路由到 (wing, room) 命名空间。 |
| 偏好提取器 | src/memory/preference-extractor.ts |
从压缩摘要中挖掘用户明确偏好(27 条英文 + 12 条中文正则模式;CJK 感知的长度边界),并将其存储为合成记忆文档以支持释义召回。 |
| Token 估算器 | src/token-estimator.ts |
CJK 感知的 Token 计数估算,供压缩、组装、引导和层级预算使用。基于 cl100k_base 的经验比率:CJK 为 0.87 字符/Token,拉丁字符为 4.0。非 CJK 快速路径与原有的 Math.ceil(text.length / 4) 公式完全一致。 |
- 写入。 OpenClaw 将每个轮次传递给
ContextEngine.ingest()。大型文件被外化;原始消息被存储,并将一个raw上下文条目追加到对话的条目列表中。 - 压缩检查。 轮次结束后,
afterTurn()询问CompactionEngine对话是否超过了预算的 75%。如果是,生成一个leaf摘要(可选地滚入condensed摘要)。 - 摘要 → 记忆。 每个摘要通过
MemoryRuntimeBridge.ingestSummary()推送。MemoryRuntime对其分类,用 OpenAI 进行嵌入,upsert 到 LanceDB,并运行偏好提取器。摘要文档和任何合成偏好文档都进入向量存储。 - 组装。 在下一个轮次,
assemble()获取最近 3 条消息作为检索查询,调用memoryRuntime.retrieve(query),然后通过injectIntoContext(retrieval)生成受预算限制的<memcc-identity>、<memcc-essential>、<memcc-context>、<memcc-recall>区段。这些区段与 DAG 组装(摘要 + 新鲜尾部)交错排列。 - 提示。 如果存在压缩历史,则发出一个
systemPromptAddition,告知智能体可以调用lcm_grep/lcm_expand_query从摘要中恢复精确细节。
MemCC 不进行单一的"Top-K 向量搜索"。每次检索会向四个独立层并行展开,每层回答不同的问题,并有各自的 Token 预算。
查询被广播到 L0–L3 并行执行。L3 内部依次执行:向量搜索 → 混合重排序 → KG 合并 → (可选)LLM 重排序。每层在注入时写入自己的包装区段,当 contextOptimization=true 时 Token 预算收紧。
| 层级 | 数据源 | 用途 | 预算 |
|---|---|---|---|
| L0 身份 | agentDir/* 静态文件 |
该智能体是谁 —— 角色、人格、基础事实。始终原文注入。 | 不限 |
| L1 必要 | MemoryStore 固定记忆 |
顶级身份片段和高权重固定事实。按行裁剪到预算。 | 800 tok(优化模式下 400) |
| L2 按需 | MemoryStore wing 过滤 |
用查询检测到的 wing 标记的记忆(例如代码问题对应"coding" wing)。优化模式下完全跳过。 | ≤ 500 tok 或放弃 |
| L3 深度搜索 | VectorStore + MemoryStore 三元组 |
语义召回:向量 Top-20 → BM25 + 时序重排序 → 可选 KG 合并 → 可选 LLM 重排序。 | top 10(优化模式 top 3) |
- 向量搜索 —— 从 LanceDB 获取 20 个候选(
text-embedding-3-small)。 - 混合重排序(
hybrid-search.ts)—— BM25 风格词法评分 + 时序提升(较新的记忆在平局时优先)。 - KG 合并(
knowledge-graph.ts)—— 如果查询实体匹配已存储的(subject, predicate, object)三元组,则注入一个合成 KG 结果(除非已覆盖)。 - LLM 重排序(
llm-rerank.ts,由llmRerank=true控制,默认:gpt-4o-mini)—— 最后一公里精度的最终交叉编码器过滤。每次搜索约消耗 $0.001。
💡 层级拆分并非学术考量 —— 这正是使预算可收缩的原因。
contextOptimization=true完全放弃 L2,将 L1 减半,并将 L3 限制为 3 个命中。这就是在同一查询上"每轮注入约 2500 tok"与"每轮注入约 800 tok"的差距所在。
MemCC 通过了 LoCoMo 和 LongMemEval 上的所有目标,并在时序推理方面取得了领先成绩 —— 这历来是记忆系统最难攻克的子集。
| 🏆 86.9% LoCoMo 综合(1,986 题) |
🏆 80.4% LoCoMo 时序(321 题) |
🏆 88.1% LoCoMo 情节(1,665 题) |
🏆 80.9% LongMemEval-500(498 题) |
| 基准测试 | 得分 | 目标 | 状态 |
|---|---|---|---|
| LoCoMo 综合 | 86.9% | ≥ 50% | ✅ 通过 |
| LoCoMo 情节 | 88.1% | ≥ 60% | ✅ 通过 |
| LoCoMo 时序 | 80.4% | ≥ 50% | ✅ 通过 |
| LongMemEval-100 | 92.1% | ≥ 88% | ✅ 通过 |
| LongMemEval-500 | 80.9% | — | 领先 |
MemCC 向 OpenClaw 注册四个工具。其中三个操作短期上下文(压缩后的 DAG),一个操作长期记忆。
| 工具 | 操作对象 | 功能 |
|---|---|---|
memory_search |
长期(LanceDB) | 跨所有已存储记忆的语义搜索。返回带分数的 L3 深度搜索层命中结果。最适合"我们上个月对 X 做了什么决定"。 |
memory_get |
长期(LanceDB) | 通过 ID 获取单条记忆 —— 当 memory_search 返回 ID 且智能体需要完整原文时使用。 |
lcm_grep |
短期(上下文 DAG) | 在当前会话的消息及压缩摘要中进行全文搜索。当智能体需要从压缩历史中找到精确短语时使用。 |
lcm_expand_query |
短期(上下文 DAG) | 将压缩摘要展开回生成它的底层原始消息 —— 实际上是"撤销这部分压缩"。 |
当 assemble() 检测到压缩历史时,它会注入一条系统提示词提示,精确告知智能体该使用哪些工具:
"早期的一些对话已被压缩成摘要。如果你需要从压缩历史中获取精确细节,请使用
lcm_grep进行搜索,然后使用lcm_expand_query获取完整上下文。"
所有配置通过 openclaw.plugin.json 中定义的 OpenClaw 插件配置 Schema 流转。每个选项都是可选的 —— 默认值已经过调优。
| 选项 | 默认值 | 效果 |
|---|---|---|
contextOptimization |
false |
true 时将 L1 预算减半(800→400),完全放弃 L2,将 L3 限制为 3 个命中,收紧压缩目标(leaf 1500→1000,condensed 2000→1200)。 |
compactionModel |
null(继承) |
用于生成 leaf/condensed 摘要的模型。格式为 Provider/模型字符串,如 openai/gpt-4o-mini。 |
embeddingModel |
text-embedding-3-small |
OpenAI 嵌入模型 —— 任何与 OpenAI Embeddings API 兼容的模型均可。 |
llmRerank |
true |
在混合重排序后运行 LLM 交叉编码器过滤。设为 false 则仅使用向量 + BM25。 |
rerankModel |
gpt-4o-mini |
用于 llmRerank 的模型。推荐使用低成本默认值。 |
vectorDedupEnabled |
false |
当设为 true 时,VectorStore.add() 会计算与现有最近邻的余弦相似度,并跳过相似度 ≥ vectorDedupThreshold 的写入。仅比较已持久化的状态(非批内比较)。适用于同一事实可能被重复写入的长期运行工作负载;之所以默认关闭,是因为 LongMemEval 风格的基准测试不会触发去重路径。 |
vectorDedupThreshold |
0.95 |
写入时去重的余弦相似度阈值。有效范围为 [0, 1](含端点)。超出范围或 NaN 的值会回退到默认值。阈值调紧(0.98)会抑制更少重复,调松(0.90)则抑制更多。 |
$HOME/.openclaw/memcc/
├── context.db # SQLite — 对话、消息、摘要
├── memory.db # SQLite — 记忆记录、三元组、FTS
└── lance/ # LanceDB — 向量存储
这些参数不在配置 Schema 中 —— 它们是通过基准测试运行调优的结果。
| 参数 | 默认值(优化模式) | 说明 |
|---|---|---|
contextThreshold |
0.75 | 压缩在达到 Token 预算的 75% 时触发。 |
freshTailCount |
4 | 最后 4 条消息始终原文保留。 |
leafChunkTokens |
4000 | 折叠成一个 leaf 的最大 Token 数。 |
leafTargetTokens |
1500(1000) | leaf 摘要的目标大小。 |
condensedTargetTokens |
2000(1200) | condensed 摘要的目标大小。 |
leafMinFanout |
3 | 触发 condensed 汇总前所需的最少 leaf 数。 |
- 🟢 Node.js 22.x(为使用内置的
node:sqlite模块) - 🔑 兼容 OpenAI 的 API(用于嵌入 + 重排序)
- 🪶 OpenClaw ≥ 2026.4.2(对等依赖)
# 在 memCC 目录下执行
npm install
# 验证插件元数据是否被识别
cat openclaw.plugin.json
# 运行测试
npm test
# 运行基准测试(需要 OPENAI_API_KEY)
OPENAI_API_KEY=sk-... npm run bench| 包 | 用途 |
|---|---|
@lancedb/lancedb |
向量存储后端 |
apache-arrow |
LanceDB 的列式数据传输 |
@sinclair/typebox |
插件配置 Schema 校验 |
node:sqlite(内置) |
上下文 + 记忆元数据存储 |
基准测试套件是 test/benchmark/ 下的标准 Vitest 测试文件。它们从兄弟目录 memory-lancedb-pro/bench/ 中读取数据集(LongMemEval 和 LoCoMo)。
# 🎯 LongMemEval-50(冒烟测试,约 1 分钟)
OPENAI_API_KEY=<key> npx vitest run test/benchmark/longmemeval-50.test.ts --testTimeout 600000
# 📏 LongMemEval-100(约 3 分钟)
OPENAI_API_KEY=<key> npx vitest run test/benchmark/longmemeval-100.test.ts --testTimeout 600000
# 🏁 LongMemEval-500 完整版(约 45 分钟)
OPENAI_API_KEY=<key> npx vitest run test/benchmark/longmemeval-500.test.ts --testTimeout 3600000
# 🎢 LoCoMo 完整版(约 40-50 分钟)
OPENAI_API_KEY=<key> npx vitest run test/benchmark/locomo.test.ts --testTimeout 3600000test/benchmark/longmemeval.test.ts—— 代表性子集test/benchmark/longmemeval-100.test.ts—— 100 题 + 合成偏好 + LLM 重排序test/benchmark/longmemeval-500.test.ts—— 完整 500 题test/benchmark/longmemeval-tail.test.ts—— 420-500 切片(超时后继续)test/benchmark/locomo.test.ts—— 10 个对话,1,986 个查询
OS: macOS 26.4 (arm64)
Node: 22.22.0
OpenClaw: 2026.4.9
MemCC: 0.1.0
嵌入器: OpenAI text-embedding-3-small(1536 维)
评判器: gpt-4o-mini
检索质量
- 针对多会话"共有多少 X" / "列出所有 Y"查询的聚合/计数路径(LongMemEval 尾部在添加日期头部后的剩余弱点)
- 地标缓存 —— 廉价的按文档元数据(作者、位置、实体标签,在写入时一次性提取)以解锁类似日期头部对实体中心查询的增益
- CI 中的按子集回归门控,使任何基准子集都不会悄无声息地下降
基础设施
- 替代嵌入后端(本地模型、Qwen3-Embedding、Voyage)
- 可配置的向量存储后端(当前为 LanceDB;sqlite-vec 作为零依赖备用)
- 记忆存储的导出/导入以支持智能体迁移
集成
- 带访问控制的多智能体记忆共享(子智能体的授权已存在;扩展到对等智能体)
- 用于从外部事件实时更新记忆的流式写入 API
欢迎贡献!
MIT —— 详见 LICENSE 文件(或 package.json 的 license 字段)。

{ "plugins": { "memcc": { "contextOptimization": false, // true → 收紧 L1-L3 的预算 "compactionModel": null, // null → 继承 OpenClaw 配置 "embeddingModel": "text-embedding-3-small", "llmRerank": true, // ~$0.001/次搜索 — 默认开启 "rerankModel": "gpt-4o-mini", // null → 默认使用 gpt-4o-mini "vectorDedupEnabled": false, // 写入时可选的近重复抑制 "vectorDedupThreshold": 0.95 // 余弦相似度,范围 [0,1];≥ 阈值时跳过写入 } } }