霍格沃兹测试开发学社

《Python测试开发进阶训练营》(随到随学!)
2023年第2期《Python全栈开发与自动化测试班》(开班在即)
报名联系weixin/qq:2314507862

OpenClaw三级记忆系统实现揭秘:向量数据库+关系型数据库的混合存储方案

如果你用过OpenClaw,一定被它的“记忆力”震撼过——几个月前随口提过的偏好,下次聊天它还记得;上周讨论过的项目细节,这周接着聊它能无缝衔接。

这种能力不是魔法,是一套精心设计的三级记忆系统在背后支撑。今天我们就从源码层面,彻底拆解这套架构:它怎么存、怎么查、怎么在“记得住”和“不烧钱”之间找到平衡。

一、为什么需要三级记忆?
在OpenClaw之前,AI助手的记忆通常只有一层:会话上下文。你今天跟它聊完,明天它就不认识你了。OpenClaw的设计者想得很明白:人的记忆是分层的,AI也应该这样。

打开OpenClaw的工作区,你会看到这样的结构:

~/.openclaw/workspace/
├── MEMORY.md # 长期记忆:偏好、决策、持久事实
├── memory/
│ ├── 2026-03-05.md # 今日日志(短期记忆)
│ ├── 2026-03-04.md # 昨日日志
│ └── ... # 历史日志
├── sessions/ # 会话存档(近端记忆)
├── USER.md # 用户身份
└── SOUL.md # Agent人格设定
这套结构对应着三个层次:

短期记忆(Daily Log):每天一个append-only的日志文件,记录当天发生的事。新会话启动时,系统会自动加载“今天+昨天”的日志,让Agent拥有最近48小时的连续感。

近端记忆(Sessions):完整的会话存档。当对话太长被压缩时,关键信息会被冲刷到这里。

长期记忆(MEMORY.md):经过筛选的持久知识——你的技术栈偏好、项目决策、常用工具链。这些信息会在每次私聊时自动加载。

这套分层设计的核心思想是:模型不需要知道所有事,只需要知道此刻最相关的事。

二、存储层:SQLite + 向量,关系型与非结构化的联姻
但光有Markdown文件不够——要在大堆文本里快速找到相关内容,必须建索引。这就是SQLite登场的时刻。

每个Agent对应一个独立的SQLite数据库,位于~/.openclaw/memory/{agentId}.sqlite。看表结构就知道设计者的用心:

-- 核心表:记录文件元数据
CREATETABLE files (
idINTEGER PRIMARY KEY,
pathTEXTUNIQUE,
mtime INTEGER, -- 修改时间,用于增量索引
sizeINTEGER,
hashTEXT -- 内容哈希,去重用
);

-- 核心表:存储文本块
CREATETABLE chunks (
idINTEGER PRIMARY KEY,
file_id INTEGER,
start_line INTEGER,
end_line INTEGER,
textTEXT,
hashTEXTUNIQUE, -- 文本哈希,跨文件去重
embedding TEXT -- JSON序列化的向量
);

-- 虚拟表:全文搜索(FTS5)
CREATEVIRTUALTABLE chunks_fts USING fts5(
text, -- 索引的文本字段
content=chunks -- 关联到chunks表
);

-- 虚拟表:向量搜索(sqlite-vec)
CREATEVIRTUALTABLE chunks_vec USING vec0(
embedding float[1536] -- 向量维度
);
这里有几个关键设计:

增量索引:通过mtime和hash,只重新索引变更的文件
去重存储:相同文本块只存一次向量,节省空间
优雅降级:如果sqlite-vec扩展没装上,系统会回退到JS暴力计算
特别值得一提的是这个降级策略。代码里是这样实现的:

