深入解析 OpenClaw 的记忆搜索架构设计
全面分析 OpenClaw 如何实现一个优雅的多层记忆搜索系统,从语义搜索到关键词匹配的平滑降级。
深入解析 OpenClaw 的记忆搜索架构
全面分析 OpenClaw 如何实现一个优雅的多层记忆搜索系统,从语义搜索到关键词匹配的平滑降级。
引言
在构建需要记忆过去交互和积累知识的 AI Agent 时,我们面临一个基本挑战:如何平衡人类可读性、搜索性能和系统复杂度?OpenClaw 的记忆搜索系统提供了一个优雅的解决方案,对 AI Agent 架构设计有深远的影响。
通过逆向工程和源代码分析,我发现 OpenClaw 实现了一个精妙的三层记忆系统,能够从语义向量搜索无缝降级到关键词匹配,同时保持 Markdown 文件作为唯一的真相源。
架构概览
OpenClaw 的记忆搜索建立在三个核心原则之上:
- 人类优先的数据格式:所有记忆都存储为用户可以直接读写的 Markdown 文件
- 渐进增强:系统在多个能力级别上工作,从基础文件扫描到高级语义搜索
- 零锁定:索引纯粹用于加速——随时可以删除并从源文件重建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用户接口层
│
├─── Markdown 文件(MEMORY.md, memory/*.md)
│
▼
SQLite 索引层
│
├─── 元数据表 (files)
├─── 全文搜索 (chunks_fts)
└─── 向量索引 (chunks_vec)
│
▼
搜索运行时
│
├─── 纯 FTS 模式(无 embedding)
├─── 纯向量模式(无 FTS)
└─── 混合模式(FTS + 向量)
三层设计
第一层:Markdown 文件(真相源)
基础设计简洁优雅:
~/.openclaw/workspace/MEMORY.md- 精选的长期记忆memory/- 记忆文件夹2024-01-15.md- 每日日志条目2024-01-16.md- 每日日志条目projects.md- 主题特定笔记
关键设计决策:
- MEMORY.md:保留重要的、精选的记忆(仅在私人会话中加载)
- memory/*.md:日常日志和主题文件,用于运行上下文
- 纯文本:无专有格式,可用任何文本编辑器
- Git 友好:每个更改都可以 diff 和追踪
第二层:SQLite 索引(性能加速器)
SQLite 数据库(~/.openclaw/memory/main.sqlite)作为一个可丢弃的加速层:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 核心架构(简化版)
CREATE TABLE chunks (
id TEXT PRIMARY KEY,
path TEXT NOT NULL, -- 源文件
start_line INTEGER NOT NULL, -- 文件中的位置
end_line INTEGER NOT NULL,
text TEXT NOT NULL, -- 块内容
embedding TEXT NOT NULL, -- 向量(JSON 数组)
updated_at INTEGER NOT NULL
);
-- 全文搜索索引
CREATE VIRTUAL TABLE chunks_fts USING fts5(
text, -- 可搜索内容
id UNINDEXED,
path UNINDEXED,
-- ... 其他元数据
);
-- 向量索引(当 sqlite-vec 可用时)
CREATE VIRTUAL TABLE chunks_vec USING vec0(
chunk_id TEXT PRIMARY KEY,
embedding FLOAT[1536]
);
这个索引的精妙之处在于它不做什么:
- 它从不修改源 Markdown 文件
- 它可以随时删除并重建
- 它不是基础功能所必需的
第三层:搜索运行时(多模式检索)
搜索层根据可用能力自适应:
1
2
3
4
5
6
7
8
9
10
11
// 根据配置确定搜索模式
if (!this.provider) {
// 模式1:纯 FTS(无 embedding provider)
return this.searchFTSOnly(query);
} else if (!this.fts.available) {
// 模式2:纯向量(FTS 不可用)
return this.searchVectorOnly(query);
} else {
// 模式3:混合(两者都可用)
return this.searchHybrid(query);
}
存储层:Markdown 作为真相源
为什么选择 Markdown?
选择 Markdown 而非 JSON、YAML 或数据库是经过深思熟虑的:
- 人类可编辑:用户可以修正 AI 的错误、添加笔记、重新组织内容
- AI 原生:LLM 无需特殊解析就能理解 Markdown 结构
- 工具无关:适用于任何文本编辑器、grep、git 等
- 自文档化:格式本身就提示了如何组织记忆
记忆文件约定
1
2
3
4
5
6
7
8
9
10
# MEMORY.md 示例
## 关于用户
- 喜欢咖啡胜过茶
- 居住在北京
- 从事 AI 项目
## 重要决策
### 2024-01-15
决定使用 SQLite 实现向量搜索,而不是专用的向量数据库...
日常文件遵循类似的模式:
1
2
3
4
5
6
7
# 2024-01-15
## 早晨讨论
用户询问了如何实现记忆搜索。要点:
- 需要平衡性能和简单性
- SQLite 似乎是个不错的折中方案
- 应该支持优雅降级
人机协作
OpenClaw 将记忆管理视为协作过程:
- AI 写入初始记忆
- 人类可以编辑、重组和纠正
- 文件监视器自动捕获更改
- 没有”同步冲突”——文件始终是权威的
索引层:SQLite 作为加速器
分块策略
文档被分割成重叠的块以实现最佳检索:
1
2
const DEFAULT_CHUNK_TOKENS = 400; // ~100-200 个词
const DEFAULT_CHUNK_OVERLAP = 80; // ~20% 重叠
这确保了:
- 块足够大以保持上下文
- 重叠防止在边界处丢失信息
- 基于 token 的分割尊重句子边界
三个表,三个用途
- chunks:存储可搜索的内容
1 2
INSERT INTO chunks (id, path, start_line, end_line, text, embedding) VALUES (?, ?, ?, ?, ?, ?);
- chunks_fts:全文搜索索引
1 2
INSERT INTO chunks_fts (text, id, path, ...) SELECT text, id, path, ... FROM chunks;
- chunks_vec:向量相似度索引(可选)
1 2
INSERT INTO chunks_vec (chunk_id, embedding) SELECT id, embedding FROM chunks;
索引生命周期
索引自动维护:
1
2
3
4
5
6
7
8
9
10
// 文件监视器触发重新索引
this.watcher = chokidar.watch(['MEMORY.md', 'memory/**/*.md'], {
persistent: false,
ignoreInitial: true,
});
this.watcher.on('change', () => {
this.markDirty();
this.scheduleSyncDebounced();
});
关键行为:
- 更改被去抖动(默认 1.5 秒)
- 同步异步运行以避免阻塞搜索
- 失败的同步不会破坏搜索(使用陈旧索引)
搜索层:混合检索
模式 1:纯 FTS(无 embedding)
当没有配置 embedding provider 时,系统回退到智能关键词搜索:
1
2
3
4
5
6
7
8
9
10
11
// 从自然语言查询中提取关键词
const keywords = extractKeywords(query);
// "我之前告诉过你什么关于咖啡的事?" → ["告诉", "咖啡"]
// 独立搜索每个关键词
const resultSets = await Promise.all(
keywords.map(term => this.searchKeyword(term))
);
// 合并并去重结果
const merged = mergeResultSets(resultSets);
关键词提取相当复杂:
- 删除停用词(”的”、”一个”、”什么”)
- 保留重要术语
- 处理多种语言
模式 2:纯向量(纯语义)
当只有 embedding 可用时:
1
2
3
4
5
// 获取查询 embedding
const queryVec = await this.provider.embed(query);
// 使用余弦相似度查找相似块
const results = await this.searchVector(queryVec, limit);
向量搜索擅长:
- 语义相似性(”咖啡” ≈ “拿铁” ≈ “浓缩咖啡”)
- 语义改写(”我的机器” ≈ “我拥有的电脑”)
- 跨语言匹配(使用多语言模型)
模式 3:混合搜索(最佳点)
当 FTS 和向量搜索都可用时,OpenClaw 结合了它们的优势:
1
2
3
4
5
6
7
8
9
10
// 并行运行两种搜索
const [vectorResults, keywordResults] = await Promise.all([
this.searchVector(queryVec, candidateLimit),
this.searchKeyword(query, candidateLimit)
]);
// 加权组合
const finalScore =
vectorWeight * vectorScore +
textWeight * textScore;
为什么混合搜索很重要:
- 向量处理概念和改述
- 关键词擅长精确匹配(ID、名称、代码)
- 两者结合覆盖更多检索场景
后处理管道
合并结果后,两个可选阶段优化输出:
1. MMR(最大边际相关性)
通过平衡相关性和多样性来减少冗余:
1
2
// MMR 评分
const mmrScore = λ * relevance - (1-λ) * maxSimilarityToSelected;
这防止返回五个几乎相同的关于同一主题的块。
2. 时间衰减
提升最近的记忆而非旧的:
1
2
// 基于年龄的指数衰减
const decayedScore = score * Math.exp(-λ * ageInDays);
默认半衰期为 30 天:
- 今天:100% 分数
- 1 周:~84% 分数
- 1 月:50% 分数
- 3 月:12.5% 分数
优雅降级
系统的降级策略确保它总是返回某些东西:
降级流程:
- Embedding API 失败 → 降级到 FTS 搜索
- SQLite 损坏 → 降级到文件扫描
- 文件不可访问 → 返回空结果
每个级别提供逐渐基础但仍然功能性的搜索:
- 完全混合:语义 + 关键词搜索
- 纯 FTS:智能关键词提取和匹配
- 文件扫描:线性搜索 Markdown 文件(最后手段)
实现细节
Embedding Provider
OpenClaw 支持多个 embedding provider,具有自动回退:
1
2
3
4
5
6
// 提供者解析顺序
1. 本地(通过 llama.cpp 的 GGUF 模型)
2. OpenAI(text-embedding-3-small)
3. Google(gemini-embedding-001)
4. Voyage(voyage-3)
5. 无(纯 FTS 模式)
性能优化
- Embedding 缓存
1 2 3 4 5
CREATE TABLE embedding_cache ( hash TEXT PRIMARY KEY, embedding TEXT, updated_at INTEGER );
- 批处理
1 2 3 4
// 用于大型语料库的 OpenAI 批处理 API if (settings.batch.enabled) { return this.batchEmbed(chunks); }
- SQLite-vec 扩展
1 2 3 4
-- 硬件加速的向量操作 SELECT vec_distance_cosine(embedding, ?) AS distance FROM chunks_vec ORDER BY distance ASC
记忆作用域
不同的会话类型有不同的记忆访问权限:
1
2
3
4
5
6
7
8
9
// 私人 DM 会话 - 完全访问
if (sessionType === 'direct') {
return ['MEMORY.md', 'memory/**/*.md'];
}
// 群聊 - 有限访问
if (sessionType === 'group') {
return ['memory/**/*.md']; // 没有 MEMORY.md
}
这防止了将个人上下文泄露到公共空间。
性能特征
基于实现,以下是预期性能:
索引性能
- 初始索引:每 MB Markdown 约 1-2 秒
- 增量更新:每个更改文件 <100ms
- 内存使用:约为 Markdown 大小的 10-20 倍(含 embedding)
搜索性能
- 纯 FTS:大多数查询 <10ms
- 向量搜索(使用 sqlite-vec):10k 块 <50ms
- 混合搜索:典型 <100ms
可扩展性限制
- 测试达到:100MB Markdown(约 50k 块)
- 实际限制:1GB Markdown(取决于系统)
- 超过此限制:考虑专用向量数据库
对 AI Agent 设计的启示
OpenClaw 的记忆系统教会了我们几个宝贵的经验:
1. 用户自主权很重要
通过将记忆保存在可编辑的 Markdown 中:
- 用户保持对其数据的控制
- 错误可以被纠正
- 组织可以自定义
- 无供应商锁定
2. 渐进增强有效
多层降级确保:
- 无需外部依赖的基本功能
- 资源允许时的增强能力
- 能力级别之间的平滑过渡
- 没有突然的功能丢失
3. 混合方法获胜
结合多种检索方法:
- 覆盖更多用例
- 提供回退选项
- 平衡优缺点
- 提高整体可靠性
4. 简单可扩展
使用 SQLite 而非专用向量数据库:
- 减少操作复杂性
- 实现本地优先操作
- 简化备份/恢复
- 降低入门门槛
结论
OpenClaw 的记忆搜索架构证明,复杂的 AI 能力不需要复杂的基础设施。通过深思熟虑地组合简单组件——Markdown 文件、SQLite 和可选嵌入——它实现了一个既强大又易于访问的系统。
关键见解是最好的 AI 记忆系统是将人类和 AI 都视为一等参与者的系统。Markdown 提供人机界面,SQLite 提供性能,降级策略确保它始终在某个级别上工作。
对于构建 AI Agent 的开发者,OpenClaw 的方法提供了一个蓝图:从人类可读的格式开始,添加可以重建的加速层,并始终提供优雅的降级路径。结果是用户可以信任、理解和控制的系统——这是处理个人信息的 AI 系统的基本品质。
参考文献和延伸阅读
本分析基于 OpenClaw 版本 2026.2.17 的源代码审查和运行时行为观察。