在发布分层保留与混合搜索三天后,我们正好遇到了当初促成构建持久记忆服务器的那个故障模式:一台机器上的 SQLite 数据库是空的,而 Qdrant Cloud 上仍然保留着所有向量。所有内容——每条记忆的实际文本——只存在于 SQLite 中。向量完好无损,但没有它们所编码的文本就毫无用处。
那次事故催生了 BHGBrain 1.3。这个版本使记忆服务器能够抵御存储故障,为在多台机器上运行 BHGBrain 的团队增加了多设备支持,并提供了一个灾难恢复工具,能够从共享向量存储重建你整个本地数据库。
问题:双存储、单点故障
BHGBrain 的架构使用两个存储:SQLite 用于内容、元数据和全文搜索;Qdrant 用于向量嵌入和语义相似度。每次写入都会同时写入两者。每次搜索都会跨两者关联。
这个设计假设 SQLite 是持久的。它确实是——sql.js 通过先写临时文件再重命名的方式原子性地刷盘。但如果数据库文件丢失、被重新创建,或者在一台不同的机器上从零开始,所有内容就都没了。Qdrant 拥有嵌入和部分元数据(标签、类型、重要性),但没有实际的记忆文本。依赖 SQLite 关联的搜索结果会悄无声息地返回空。
这正是发生的事情。两个 BHGBrain 实例——一个在主工作站上,一个在 Windows 365 云电脑上——指向同一个 Qdrant Cloud 集群。云电脑的 SQLite 是空的。每次召回都返回零结果,尽管 Qdrant 持有数十个高置信度匹配的向量。
修复 1:内容存入 Qdrant Payload
第一个变更很直接:将完整的记忆内容与向量一起存储在 Qdrant payload 中。
在 1.3 之前,Qdrant upsert 的 payload 只包含元数据——namespace、type、tags、importance、retention_tier、decay_eligible、expires_at。内容文本仅存于 SQLite。
现在,writeMemory 和 updateMemory 在每次 Qdrant upsert 中都包含 content、summary、category、source、created_at 和 device_id。Qdrant 成为内容的冗余备份。SQLite 仍然是查询、全文搜索和生命周期管理的主要存储——但丢失它不再意味着丢失数据。
修复 2:自动搜索回退
搜索服务的 buildSearchResults 方法过去会在 SQLite 中查找每个 Qdrant 结果。如果某个记忆 ID 在本地找不到,就会被丢弃。这正是让这个 bug 如此令人困惑的静默失败——Qdrant 返回了匹配项,但调用方看到的是空结果。
现在,当 Qdrant 结果没有匹配的 SQLite 行时,搜索服务会直接从 Qdrant payload 构造结果:
Qdrant returns match → SQLite lookup → found? → return full record
→ not found? → payload has content?
→ yes → return from payload
→ no → skip (pre-1.3 memory)
这意味着一个指向现有 Qdrant 集群的新 BHGBrain 实例会立即返回搜索结果——无需任何 SQLite 数据。结果包含 payload 中存储的所有内容:内容、摘要、类型、标签、保留层级、设备 ID 和创建时间戳。
修复 3:修复工具
要进行完整恢复——重建 SQLite 以使全文搜索、生命周期管理和访问追踪正常工作——现在有一个 repair MCP 工具:
{
"tool": "bhgbrain.repair",
"params": {
"dry_run": true
}
}
修复工具会:
- 通过 collections API 发现 Qdrant 中所有
bhgbrain_*集合 - 滚动遍历每个集合中的每个点(分批分页)
- 对每个点:检查该 ID 是否存在于本地 SQLite
- 如果缺失且 Qdrant payload 包含
content:向 SQLite 插入完整的MemoryRecord - 如果缺失且 payload 中没有内容:跳过(1.3 之前的记忆,内容无法恢复)
- 报告统计信息:扫描的集合、扫描的点、恢复的、跳过的、错误数
使用 dry_run: true 可预览将要恢复的内容而不写入任何数据。使用 device_id 可将恢复范围限定为来自特定设备的记忆。
这就是灾难恢复路径。它也是新设备的入门路径——将一个全新的 BHGBrain 实例指向你现有的 Qdrant 集群,运行 repair,本地 SQLite 就会从共享存储中填充所有内容。
多设备记忆
1.3 中更大的架构变更是对多个 BHGBrain 实例共享单个 Qdrant 后端的一等支持。
架构
Device A (Workstation) Device B (Cloud PC)
┌──────────────────┐ ┌──────────────────┐
│ SQLite (local) │ │ SQLite (local) │
│ device_id: ws-1 │ │ device_id: w365 │
└────────┬─────────┘ └────────┬─────────┘
│ │
└──────────┬───────────────────┘
│
┌──────────▼──────────┐
│ Qdrant Cloud │
│ (shared backend) │
│ content + vectors │
│ device_id index │
└─────────────────────┘
每台设备维护自己的 SQLite 数据库。Qdrant 是共享层。每次写入都会将内容存储到两个存储中,并用来源 device_id 标记该记忆。
设备身份
每个实例在启动时解析一个稳定的 device_id:
- 显式配置:
config.json中的device.id - 环境变量:
BHGBRAIN_DEVICE_ID - 自动生成:从
os.hostname()派生,转为小写并做清理
解析出的 ID 在首次运行时持久化到 config.json。它出现在每个 Qdrant payload、每条 SQLite 记录和每个搜索结果中——因此你始终知道是哪台设备创建了某条记忆。
跨设备可见性
两台设备都能看到所有记忆。当设备 B 搜索设备 A 存储的内容时,Qdrant 搜索会返回匹配项。如果设备 B 的 SQLite 没有该记录,搜索回退会从 Qdrant payload 构造结果。没有任何数据是不可见的。
| 来源 | 设备 A 可见 | 设备 B 可见 |
|---|---|---|
| 设备 A 的记忆(SQLite) | 完整记录 | Qdrant 回退 |
| 设备 B 的记忆(SQLite) | Qdrant 回退 | 完整记录 |
要获得完整的本地功能(全文搜索、生命周期追踪、访问计数),在该设备上运行 repair 以从 Qdrant 填充其 SQLite。
配置
将两台设备指向同一个 Qdrant 集群,使用不同的设备 ID:
// Device A
{
"device": { "id": "workstation" },
"qdrant": {
"mode": "external",
"external_url": "https://your-cluster.cloud.qdrant.io",
"api_key_env": "QDRANT_API_KEY"
}
}
// Device B
{
"device": { "id": "cloud-pc" },
"qdrant": {
"mode": "external",
"external_url": "https://your-cluster.cloud.qdrant.io",
"api_key_env": "QDRANT_API_KEY"
}
}
就这样。两个实例共享同一个记忆池。两者都为写入标记来源。两者都能看到所有内容。
文档:8 张 Mermaid 图
README 现在包含覆盖每个主要子系统的详细 Mermaid 图:
- 架构 —— 展示完整服务器堆栈的组件图
- 多设备拓扑 —— 共享 Qdrant 加每台设备本地 SQLite
- 写入流水线 —— 完整的去重决策流程图
- 层级分配 —— 按优先级排序的分类逻辑
- 层级生命周期 —— 包含晋升、TTL 和过期流程的状态图
- 混合搜索 —— 通过 RRF 融合的并行语义/全文路径
- 备份与恢复 —— 创建和恢复流程的时序图
- 修复流程 —— 灾难恢复流程图
所有四种翻译(德语、西班牙语、法语、简体中文)均已更新至与英文 README 完全对齐,包括所有图表和新的多设备章节。
升级到 1.3
无需手动迁移。升级后首次启动时:
- SQLite 增加一个可为空的
device_id列(现有记忆保持为null) - Qdrant 集合获得
device_idkeyword 索引 - 配置获得从主机名自动解析的
device.id字段 - 所有新写入都会将内容存储到 Qdrant payload 中
Qdrant 中没有内容的 1.3 之前记忆仍通过 SQLite 正常工作。它们只是无法在 SQLite 丢失时通过 repair 恢复——因为写入时内容不在 Qdrant 中。
启示
SQLite 事故带来的最大教训:如果你的系统有两个存储,且其中一个会静默降级,你需要另一个能够在无需运维人员干预的情况下顶上。BHGBrain 1.3 让 Qdrant 成为 SQLite 的安全网,反之亦然。搜索自动回退。恢复只需一次工具调用。多设备共享之所以可行,是因为两个存储都承载了完整信息。
如果你在多台机器上运行 BHGBrain,请升级到 1.3 并在每台设备上运行 repair。如果你只在一台机器上运行,也请升级——内容存入 Qdrant 的变更意味着你下一次 SQLite 意外将是一次无事件,而不是数据丢失。
BHGBrain 是开源的,采用 MIT 许可证,可在 github.com/Big-Hat-Group-Inc/BHGBrain 获取。
Kevin Kaminski 是 Big Hat Group 的负责人,专注于企业 AI 基础设施、Microsoft 365 和 Windows 365。他为在工作中运行 AI 智能体的团队构建开源工具。