本文主要介绍了OpenClaw忆管理系统架构设计,包括双后端架构,记忆存储数据结构,记忆同步记忆,容错机制,还有记忆系统运行状态。
OpenClaw的记忆管理系统是一个高性能、可扩展的AI记忆检索解决方案,专为AI Agent和对话系统设计。该系统采用创新的双后端架构,结合了向量搜索和全文搜索的优势,为AI提供语义理解和关键词匹配的混合检索能力。创新的架构设计实现包括:
高可用性 - 双后端自动降级
高性能 - 混合搜索+缓存优化
高可扩展性 - 支持多种嵌入提供商
实时性 - 文件监听+自动同步
容错性 - 多层错误恢复机制
这种架构使得OpenClaw能够为AI Agent提供可靠、高效的记忆检索服务,是实现长期记忆和上下文感知的关键基础设施。

1.核心架构
1.1 双后端设计
(1)QMD后端(外部)
基于外部QMD进程的记忆管理
支持更高级的搜索特性和性能优化
通过进程间通信进行交互
具备自动降级能力,失败时回退到内置索引
(2)内置索引后端
基于SQLite的本地实现
集成向量搜索(通过vec0扩展)
全文搜索(FTS5)
完全离线工作,无需外部依赖
// 后端选择逻辑exporttype MemoryBackend = "builtin" | "qmd";// 自动降级包装器class FallbackMemoryManager implements MemorySearchManager {private primary: MemorySearchManager;private fallback: MemorySearchManager | null = null;private primaryFailed = false;}1.2 分层架构
┌─────────────────────────────────────────┐│ Agent Tools Layer ││ (memory_search, memory_get) │└──────────────┬──────────────────────────┘ │┌──────────────▼──────────────────────────┐│ Memory Manager Interface ││ (MemorySearchManager) │└──────────────┬──────────────────────────┘ │ ┌───────┴────────┐ │ │┌──────▼─────┐ ┌─────▼──────────┐│ QMD │ │ Built-in ││ Backend │ │ Index │└────────────┘ └────────────────┘ │ ┌───────────────┼───────────────┐ │ │ │┌──────▼──────┐ ┌─────▼─────┐ ┌──────▼──────┐│ Vector │ │ FTS │ │ Embedding ││ Search │ │ Search │ │ Provider │└─────────────┘ └───────────┘ └─────────────┘1.3 核心组件
1.3.1 MemoryIndexManager [3]

内置索引的主要实现类,继承自MemoryManagerEmbeddingOps:
管理SQLite数据库连接
协调向量搜索和全文搜索
处理文件监听和自动同步
实现缓存和批处理优化
1.3.2 QmdMemoryManager [4]
外部QMD后端的封装:

通过CLI命令与QMD进程通信
解析QMD输出的搜索结果
处理作用域和权限验证
管理进程生命周期
2.数据模型 - 记忆存储结构
OpenClaw中的记忆默认存储在 SQLite 数据库文件中,路径为~/.openclaw/memory/memory.db。