async function searchMemory(queryVector, limit = 5) {
try {
// 快速路径:用sqlite-vec在数据库内计算余弦距离
returnawait db.all( SELECT c.text, vec_distance_cosine(v.embedding, ?) AS dist FROM chunks_vec v JOIN chunks c ON c.id = v.id ORDER BY dist ASC LIMIT ? , [queryVector, limit]);
} catch (err) {
// 回退路径:全量加载到内存暴力计算
const allChunks = await db.all("SELECT id, text, embedding FROM chunks");
return allChunks
.map(chunk => ({
...chunk,
dist: cosineSimilarity(queryVector, JSON.parse(chunk.embedding))
}))
.sort((a, b) => a.dist - b.dist)
.slice(0, limit);
}
}
这套机制保证了:无论环境如何,记忆系统永远可用——只是快慢的区别。

三、检索层:BM25 + 向量,两种思维的交织
有了存储,下一步是怎么查。OpenClaw的核心检索工具叫memory_search,它实现的是混合检索。

为什么需要混合?因为纯向量检索有盲区——它懂语义但不懂精确匹配。你搜“Mac Studio网关主机”能找到“运行网关的那台机器”,但搜环境变量名“DB_PASSWORD”可能抓瞎。反之,BM25擅长精确匹配但不懂同义替换。

OpenClaw的做法是:让两者打架,然后加权平均。

// 混合检索的核心逻辑(简化版)
asyncfunction hybridSearch(query, options = {}) {
const vecWeight = 0.7; // 向量权重
const bm25Weight = 0.3; // BM25权重

// 分别检索
const vectorResults = await vectorSearch(query);
const bm25Results = await bm25Search(query);

// 合并结果集(取并集)
const allChunkIds = newSet([
...vectorResults.map(r => r.id),
...bm25Results.map(r => r.id)
]);

// 计算综合得分
const finalResults = [];
for (const id of allChunkIds) {
const vecScore = vectorResults.find(r => r.id === id)?.score || 0;
const bm25Score = bm25Results.find(r => r.id === id)?.score || 0;

// 归一化BM25分数(越小越好转成越大越好)
const normalizedBm25 = 1 / (1 + bm25Score);

finalResults.push({
  id,
  score: vecWeight * vecScore + bm25Weight * normalizedBm25
});

}

return finalResults.sort((a, b) => b.score - a.score).slice(0, options.limit);
}
这套算法的关键在于:用并集而非交集。只要向量或BM25任一方法认为某块内容相关,它就有机会进入候选池,最后通过加权得分决定谁胜出。

检索到相关块后,如果需要完整上下文,Agent可以调用memory_get工具,根据文件路径和行号范围精确读取内容。

人工智能技术学习交流群
伙伴们,对AI测试、大模型评测、质量保障感兴趣吗?我们建了一个 「人工智能测试开发交流群」,专门用来探讨相关技术、分享资料、互通有无。无论你是正在实践还是好奇探索,都欢迎扫码加入,一起抱团成长!期待与你交流!👇

image

四、写入策略:Agent自己决定该记什么
比检索更难的是写入——什么东西值得记?什么东西不值得?

OpenClaw的原则很激进:由Agent自己判断。系统提示词里明确写着:“如果有人说‘记住这个’,写到文件里(不要只存在内存中)。”

写入触发分两类:

自动写入:会话中的重要步骤、决策、异常,由Agent判断后追加进当天的Daily Log。这是append-only的,不覆盖,只追加。

识别写入:当系统判断某个信息“长期有用”时,写入对应的长期记忆文件。判断标准是稳定性——这个信息会在未来的多次会话里持续有价值吗?

具体分类逻辑:

“我以后都用深色模式” → 稳定偏好 → preferences.md
解决了一个复杂问题 → 可复用经验 → learnings.md
开始一个新项目 → 项目状态 → projects.md
提到一个重要的人 → 人物信息 → contacts.md
还有一个很妙的机制叫预压缩记忆冲刷。当会话token数逼近上下文窗口上限时(比如Claude的200K用了176K),系统会主动提示Agent:“快把重要东西写到磁盘上,不然等会儿被压缩了就没了”。这个设计解决了大模型的致命伤——静默遗忘。

