在发布分层保留与混合搜索三天后,我们正好遇到了当初促成构建持久记忆服务器的那个故障模式:一台机器上的 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 只包含元数据——namespacetypetagsimportanceretention_tierdecay_eligibleexpires_at。内容文本仅存于 SQLite。

现在,writeMemoryupdateMemory 在每次 Qdrant upsert 中都包含 contentsummarycategorysourcecreated_atdevice_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
  }
}

修复工具会:

  1. 通过 collections API 发现 Qdrant 中所有 bhgbrain_* 集合
  2. 滚动遍历每个集合中的每个点(分批分页)
  3. 对每个点:检查该 ID 是否存在于本地 SQLite
  4. 如果缺失且 Qdrant payload 包含 content:向 SQLite 插入完整的 MemoryRecord
  5. 如果缺失且 payload 中没有内容:跳过(1.3 之前的记忆,内容无法恢复)
  6. 报告统计信息:扫描的集合、扫描的点、恢复的、跳过的、错误数

使用 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

  1. 显式配置config.json 中的 device.id
  2. 环境变量BHGBRAIN_DEVICE_ID
  3. 自动生成:从 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 图:

  1. 架构 —— 展示完整服务器堆栈的组件图
  2. 多设备拓扑 —— 共享 Qdrant 加每台设备本地 SQLite
  3. 写入流水线 —— 完整的去重决策流程图
  4. 层级分配 —— 按优先级排序的分类逻辑
  5. 层级生命周期 —— 包含晋升、TTL 和过期流程的状态图
  6. 混合搜索 —— 通过 RRF 融合的并行语义/全文路径
  7. 备份与恢复 —— 创建和恢复流程的时序图
  8. 修复流程 —— 灾难恢复流程图

所有四种翻译(德语、西班牙语、法语、简体中文)均已更新至与英文 README 完全对齐,包括所有图表和新的多设备章节。


升级到 1.3

无需手动迁移。升级后首次启动时:

  • SQLite 增加一个可为空的 device_id 列(现有记忆保持为 null
  • Qdrant 集合获得 device_id keyword 索引
  • 配置获得从主机名自动解析的 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 智能体的团队构建开源工具。