2.1 记忆存储结构
// 记忆来源类型exporttype MemorySource = "memory" | "sessions";// 搜索结果exporttype MemorySearchResult = { path: string; // 文件相对路径 startLine: number; // 起始行号 endLine: number; // 结束行号 score: number; // 相关性分数 snippet: string; // 内容片段 source: MemorySource; // 来源类型 citation?: string; // 引用标记};2.2 memory vs sessions
2.2.1 memory — 用户记忆文件
来源:用户主动编写的 Markdown 文件
路径示例:
MEMORY.md # 根记忆文件memory/ # 记忆目录 ├── 2026-03-24.md # 日期记忆 ├── architecture.md # 架构文档 ├── patterns.md # 设计模式 └── conventions.md # 编码规范特点:
永久性知识
用户主动维护
支持时间衰减(日期文件)
常青记忆不衰减(如
MEMORY.md)
用途:
存储项目知识、决策记录、编码规范等
2.2.2 sessions — Agent 会话记录
来源:AI Agent 对话的历史记录
路径示例:
~/.openclaw/agents/<agentId>/sessions/ ├── 2026-03-24T10-30-00.123Z.jsonl ├── 2026-03-24T14-22-33.456Z.jsonl └── ...特点:
JSONL 格式存储
自动生成(对话结束时)
包含完整对话上下文
增量同步
内容结构(每行一个 JSON)
{ "role": "user", "content": "用户消息" }{ "role": "assistant", "content": "AI 回复", "toolCalls": [...] }2.2.3 实际应用场景
搜索时的行为:
{"memorySearch": {"sources": ["memory", "sessions"] }}memory 来源返回结果示例:
{"path": "memory/architecture.md","source": "memory","score": 0.85,"snippet": "..."}sessions 来源返回结果示例:
{"path": "~/.openclaw/agents/xxx/sessions/2026-03-24T10-30-00.123Z.jsonl","source": "sessions","score": 0.92,"snippet": "..."}2.2.4 为什么分开存储
2.2.5 实际查询示例
用户:之前讨论过的数据库优化方案在哪?
搜索结果:
1.sessions/2026-03-15.jsonl (score: 0.95)snippet: "关于数据库优化,我们讨论了..."2.memory/2026-03-15.md (score: 0.82)snippet: "数据库优化会议纪要..."3.memory/architecture.md (score: 0.76)snippet: "数据库索引优化策略..."这种设计让 AI 能同时检索 讨论过程(sessions) 和 结论知识(memory),从而提供更全面的上下文。
3.数据库Schema - chunks表 - 文本块存储
3.1 chunks表
存储文本块的原始内容、文件关联和元数据 。
CREATETABLE chunks (idINTEGER PRIMARY KEY, file_id INTEGERNOTNULL, chunk_index INTEGERNOTNULL,contentTEXTNOTNULL,sourceTEXTNOTNULL, provider_model TEXT, created_at INTEGER,FOREIGNKEY (file_id) REFERENCES files(id));3.2 chunks 表结构说明
id | ||
file_id | ||
chunk_index | ||
content | ||
source | "memory" 或 "sessions" | |
provider_model | "text-embedding-3-small" | |
created_at |
3.3 示例数据
3.4 关键关系
files (id=42, path="MEMORY.md") └─ chunks (file_id=42, chunk_index=0) → content: "第一段内容" └─ chunks (file_id=42, chunk_index=1) → content: "第二段内容" └─ chunks (file_id=42, chunk_index=2) → content: "第三段内容"说明:同一个文件被切分成多个 chunk,使用 chunk_index 保持原始顺序。
4.数据库Schema - chunks_vec表 - 向量索引(语义检索)
4.1 chunks_vec表
存储向量嵌入,通过向量距离计算实现语义相似度搜索,找到与查询语义最相近的文本块。
CREATEVIRTUALTABLE chunks_vec USING vec0( chunk_id INTEGER PRIMARY KEY, vector_float(1536), -- 根据模型维度调整sourceTEXT);4.2 chunks_vec 表结构说明
chunk_id | ||
vector_float | ||
source | "memory" 或 "sessions",用于过滤搜索范围 |
4.3 向量维度说明
4.4 示例数据
4.5 与 chunks 表的关系
chunks (id=1, content="OpenClaw记忆管理...") └─ chunks_vec (chunk_id=1, vector_float=[0.12, -0.34, ...])4.6 关联查询示例
SELECT c.id, c.content, c.source, v.vector_floatFROM chunks cJOIN chunks_vec v ON c.id = v.chunk_idWHERE c.source = 'memory'4.7 使用场景 - 语义搜索(按相似度排序)
SELECT c.id, c.content, vec0_distance_chunks_vec(c.vector_float, ?) AS distanceFROM chunks cJOIN chunks_vec v ON c.id = v.chunk_idWHERE c.source = 'memory'ORDERBY distanceLIMIT10;4.8 使用场景 - 只搜索 memory 来源
SELECT chunk_idFROM chunks_vecWHEREsource = 'memory';4.9 使用场景 - 只搜索 sessions 来源
SELECT chunk_idFROM chunks_vecWHEREsource = 'sessions';5.数据库Schema - chunks_fts表 - 全文搜索(关键词检索)
5.1 chunks_fts表
通过关键词匹配和 BM25 排序实现精确的文本搜索,补充向量搜索的不足。
CREATEVIRTUALTABLE chunks_fts USING fts5(content,source, tokenize='porter unicode61');5.2 chunks_fts 表结构说明
content | ||
source | "memory" 或 "sessions",用于过滤搜索范围 | |
tokenize |
5.3 FTS5 特性
5.4 Tokenize 参数详解
示例效果:
输入:
"机器学习 machine learning running"分词结果:
["机器", "学习", "machine", "run"]
说明:
running → run(词干提取)
与 chunks 表的关系:
chunks_fts是chunks的全文索引视图,通过rowid自动关联。
关联查询示例:
SELECT c.id, c.content, c.start_line, c.end_line, bm25(chunks_fts, 0) AS scoreFROM chunks_fts ftsJOIN chunks c ON fts.rowid = c.idWHERE chunks_fts MATCH'机器学习'ORDERBY scoreLIMIT10;5.5 搜索示例
5.5.1 简单关键词搜索
SELECT * FROM chunks_ftsWHEREcontentMATCH'database'LIMIT10;5.5.2 多词 OR 搜索
SELECT * FROM chunks_ftsWHEREcontentMATCH'"database" OR "optimization"'LIMIT10;5.5.3 短语精确匹配
SELECT * FROM chunks_ftsWHEREcontentMATCH'"memory management system"'LIMIT10;5.5.4 按来源过滤
SELECT c.*FROM chunks_fts ftsJOIN chunks c ON fts.rowid = c.idWHERE fts.content MATCH'embedding'AND c.source = 'memory';5.6 BM25 分数计算
BM25 分数范围:0(最相关)到 ∞(不相关)。转换为归一化分数:
SELECT c.content, bm25(chunks_fts, 0) AS bm25_score, (bm25(chunks_fts, 0) / (1 + bm25(chunks_fts, 0))) AS normalized_scoreFROM chunks_fts ftsJOIN chunks c ON fts.rowid = c.idWHERE fts.content MATCH'query'ORDERBY bm25_score;6.OpenClaw 记忆同步机制
OpenClaw 通过三种机制协同工作,确保记忆索引与文件系统、会话数据保持实时或准实时同步。
6.1 文件监听 (File Watcher)
6.1.1 工作原理
protected watcher: FSWatcher | null = null;private ensureWatcher(): void {// 监听 memory 目录下的所有文件const memoryPaths = [ path.join(this.workspaceDir, "MEMORY.md"), path.join(this.workspaceDir, "memory"), // memory/*.md ...this.settings.extraPaths, // 额外路径 ];this.watcher = chokidar.watch(memoryPaths, { ignored: /(^|[\/\\])\../, // 忽略 .git, .node_modules 等 persistent: true, // 持续监听,不自动退出 ignoreInitial: false, // 初始扫描也触发事件 });// 任何文件变化都标记为 dirtythis.watcher.on('all', (event, filePath) => { log.debug(`Memory file changed: ${event}${filePath}`);this.dirty = true; // 标记需要同步 });}6.1.2 触发场景
add | ||
change | ||
unlink | ||
addDir | ||
unlinkDir |
6.1.3 实际流程
用户编辑 memory/architecture.md ↓chokidar 检测到 change 事件 ↓this.dirty = true ↓下次搜索时触发 sync() ↓重新索引 architecture.md ↓更新 chunks / chunks_vec / chunks_fts 表 ↓this.dirty = false6.2 会话同步 (Session Listener)
6.2.1 工作原理
protected sessionUnsubscribe: (() => void) | null = null;protectedsessionsDirtyFiles = newSet<string>();protectedsessionsDirty = false;privateensureSessionListener(): void { // 监听 Agent 会话结束事件this.sessionUnsubscribe = onAgentSessionEnd((sessionFile) => { log.debug(`Session ended: ${sessionFile}`);this.sessionsDirtyFiles.add(sessionFile); // 记录变更的会话文件this.sessionsDirty = true; // 标记需要同步 });}6.2.2 触发时机
Agent 对话进行中... ↓用户结束对话 / 超时 ↓触发 onAgentSessionEnd 事件 ↓写入会话文件: ~/.openclaw/agents/xxx/sessions/2026-03-25T10-30-00.jsonl ↓sessionListener 收到通知 ↓sessionsDirty = true6.2.3 增量更新策略
async sync(params?: { sessionFiles?: string[] }) {if (params?.sessionFiles) {// 增量同步指定的会话文件for (const sessionFile of params.sessionFiles) {awaitthis.indexSessionFile(sessionFile); } } else {// 全量同步awaitthis.indexAllSessions(); }}6.3 定期同步 (Interval Sync)
6.3.1 工作原理
private intervalTimer: NodeJS.Timeout | null = null;private ensureIntervalSync(): void {// 每分钟检查一次是否需要同步this.intervalTimer = setInterval(() => {if (this.dirty || this.sessionsDirty) { log.debug("Periodic sync triggered");voidthis.sync({ reason: "interval" }).catch((err) => { log.warn(`Periodic sync failed: ${String(err)}`); }); } }, 60 * 1000); // 60秒}6.3.2 触发条件
if (this.dirty || this.sessionsDirty) {// 有变更才同步this.sync({ reason: "interval" });}6.3.3 时间线示例
10:00:00 - 启动定时器10:01:00 - 检查: dirty=false, 跳过10:02:00 - 检查: dirty=false, 跳过10:03:00 - 用户编辑 memory/arch.md → dirty=true10:03:45 - 检查: dirty=true → 执行 sync()10:04:00 - 检查: dirty=false, 跳过6.4 三种机制的协作关系
6.4.1 触发优先级
async search(query: string, opts?: SearchOptions) {// 1. 会话预热(最高优先级)voidthis.warmSession(opts?.sessionKey);// 2. 搜索触发同步if (this.settings.sync.onSearch && (this.dirty || this.sessionsDirty)) {voidthis.sync({ reason: "search" }); }// 3. 执行搜索...return results;}6.4.2 完整同步流程
┌─────────────────────────────────────────────────┐│ 三种触发源 │├──────────┬──────────────┬──────────────────────┤│ 文件监听 │ 会话监听 │ 定时器 ││ (实时) │ (会话结束) │ (每分钟) │└────┬─────┴──────┬───────┴──────┬───────────────┘ │ │ │ ▼ ▼ ▼ dirty=true sessionsDirty=true 检查标记 │ │ │ └────────────┴──────────────┘ │ ▼ ┌────────────────┐ │ sync() 方法 │ └────────────────┘ │ ┌─────────────┼─────────────┐ │ │ │ ▼ ▼ ▼ ┌───────┐ ┌─────────┐ ┌──────────┐ │文件监听│ │会话同步 │ │清理标记 │ │索引变更│ │增量更新 │ │dirty=false│ └───────┘ └─────────┘ └──────────┘6.4.3 并发控制
private syncing: Promise<void> | null = null;async sync(params?: SyncParams): Promise<void> {// 如果正在同步,返回现有 Promiseif (this.syncing) {// 如果有新的会话文件,加入队列if (params?.sessionFiles) {returnthis.enqueueTargetedSessionSync(params.sessionFiles); }returnthis.syncing; }// 开始新的同步this.syncing = this.runSync(params).finally(() => {this.syncing = null; // 完成后清除 });returnthis.syncing;}6.5 配置选项
{"memorySearch": {"sync": {"onSearch": true, // 搜索时自动同步"onSessionStart": true// 会话开始时预热 } }}onSearch | ||
onSessionStart |
6.6 性能优化
6.6.1 防抖策略
// 文件监听设置 dirty,但不立即同步this.watcher.on('all', (event, path) => {this.dirty = true; // 只标记,不同步});// 等待搜索或定时器触发if (this.dirty && this.settings.sync.onSearch) {awaitthis.sync(); // 批量处理所有变更}6.6.2 增量索引
只对标记为 dirty 的变更文件重新分块和嵌入,避免全量索引的性能开销。
async runSync(params?: SyncParams) {if (this.dirty) {const changedFiles = awaitthis.collectChangedFiles();for (const file of changedFiles) {awaitthis.indexFile(file); // 增量更新 } }if (this.sessionsDirty) {for (const sessionFile of this.sessionsDirtyFiles) {awaitthis.indexSessionFile(sessionFile); }this.sessionsDirtyFiles.clear(); }this.dirty = false;this.sessionsDirty = false;}6.6.3 队列机制
将并发的会话文件更新请求收集到队列中,等待当前同步完成后批量处理,防止资源竞争和重复索引。
private enqueueTargetedSessionSync(sessionFiles: string[]) {for (const file of sessionFiles) {this.queuedSessionFiles.add(file); }if (!this.queuedSessionSync) {this.queuedSessionSync = (async () => {awaitthis.syncing?.catch(() => undefined);while (this.queuedSessionFiles.size > 0) {const files = Array.from(this.queuedSessionFiles);this.queuedSessionFiles.clear();awaitthis.sync({ sessionFiles: files }); } })(); }}6.7 总结
这三种机制共同确保记忆索引始终与实际内容保持同步,兼顾实时性、可靠性和性能。
7.OpenClaw 容错机制
只读数据库恢复和后端降级,这两者结合确保 OpenClaw 记忆系统的高可用性和容错能力。
7.1 只读数据库恢复 (Readonly Database Recovery)
处理临时性的数据库访问问题,通过重新连接尝试自动恢复。
7.1.1 触发场景
// 检测只读数据库错误private isReadonlyDbError(err: unknown): boolean {const readonlyPattern =/attempt to write a readonly database|database is read-only|SQLITE_READONLY/i;// 检查错误消息const message = err instanceofError ? err.message : String(err);return readonlyPattern.test(message);}常见原因:
🔒 SQLite 数据库文件被设置为只读权限
💾 磁盘空间不足
📁 文件被其他进程锁定
🔄 文件系统错误(如网络挂载断开)
7.1.2 恢复流程
privateasync runSyncWithReadonlyRecovery(params?: SyncParams): Promise<void> {try {// 第一次尝试:正常同步awaitthis.runSync(params);return; } catch (err) {// 1. 检查是否是只读错误if (!this.isReadonlyDbError(err) || this.closed) {throw err; // 不是只读错误或已关闭,直接抛出 }const reason = this.extractErrorReason(err);this.readonlyRecoveryAttempts += 1;this.readonlyRecoveryLastError = reason; log.warn(`memory sync readonly handle detected; reopening sqlite connection`, { reason });// 2. 关闭旧连接try {this.db.close(); } catch {}// 3. 重新打开数据库连接this.db = this.openDatabase();// 4. 重置向量扩展状态this.vectorReady = null;this.vector.available = null;this.vector.loadError = undefined;// 5. 重新初始化 Schemathis.ensureSchema();// 6. 读取元数据(验证连接)const meta = this.readMeta();this.vector.dims = meta?.vectorDims;// 7. 重试同步try {awaitthis.runSync(params);this.readonlyRecoverySuccesses += 1; // 成功计数 } catch (retryErr) {this.readonlyRecoveryFailures += 1; // 失败计数throw retryErr; } }}7.1.3 完整时间线
┌─────────────────────────────────────────────────────┐│ 正常流程 │├─────────────────────────────────────────────────────┤│ ││ runSync() ││ │ ││ ├─> 打开事务 ││ ├─> 写入 chunks ││ ├─> ❌ SQLITE_READONLY: attempt to write a... ││ │ ││ ▼ ││ 捕获错误 ││ │ ││ ├─> isReadonlyDbError(err) → true ││ │ ││ ▼ ││ 恢复流程 ││ │ ││ ├─> db.close() ││ ├─> db = openDatabase() ││ ├─> ensureSchema() ││ ├─> vectorReady = null ││ │ ││ ▼ ││ 重试同步 ││ │ ││ ├─> runSync() ││ ├─> ✅ 成功 ││ │ ││ ▼ ││ readonlyRecoverySuccesses++ ││ │└─────────────────────────────────────────────────────┘7.1.4 监控指标
status(): MemoryProviderStatus {return {// ... custom: { readonlyRecovery: { attempts: this.readonlyRecoveryAttempts, // 尝试次数 successes: this.readonlyRecoverySuccesses, // 成功次数 failures: this.readonlyRecoveryFailures, // 失败次数 lastError: this.readonlyRecoveryLastError, // 最后的错误 }, }, };}7.2 后端降级 (Backend Fallback)
QMD 不可用时自动切换到内置索引,确保记忆搜索始终可用。
7.2.1 架构设计
class FallbackMemoryManager implements MemorySearchManager {private primary: MemorySearchManager; // 主后端(QMD)private fallback: MemorySearchManager | null; // 备用后端private primaryFailed = false; // 主后端失败标记private lastError?: string; // 最后的错误信息}7.2.2 降级触发流程
async search(query: string, opts?: SearchOptions) {// 1. 尝试主后端if (!this.primaryFailed) {try {returnawaitthis.deps.primary.search(query, opts); } catch (err) {// 主后端失败this.primaryFailed = true;this.lastError = err instanceofError ? err.message : String(err); log.warn(`qmd memory failed; switching to builtin index: ${this.lastError}`);// 清理主后端awaitthis.deps.primary.close?.().catch(() => {});// 驱逐缓存,下次可以重试 QMDthis.evictCacheEntry(); } }// 2. 降级到备用后端const fallback = awaitthis.ensureFallback();if (fallback) {returnawait fallback.search(query, opts); }// 3. 备用后端也不可用thrownewError(this.lastError ?? "memory search unavailable");}7.2.3 备用后端初始化
privateasync ensureFallback(): Promise<MemorySearchManager | null> {// 如果已经有备用后端,直接返回if (this.fallback) {returnthis.fallback; }try {// 创建内置索引管理器const { MemoryIndexManager } = await loadManagerRuntime();const manager = await MemoryIndexManager.get(this.params);if (!manager) { log.warn("memory fallback requested but builtin index is unavailable");returnnull; }this.fallback = manager;returnthis.fallback; } catch (err) {const message = err instanceofError ? err.message : String(err); log.warn(`memory fallback unavailable: ${message}`);returnnull; }}7.2.4 完整降级时间线
┌─────────────────────────────────────────────────────────┐│ 后端降级流程 │├─────────────────────────────────────────────────────────┤│ ││ 用户发起搜索 ││ │ ││ ▼ ││ primaryFailed = false? ││ │ ││ ├─ Yes ───────────────────────┐ ││ │ │ ││ ▼ │ ││ 尝试 QMD 后端 │ ││ │ │ ││ ├─ ✅ 成功 ──> 返回结果 │ ││ │ │ ││ └─ ❌ 失败 │ ││ │ │ ││ ▼ │ ││ primaryFailed = true │ ││ lastError = "..." │ ││ 关闭 QMD 连接 │ ││ 驱逐缓存条目 │ ││ │ │ ││ ▼ ▼ ││ 检查 fallback 是否存在 │ ││ │ │ ││ ├─ 存在 ──> 使用 fallback │ ││ │ │ ││ └─ 不存在 ──> 创建 │ ││ │ │ ││ ▼ │ ││ MemoryIndexManager.get() │ ││ │ │ ││ ├─ ✅ 成功 ──> 使用它 │ ││ │ │ ││ └─ ❌ 失败 ──> 抛出错误 │ ││ │ │└──────────────────────────────────────┴────────────────────┘7.2.5 状态报告
主后端正常时直接返回其状态,降级后在备用后端状态中追加降级来源和原因信息。
status(): MemoryProviderStatus {if (!this.primaryFailed) {// 主后端正常returnthis.deps.primary.status(); }// 已降级,报告降级状态const fallbackStatus = this.fallback?.status();return { ...fallbackStatus, fallback: {from: "qmd", reason: this.lastError ?? "unknown" }, custom: { ...fallbackStatus?.custom, fallback: { disabled: true, reason: this.lastError ?? "unknown" } } };}7.2.6 缓存驱逐策略
失败后从全局缓存中移除失效的管理器,下次请求创建新实例以重试主后端。
private evictCacheEntry(): void {if (this.cacheEvicted) {return; // 已经驱逐过 }this.cacheEvicted = true;this.onClose?.(); // 从全局缓存中移除}// 下次请求会创建新的管理器,可以重试 QMDexportasyncfunctiongetMemorySearchManager(params) {const cached = QMD_MANAGER_CACHE.get(cacheKey);if (cached) {return { manager: cached }; // 使用缓存的 }// 创建新的管理器(QMD 可能已恢复)const manager = await QmdMemoryManager.create({ ... });return { manager };}7.3 两种机制的对比
7.4 实际应用场景
7.4.1 场景1:外部磁盘断开
用户挂载网络磁盘
/mnt/memorySQLite 数据库在网络磁盘上
网络中断 → SQLITE_READONLY
readonlyRecovery 触发
重新打开连接 → 失败(磁盘仍不可用)
readonlyRecoveryFailures++
抛出错误给用户
7.4.2 场景2:QMD 进程崩溃
用户配置 backend = "qmd"
QMD 进程意外崩溃
search() 调用失败
自动降级到 MemoryIndexManager
使用内置索引继续搜索
status() 显示 fallback 信息
7.4.3 场景3:权限问题
数据库文件被设为只读:
chmod 444 memory.dbsync() 尝试写入 → SQLITE_READONLY
readonlyRecovery 重新打开连接
仍然只读 → 恢复失败
提示用户检查权限
8.MemoryProviderStatus 详细解释
这些性能指标帮助用户和开发者实时了解记忆系统的运行状态。
exporttype MemoryProviderStatus = {// 基础配置 backend: "builtin" | "qmd"; // 后端类型 provider: string; // 嵌入提供商ID model?: string; // 嵌入模型名称// 索引统计 files?: number; // 已索引的文件数量 chunks?: number; // 文本块总数 dirty?: boolean; // 是否有待同步的变更// 向量搜索状态 vector?: { enabled: boolean; // 是否启用向量搜索 available?: boolean; // vec0 扩展是否可用 dims?: number; // 向量维度(1536/3072/768等) };// 全文搜索状态 fts?: { enabled: boolean; // 是否启用 FTS available: boolean; // FTS5 是否可用 };// 缓存状态 cache?: { enabled: boolean; // 是否启用嵌入缓存 entries?: number; // 当前缓存条目数 maxEntries?: number; // 最大缓存条目数 };// 额外信息 fallback?: { // 降级状态from: string; // 从哪个后端降级 reason?: string; // 降级原因 }; batch?: { // 批处理状态 enabled: boolean; // 批处理是否启用 failures: number; // 失败次数 limit: number; // 失败上限 wait: boolean; // 是否等待重试 concurrency: number; // 并发数 pollIntervalMs: number; // 轮询间隔 timeoutMs: number; // 超时时间 lastError?: string; // 最后的错误 lastProvider?: string; // 最后的提供商 }; custom?: Record<string, unknown>; // 自定义字段};8.1 字段信息详解
8.1.1 基础配置信息
backend | "builtin" | "qmd" | "builtin""qmd" — 外部 QMD | |
provider | string | "openai""gemini", "voyage", "mistral", "ollama", "local", "none" | |
model | string? | "text-embedding-3-small""gemini-embedding-2", "voyage-3" |
8.1.2 索引统计信息
files | number? | SELECT COUNT(*) FROM files42 | |
chunks | number? | SELECT COUNT(*) FROM chunks1580 | |
dirty | boolean? | truefalse — 已同步 |
8.1.3 向量搜索状态
vector.enabled | boolean | truefalse — 仅 FTS | |
vector.available | boolean? | vec0 | truefalse — 不可用undefined — 未检测 |
vector.dims | number? | 15363072 — OpenAI large768 — Gemini1024 — Voyage / Mistral |
8.1.4 全文搜索状态
fts.enabled | boolean | truefalse — 仅向量 | |
fts.available | boolean | truefalse — 创建失败 |
8.1.5 缓存状态
cache.enabled | boolean | memorySearch.cache.enabled | |
cache.entries | number? | SELECT COUNT(*) FROM embedding_cache1580 | |
cache.maxEntries | number? | memorySearch.cache.maxEntries示例: 10000 |
8.1.6 额外状态字段
fallback.from | string | "qmd" | |
fallback.reason | string? | "Connection refused" | |
batch.enabled | boolean | true | |
batch.failures | number | 0 | |
batch.limit | number | 2 | |
batch.concurrency | number | 4 | |
batch.pollIntervalMs | number | 5000 | |
batch.timeoutMs | number | 30000 |
8.1.7 向量维度对照表
text-embedding-3-small | ||
text-embedding-3-large | ||
gemini-embedding-2 | ||
gemini-embedding-2-preview | ||
voyage-3 | ||
voyage-3-lite | ||
mistral-embed |
8.1.8 状态示例速查
backend | provider | dirty | vector.available | ||
|---|---|---|---|---|---|
builtin | openai | false | true | ||
builtin | gemini | true | true | ||
builtin | none | false | false | ||
qmd | gemini | false | - | fallback |
8.2 字段速查表
backend | "builtin" | "qmd" | "builtin" | |
provider | string | "openai""gemini", "none" | |
model | string? | "text-embedding-3-small" | |
files | number? | 42 | |
chunks | number? | 1580 | |
dirty | boolean? | truefalse | |
vector.enabled | boolean | true | |
vector.available | boolean? | truefalse | |
vector.dims | number? | 1536768, 3072 | |
fts.enabled | boolean | true | |
fts.available | boolean | truefalse | |
cache.enabled | boolean | true | |
cache.entries | number? | 1580 | |
cache.maxEntries | number? | 10000 |
8.3 完整状态示例
8.3.1 内置索引后端 - 正常状态
{ // ========== 基础配置 =========="backend": "builtin", // 后端类型: "builtin"(内置SQLite) | "qmd"(外部QMD) "provider": "openai", // 嵌入提供商: openai/gemini/voyage/mistral/ollama/local/none"model": "text-embedding-3-small", // 嵌入模型名称 (FTS-only时为undefined) // ========== 索引统计 =========="files": 42, // 已索引的文件总数 (memory + sessions) "chunks": 1580, // 文本块总数 (一个文件可能切分为多个chunk)"dirty": false, // 是否有待同步的变更 (true=需重新索引, false=已最新)// ========== 向量搜索状态 =========="vector": {"enabled": true, // 是否启用向量搜索 (由 memorySearch.store.vector.enabled 控制)"available": true, // vec0扩展是否可用 (false表示扩展加载失败)"dims": 1536// 向量维度 (根据模型: 1536/3072/768/1024) },// ========== 全文搜索状态 ========== "fts": {"enabled": true, // 是否启用FTS5全文搜索 (由 memorySearch.query.hybrid.enabled 控制)"available": true// FTS5是否可用 (false表示表创建失败) },// ========== 缓存状态 =========="cache": {"enabled": true, // 是否启用嵌入缓存 (由 memorySearch.cache.enabled 控制)"entries": 1580, // 当前缓存的嵌入条目数 "maxEntries": 10000// 最大缓存条目数 (由 memorySearch.cache.maxEntries 控制) }, // ========== 批处理状态 =========="batch": {"enabled": true, // 批处理是否启用"failures": 0, // 累计失败次数"limit": 2, // 失败上限 (达到上限后禁用批处理)"wait": true, // 失败后是否等待重试"concurrency": 4, // 并发处理的请求数 "pollIntervalMs": 5000, // 批处理轮询间隔 (毫秒)"timeoutMs": 30000// 单次批处理超时时间 (毫秒) }, // ========== 自定义字段 =========="custom": {"searchMode": "hybrid", // 搜索模式: "hybrid"(向量+FTS) | "fts-only"(仅FTS)"readonlyRecovery": { // 只读数据库恢复统计"attempts": 0, // 恢复尝试次数 "successes": 0, // 恢复成功次数"failures": 0// 恢复失败次数 } }}8.3.2 QMD 后端 - 降级状态
{ // ========== 基础配置 =========="backend": "qmd", // 后端类型: "qmd"(外部QMD) | "builtin"(内置SQLite) "provider": "gemini", // 嵌入提供商: gemini/openai/voyage/mistral等"model": "gemini-embedding-2", // QMD使用的嵌入模型名称 // ========== 索引统计 ========== "files": 42, // 已索引的文件总数 (来自fallback后端)"chunks": 1580, // 文本块总数 (来自fallback后端) "dirty": false, // 是否有待同步的变更 (来自fallback后端)// ========== 降级信息 =========="fallback": {"from": "qmd", // 从哪个后端降级 (原主后端)"reason": "Connection refused"// 降级原因 (QMD进程崩溃/不可用/超时等) }, // ========== 自定义字段 ========== "custom": { "fallback": { // fallback详细状态"disabled": true, // fallback已禁用标记"reason": "Connection refused"// fallback禁用原因 (与fallback.reason相同) } }}8.3.3 FTS-only 模式
{ // ========== 基础配置 =========="backend": "builtin", // 后端类型: "builtin"(内置SQLite) "provider": "none", // ⚠️ 嵌入提供商: "none" 表示无向量嵌入"model": undefined, // ⚠️ 模型名称: undefined (无嵌入时为空) // ========== 索引统计 =========="files": 42, // 已索引的文件总数"chunks": 1580, // 文本块总数 "dirty": false, // 是否有待同步的变更// ========== 向量搜索状态 ========== "vector": {"enabled": false, // ❌ 向量搜索未启用"available": false// ❌ vec0扩展不可用 (无嵌入时自然不可用) },// ========== 全文搜索状态 =========="fts": {"enabled": true, // ✅ FTS5全文搜索启用 (FTS-only模式的主要搜索方式)"available": true// ✅ FTS5可用 ─ },// ========== 自定义字段 ========== "custom": {"searchMode": "fts-only", // ⚠️ 搜索模式: "fts-only" (仅全文搜索,无向量)"providerUnavailableReason": "No API key configured"// 无嵌入提供商的原因 }}8.4 使用场景
8.4.1 命令行查询
执行 openclaw memory status 快速查看记忆系统的后端、提供商、索引统计和组件状态。
$ openclaw memory statusBackend: builtinProvider: openai (text-embedding-3-small)Files: 42Chunks: 1,580Dirty: noVector: enabled, available, 1536 dimsFTS: enabled, availableCache: enabled, 1,580/10,000 entries8.4.2 程序化查询
通过 status() 方法获取状态对象,在代码中检查 dirty 标记、向量可用性和缓存使用率等指标。
const status = memoryManager.status();if (status.dirty) {console.log("需要同步记忆索引");}if (!status.vector?.available) {console.warn("向量搜索不可用,将使用 FTS");}if (status.cache?.entries && status.cache.maxEntries) {const usage = (status.cache.entries / status.cache.maxEntries * 100).toFixed(1);console.log(`缓存使用率: ${usage}%`);}8.4.3 健康检查
根据后端类型、提供商可用性和向量状态返回系统健康等级。
functioncheckMemoryHealth(status: MemoryProviderStatus): "healthy" | "degraded" | "down" {if (status.backend === "qmd" && status.fallback) {return"degraded"; // QMD 降级到 builtin }if (status.provider === "none") {return"degraded"; // FTS-only 模式 }if (status.vector?.enabled && !status.vector?.available) {return"down"; // 向量搜索失败 }return"healthy";}参考文献
[1] OpenClaw记忆管理系统架构设计:https://my.feishu.cn/wiki/DbGbwTj1OijmOUkVMrCc4VVQnrh
[2] QMD GitHub 仓库: https://github.com/tobi/qmd
核心特性:结合 BM25(全文搜索)+ Vectors(向量搜索)+ Reranking(重排序)
[3] MemoryIndexManager 类结构总结:https://my.feishu.cn/wiki/HKfUw5q2ViXqzvkk8XOcYuIynme
[4] QmdMemoryManager 类结构总结:https://my.feishu.cn/wiki/O7mywzep3iaQYUkhIOvcz7V7n1b
知识星球:Dify源码剖析及答疑,OpenClaw源码剖析及答疑。加微信buxingtianxia21进NLP工程化资料群,Dify源码交流群,OpenClaw源码交流群。
夜雨聆风