五、这套方案的优缺点,我摊牌了
用了这么久,OpenClaw的记忆系统确实牛,但槽点也不少。咱们客观聊聊。

优点:

零运维:不用装Postgres,不用配Docker,一个SQLite文件搞定
数据私有:全在本地,不上云
可审计:所有记忆都是Markdown,打开就能看,用户知道Agent记住了什么
增量索引:只处理变更文件,效率高
优雅降级:向量库挂了还有BM25,BM25挂了还有纯文本
缺点:

token消耗偏高:LinkedIn的实战报告说OpenClaw是“token-hungry”,记忆系统是主要原因
向量检索不懂关系:能找到“Alice”和“auth团队”,但推不出“Alice负责auth”
维护成本随规模线性增长:文件一多,索引更新、重嵌入都得自己操心
高门槛:虽然零运维,但得懂文件结构、会配环境变量,小白上手并不容易
社区已经在尝试改进。有人用Mem0做自动捕获,有人用QMD做更精准的检索,还有人引入知识图谱解决关系推理。但这些改进也带来了新的复杂度——没有银弹。

六、实战:怎么用好这套记忆系统
最后给几个实战建议,都是踩坑换来的经验。

  1. 定期做记忆“体检”
    长期记忆文件会随着时间膨胀。建议定期(比如每月)手动过一遍MEMORY.md,删掉过时的、合并重复的。这活不能全指望AI。

  2. 教会Agent分类
    在系统提示词里加几句引导,告诉Agent什么该记、记哪里。比如:

当用户表达稳定偏好时(“我喜欢”“我习惯”),写入preferences.md
当用户开始新项目时(“我要做一个”),写入projects.md
当用户完成复杂任务时(“解决了”),写入learnings.md
3. 善用Heartbeat做记忆维护
OpenClaw的Heartbeat机制可以定期执行记忆维护任务。比如每天凌晨跑一次:

openclaw cron add --name "记忆维护"
--cron "0 3 * * *"
--system-event "运行记忆整理:合并相似项,删除低价值项,生成摘要"
4. 查询时显式指定范围
调用memory_search时,可以通过scope参数限定搜索范围(比如只搜learnings.md),能显著提升召回准确率,节省token。

OpenClaw的记忆系统给我最大的启发是:AI的记忆不应该是黑盒。用Markdown存真相,用SQLite建索引,用BM25+向量做检索——这套组合拳既保证了功能,又让一切透明可控。

当然,它远非完美。但在这个“所有Agent都想记住你”的时代,能有一个让你随时打开文件、看清它记住了什么的系统,本身就是一种难得的清醒。

推荐学习
开源AI助理 OpenClaw(龙虾)公开课,手把手带你打造24小时不休的AI打工人。

扫码进群,报名学习。

image

关于我们
霍格沃兹测试开发学社,隶属于 测吧(北京)科技有限公司,是一个面向软件测试爱好者的技术交流社区。

学社围绕现代软件测试工程体系展开,内容涵盖软件测试入门、自动化测试、性能测试、接口测试、测试开发、全栈测试,以及人工智能测试与 AI 在测试工程中的应用实践。

我们关注测试工程能力的系统化建设,包括 Python 自动化测试、Java 自动化测试、Web 与 App 自动化、持续集成与质量体系建设,同时探索 AI 驱动的测试设计、用例生成、自动化执行与质量分析方法,沉淀可复用、可落地的测试开发工程经验。

在技术社区与工程实践之外,学社还参与测试工程人才培养体系建设,面向高校提供测试实训平台与实践支持,组织开展 “火焰杯” 软件测试相关技术赛事,并探索以能力为导向的人才培养模式,包括高校学员先学习、就业后付款的实践路径。

同时,学社结合真实行业需求,为在职测试工程师与高潜学员提供名企大厂 1v1 私教服务,用于个性化能力提升与工程实践指导。

posted @ 2026-03-09 14:44  霍格沃兹测试开发学社  阅读(222)  评论(0)    收藏  举报