Moltbot 记忆系统深度解析:文件驱动的 AI 长期记忆方案
Moltbot 记忆系统深度解析:文件驱动的 AI 长期记忆方案
LLM 的记忆问题一直是业界难题。GPT 用黑盒式记忆,用户无法查看和编辑;Gemini 靠超长上下文硬扛,但终究有限。Moltbot 走了一条不同的路:文件为源、数据库为缓存、AI 自主管理。
本文基于 Moltbot 源码,深入解析这套记忆系统的架构和实现细节。
核心设计理念
Moltbot 记忆系统的核心原则:
- Markdown 文件是唯一的事实来源 — 数据库只是派生缓存
- 透明可编辑 — 用户可以直接
cat、grep、手动编辑记忆文件 - 可迁移 — 复制
~/clawd/目录就能迁移全部记忆 - AI 自主管理 — 模型在上下文压缩前主动保存重要信息
整体架构
flowchart TB
subgraph 文件层["文件层 (Source of Truth)"]
MEMORY["MEMORY.md<br/>长期记忆"]
DAILY["memory/YYYY-MM-DD.md<br/>每日日志"]
end
subgraph 索引层["索引层 (Derived Cache)"]
SQLITE[("SQLite 数据库<br/>~/.clawdbot/memory/")]
subgraph 表结构
CHUNKS["chunks<br/>文本块 + 向量"]
FTS["chunks_fts<br/>BM25 全文索引"]
VEC["chunks_vec<br/>向量索引"]
CACHE["embedding_cache<br/>嵌入缓存"]
end
end
subgraph 检索层["检索层"]
HYBRID["混合搜索引擎"]
VECTOR["向量检索<br/>(语义相似)"]
KEYWORD["关键词检索<br/>(精确匹配)"]
end
subgraph 工具层["工具层"]
SEARCH["memory_search<br/>语义搜索"]
GET["memory_get<br/>读取文件"]
end
MEMORY --> SQLITE
DAILY --> SQLITE
SQLITE --> CHUNKS
SQLITE --> FTS
SQLITE --> VEC
SQLITE --> CACHE
CHUNKS --> VECTOR
FTS --> KEYWORD
VECTOR --> HYBRID
KEYWORD --> HYBRID
HYBRID --> SEARCH
MEMORY --> GET
DAILY --> GET
文件存储层
两层记忆结构
Moltbot 使用两层 Markdown 文件来组织记忆:
~/clawd/ # 默认工作区
├── MEMORY.md # 长期记忆 (可选)
└── memory/
├── 2026-01-27.md # 昨天的笔记
├── 2026-01-28.md # 今天的笔记
└── ...
| 文件 | 用途 | 加载时机 |
|---|---|---|
MEMORY.md | 决策、偏好、持久事实 | 仅在私人主会话中加载 |
memory/YYYY-MM-DD.md | 每日运行笔记和上下文 | 加载今天 + 昨天 |
为什么选择 Markdown
- 人类可读 — 不需要特殊工具就能查看和编辑
- 版本控制友好 — 可以用 Git 追踪记忆变化
- 跨平台 — 任何文本编辑器都能处理
- AI 原生 — LLM 天生擅长理解和生成 Markdown
索引层:SQLite 作为派生缓存
虽然网上有说法称 Moltbot "没有数据库",但实际上它使用 SQLite 来加速检索。关键区别在于:数据库是派生的,不是主存储。
数据库结构
-- 索引元数据 (用于检测配置变更)
CREATE TABLE meta (
key TEXT PRIMARY KEY,
value TEXT
);
-- 文件追踪
CREATE TABLE files (
path TEXT PRIMARY KEY,
hash TEXT,
mtime INTEGER,
size INTEGER,
source TEXT
);
-- 文本块 + 嵌入向量
CREATE TABLE chunks (
id TEXT PRIMARY KEY,
path TEXT,
start_line INTEGER,
end_line INTEGER,
text TEXT,
embedding BLOB,
model TEXT
);
-- BM25 全文搜索
CREATE VIRTUAL TABLE chunks_fts USING fts5(
text,
content='chunks',
content_rowid='rowid'
);
-- 嵌入缓存 (避免重复计算)
CREATE TABLE embedding_cache (
provider TEXT,
model TEXT,
hash TEXT,
embedding BLOB,
dims INTEGER,
updated_at INTEGER,
PRIMARY KEY (provider, model, hash)
);
为什么需要数据库
纯文件检索的问题:
- 向量搜索需要预计算 — 每次搜索都重新计算嵌入向量太慢
- 全文搜索需要索引 — 遍历所有文件效率太低
- 增量更新 — 只处理变更的文件,而不是每次全量重建
但数据库是 可丢弃的:
# 删除数据库,系统会从 Markdown 文件重建
rm ~/.clawdbot/memory/*.sqlite
分块策略
Moltbot 将 Markdown 文件切分成小块,每块约 400 tokens,带 80 tokens 重叠:
// src/memory/internal.ts
const CHARS_PER_TOKEN = 4;
const DEFAULT_CHUNK_TOKENS = 400;
const DEFAULT_OVERLAP_TOKENS = 80;
// 每块约 1600 字符,重叠 320 字符
const chunkSize = DEFAULT_CHUNK_TOKENS * CHARS_PER_TOKEN; // 1600
const overlap = DEFAULT_OVERLAP_TOKENS * CHARS_PER_TOKEN; // 320
分块时保留行号信息,便于后续精确定位:
flowchart LR
subgraph 原文件
L1["Line 1-40"]
L2["Line 35-75"]
L3["Line 70-110"]
end
subgraph 分块结果
C1["Chunk 1<br/>lines 1-40<br/>~400 tokens"]
C2["Chunk 2<br/>lines 35-75<br/>~400 tokens"]
C3["Chunk 3<br/>lines 70-110<br/>~400 tokens"]
end
L1 --> C1
L2 --> C2
L3 --> C3
C1 -.->|"80 token<br/>重叠"| C2
C2 -.->|"80 token<br/>重叠"| C3
混合搜索引擎
Moltbot 采用向量 + 关键词的混合搜索策略,兼顾语义理解和精确匹配。
为什么需要混合搜索
| 搜索类型 | 擅长 | 不擅长 |
|---|---|---|
| 向量搜索 | 语义相似("Mac Studio 网关主机" ≈ "运行网关的机器") | 精确 ID、代码符号 |
| 关键词搜索 | 精确匹配(a828e60、memorySearch.query.hybrid) | 同义词、换种说法 |
混合算法实现
// src/memory/hybrid.ts
// 1. BM25 排名转换为 0-1 分数
export function bm25RankToScore(rank: number): number {
const normalized = Math.max(0, rank);
return 1 / (1 + normalized);
}
// 2. 合并两路检索结果
export function mergeHybridResults(params: {
vector: HybridVectorResult[];
keyword: HybridKeywordResult[];
vectorWeight: number; // 默认 0.7
textWeight: number; // 默认 0.3
}) {
const byId = new Map();
// 收集向量检索结果
for (const r of params.vector) {
byId.set(r.id, {
...r,
vectorScore: r.vectorScore,
textScore: 0,
});
}
// 合并关键词检索结果
for (const r of params.keyword) {
const existing = byId.get(r.id);
if (existing) {
existing.textScore = r.textScore;
} else {
byId.set(r.id, { ...r, vectorScore: 0, textScore: r.textScore });
}
}
// 计算加权分数并排序
return Array.from(byId.values())
.map(entry => ({
...entry,
score: params.vectorWeight * entry.vectorScore
+ params.textWeight * entry.textScore,
}))
.sort((a, b) => b.score - a.score);
}
检索流程
sequenceDiagram
participant User as 用户查询
participant Hybrid as 混合引擎
participant Vector as 向量检索
participant BM25 as BM25 检索
participant Result as 最终结果
User->>Hybrid: "Mac Studio 网关配置"
par 并行检索
Hybrid->>Vector: 语义搜索 (top 24)
Hybrid->>BM25: 关键词搜索 (top 24)
end
Vector-->>Hybrid: 向量相似度结果
BM25-->>Hybrid: BM25 排名结果
Hybrid->>Hybrid: 合并去重
Hybrid->>Hybrid: 加权打分<br/>0.7×vector + 0.3×text
Hybrid->>Hybrid: 按分数排序
Hybrid->>Result: 返回 top 6
默认配置
{
"agents": {
"defaults": {
"memorySearch": {
"query": {
"maxResults": 6,
"minScore": 0.35,
"hybrid": {
"enabled": true,
"vectorWeight": 0.7,
"textWeight": 0.3,
"candidateMultiplier": 4 // 检索 4 倍候选,再重排序
}
}
}
}
}
}
预压缩记忆刷新:AI 主动保存记忆
这是 Moltbot 记忆系统最巧妙的设计之一。当会话上下文即将被压缩时,系统会触发一个 静默的 agentic turn,提示模型主动保存重要信息。
触发条件
// src/auto-reply/reply/memory-flush.ts
export function shouldRunMemoryFlush(params: {
entry?: { totalTokens, compactionCount, memoryFlushCompactionCount };
contextWindowTokens: number;
reserveTokensFloor: number; // 默认 20000
softThresholdTokens: number; // 默认 4000
}): boolean {
const { totalTokens } = params.entry;
// 计算触发阈值
const threshold = contextWindowTokens - reserveTokensFloor - softThresholdTokens;
// token 数未达阈值,不触发
if (totalTokens < threshold) return false;
// 本轮压缩周期已经刷新过,不重复触发
const { compactionCount, memoryFlushCompactionCount } = params.entry;
if (memoryFlushCompactionCount === compactionCount) return false;
return true;
}
刷新流程
flowchart TB
START[会话进行中] --> CHECK{totalTokens >= threshold?}
CHECK -->|否| CONTINUE[继续会话]
CHECK -->|是| FLUSHED{本周期已刷新?}
FLUSHED -->|是| CONTINUE
FLUSHED -->|否| INJECT[注入静默 turn]
INJECT --> PROMPT["系统提示:<br/>Pre-compaction memory flush.<br/>Store durable memories now."]
PROMPT --> MODEL[模型执行]
MODEL --> WRITE["写入 memory/YYYY-MM-DD.md"]
WRITE --> REPLY["回复 NO_REPLY<br/>(用户不可见)"]
REPLY --> MARK[标记已刷新]
MARK --> CONTINUE
默认提示词
// src/auto-reply/reply/memory-flush.ts
export const DEFAULT_MEMORY_FLUSH_PROMPT = [
"Pre-compaction memory flush.",
"Store durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed).",
"If nothing to store, reply with NO_REPLY.",
].join(" ");
export const DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT = [
"Pre-compaction memory flush turn.",
"The session is near auto-compaction; capture durable memories to disk.",
"You may reply, but usually NO_REPLY is correct.",
].join(" ");
为什么这样设计
- 自动化 — 不需要用户手动提醒 "记住这个"
- 静默 — 用户看不到这轮对话,体验无感
- 防重复 — 每个压缩周期只触发一次
- 可配置 — 阈值、提示词都可以自定义
嵌入向量提供者
Moltbot 支持多种嵌入向量提供者,按优先级自动选择:
flowchart TD
START[开始] --> LOCAL{本地模型<br/>已配置?}
LOCAL -->|是| USE_LOCAL[使用 node-llama-cpp]
LOCAL -->|否| OPENAI{OpenAI<br/>API Key?}
OPENAI -->|是| USE_OPENAI[使用 text-embedding-3-small]
OPENAI -->|否| GEMINI{Gemini<br/>API Key?}
GEMINI -->|是| USE_GEMINI[使用 gemini-embedding-001]
GEMINI -->|否| DISABLED[记忆搜索禁用]
批量嵌入优化
对于大量文本的索引,Moltbot 支持 OpenAI 和 Gemini 的批量 API:
{
"agents": {
"defaults": {
"memorySearch": {
"provider": "openai",
"model": "text-embedding-3-small",
"remote": {
"batch": {
"enabled": true,
"concurrency": 2, // 并行批次数
"pollIntervalMs": 2000, // 轮询间隔
"timeoutMinutes": 60 // 超时时间
}
}
}
}
}
}
批量 API 的优势:
- 成本更低 — OpenAI 批量 API 约有 50% 折扣
- 速度更快 — 异步处理,不阻塞主流程
- 自动重试 — 429/5xx 错误自动重试最多 3 次
索引同步策略
Moltbot 采用多种策略保持索引新鲜:
| 触发时机 | 行为 |
|---|---|
| 会话启动 | 检查索引是否 dirty,异步触发同步 |
| 搜索时 | 同步 dirty 文件后再搜索 |
| 文件变更 | 监听 MEMORY.md 和 memory/,1.5s 防抖 |
| 定时同步 | 可配置的后台同步间隔 |
| 强制重建 | moltbot memory index 命令 |
重建检测
当以下配置变更时,自动触发全量重建:
- 嵌入提供者 (provider)
- 嵌入模型 (model)
- 分块参数 (tokens/overlap)
- 向量维度 (dims)
原子重建
为保证一致性,重建过程是原子的:
flowchart LR
A[创建临时数据库] --> B[并行重建索引]
B --> C[原子交换文件]
C --> D{成功?}
D -->|是| E[清理临时文件]
D -->|否| F[从备份恢复]
工具接口
Moltbot 提供两个记忆工具给 AI 使用:
memory_search — 语义搜索
memory_search(
query: string, // 搜索查询
maxResults?: number, // 最大结果数 (默认 6)
minScore?: number // 最低分数阈值 (默认 0.35)
)
// 返回
{
results: [{
path: "memory/2026-01-28.md",
startLine: 15,
endLine: 23,
snippet: "...",
score: 0.82
}],
provider: "openai",
model: "text-embedding-3-small",
fallback: false
}
memory_get — 读取文件
memory_get(
path: string, // 相对路径 (MEMORY.md 或 memory/*)
from?: number, // 起始行号
lines?: number // 读取行数
)
// 返回
{
path: "memory/2026-01-28.md",
text: "..."
}
路径验证:只允许访问 MEMORY.md 和 memory/ 下的文件,防止目录遍历攻击。
与其他方案的对比
| 特性 | GPT Memory | Gemini | Moltbot |
|---|---|---|---|
| 存储位置 | 服务端黑盒 | 上下文窗口 | 本地 Markdown |
| 用户可见 | ❌ | N/A | ✅ |
| 可编辑 | ❌ | N/A | ✅ |
| 可迁移 | ❌ | N/A | ✅ 复制目录即可 |
| 容量限制 | 未知 | 上下文长度 | 磁盘空间 |
| 检索方式 | 未知 | 全量上下文 | 混合搜索 |
| 主动保存 | 自动(不透明) | 无 | ✅ 预压缩刷新 |
配置参考
完整的记忆搜索配置:
{
"agents": {
"defaults": {
"memorySearch": {
"enabled": true,
"sources": ["memory"], // 或 ["memory", "sessions"]
"provider": "auto", // "openai" | "gemini" | "local" | "auto"
"model": "text-embedding-3-small",
"fallback": "none",
"local": {
"modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/...",
"modelCacheDir": null
},
"store": {
"driver": "sqlite",
"path": "~/.clawdbot/memory/{agentId}.sqlite",
"vector": { "enabled": true }
},
"chunking": {
"tokens": 400,
"overlap": 80
},
"sync": {
"onSessionStart": true,
"onSearch": true,
"watch": true,
"watchDebounceMs": 1500
},
"query": {
"maxResults": 6,
"minScore": 0.35,
"hybrid": {
"enabled": true,
"vectorWeight": 0.7,
"textWeight": 0.3,
"candidateMultiplier": 4
}
},
"cache": {
"enabled": true,
"maxEntries": 50000
}
},
"compaction": {
"reserveTokensFloor": 20000,
"memoryFlush": {
"enabled": true,
"softThresholdTokens": 4000
}
}
}
}
}
总结
Moltbot 的记忆系统体现了一种务实的设计哲学:
- 文件为源 — Markdown 是唯一的事实来源,数据库只是加速缓存
- 透明可控 — 用户可以直接查看、编辑、迁移记忆
- 混合检索 — 向量语义 + 关键词精确,互补而非替代
- 主动保存 — AI 在上下文压缩前自动保存重要信息
- 渐进降级 — 本地 → 远程 → 禁用,总有可用方案
这不是"IR 理论最优解",但正如文档所说:"简单、够快、实用"。
在大家都在堆复杂系统的时候,Moltbot 选择了一条更透明、更可控的路。这或许才是 AI 记忆系统应有的样子。
参考资料
相关文章
2025年1月29日
Quantization:机器学习中的量化
机器学习中的量化入门
2025年1月27日
模型蒸馏:大模型知识迁移的关键技术
探索模型蒸馏技术如何实现大型模型向小型模型的知识迁移,以及在实际应用中的重要价值。
2025年1月26日
DeepSeek-R1: 通过纯强化学习打造的推理型大模型
探索DeepSeek团队在大语言模型推理能力提升方面的创新方案,包括纯强化学习的R1-Zero和结合冷启动的R1模型。