在推出分層保留與混合搜尋三天後,我們就遇到了當初打造持久記憶體伺服器要解決的失敗情境:某台機器的 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:將完整 MemoryRecord 插入 SQLite
  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 關鍵字索引
  • 設定新增從主機名稱自動解析的 device.id 欄位
  • 所有新寫入都將內容存入 Qdrant payload

1.3 前不含內容的 Qdrant 記憶仍透過 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 代理的團隊打造開源工具。