段階的リテンションとハイブリッド検索をリリースして3日後、そもそも永続メモリサーバーを構築する理由となった障害モードに直面しました: あるマシンのSQLiteデータベースが空になり、Qdrant Cloudにはすべてのベクトルが残っているという事態です。すべてのコンテンツ — すべてのメモリの実際のテキスト — はSQLiteにしか存在しませんでした。ベクトルは無事でしたが、エンコードしたテキストなしでは役に立ちませんでした。

このインシデントがBHGBrain 1.3を後押ししました。今回のリリースは、ストレージ障害に対するメモリサーバーのレジリエンス強化、複数マシンでBHGBrainを実行するチーム向けのマルチデバイスサポート、共有ベクターストアからローカルデータベース全体を再構築できるディザスタリカバリツールの提供を実現します。


問題: デュアルストア、単一障害点

BHGBrainのアーキテクチャは2つのストアを使用します: コンテンツ、メタデータ、全文検索用のSQLite、ベクトル埋め込みとセマンティック類似度用のQdrantです。すべての書き込みは両方に送られます。すべての検索は両方をまたいで結合します。

この設計はSQLiteが耐久性があることを前提としていました。実際そうですが — sql.jsはtemp書き込み後にリネームする形でディスクに原子的にフラッシュします。しかしデータベースファイルが失われたり、再作成されたり、別のマシンで新規起動されたりすると、すべてのコンテンツが消えます。Qdrantには埋め込みと一部のメタデータ(タグ、型、重要度)がありますが、実際のメモリテキストはありません。SQLite結合に依存する検索結果はサイレントに何も返しません。

まさにこれが発生しました。2つのBHGBrainインスタンス — 1つはプライマリワークステーション、1つはWindows 365 Cloud PC — が同じQdrant Cloudクラスターを指していました。Cloud PCのSQLiteは空でした。すべてのリコールがゼロ件を返しましたが、Qdrantには高信頼度マッチのベクトルが数十件ありました。


修正1: Qdrantペイロードへのコンテンツ保存

最初の変更は単純です: ベクトルと並行してQdrantペイロードに完全なメモリコンテンツを保存します。

1.3より前は、Qdrantのupsertペイロードにはメタデータのみが含まれていました — namespacetypetagsimportanceretention_tierdecay_eligibleexpires_at。コンテンツテキストはSQLiteのみでした。

現在、writeMemoryupdateMemoryはすべてのQdrant upsertにcontentsummarycategorysourcecreated_atdevice_idを含めます。Qdrantがコンテンツの冗長バックアップになります。SQLiteは引き続きクエリ、全文検索、ライフサイクル管理のプライマリストアですが、SQLiteを失ってもデータを失わなくなりました。


修正2: 自動検索フォールバック

検索サービスのbuildSearchResultsメソッドは、以前はすべてのQdrant結果をSQLiteで検索していました。メモリIDがローカルに見つからない場合は破棄されていました。これがバグを紛らわしくしていたサイレント障害です — Qdrantはマッチを返しても、呼び出し元には空の結果が見えていました。

現在、Qdrant結果に一致するSQLite行がない場合、検索サービスはQdrantペイロードから直接結果を構築します:

Qdrantがマッチを返す → SQLite検索 → 見つかった? → 完全レコードを返す
                              → 見つからない? → ペイロードにコンテンツがある?
                                               → はい → ペイロードから返す
                                               → いいえ → スキップ(1.3以前のメモリ)

これにより、既存のQdrantクラスターを指す新規BHGBrainインスタンスは、SQLiteデータ不要で即座に検索結果を返します。結果にはペイロードに保存されたすべての情報が含まれます: コンテンツ、サマリー、型、タグ、リテンションティア、デバイスID、作成タイムスタンプ。


修正3: 修復ツール

完全な復元 — 全文検索、ライフサイクル管理、アクセス追跡が適切に機能するようSQLiteを再構築する — のために、新しいrepair MCPツールがあります:

{
  "tool": "bhgbrain.repair",
  "params": {
    "dry_run": true
  }
}

修復ツールは以下を行います:

  1. コレクションAPI経由でQdrant内のすべてのbhgbrain_*コレクションを発見
  2. 各コレクションの全ポイントをスクロール(バッチページネーション)
  3. 各ポイントについて: IDがローカルSQLiteに存在するか確認
  4. 不足しておりQdrantペイロードにcontentが含まれる場合: SQLiteに完全なMemoryRecordを挿入
  5. 不足しておりペイロードにコンテンツがない場合: スキップ(1.3以前のメモリ、コンテンツは復元不可)
  6. 統計をレポート: スキャンしたコレクション、スキャンしたポイント、復元、スキップ、エラー

dry_run: trueを使用すると書き込まずに復元内容をプレビューできます。device_idを使用すると特定デバイスのメモリに復元をフィルタリングできます。

これがディザスタリカバリパスです。同時に新規デバイスのオンボーディングパスでもあります — 新規BHGBrainインスタンスを既存のQdrantクラスターに向け、repairを実行すると、ローカルSQLiteに共有ストアのすべてが取り込まれます。


マルチデバイスメモリ

1.3のより大きなアーキテクチャ変更は、単一のQdrantバックエンドを共有する複数BHGBrainインスタンスのファーストクラスサポートです。

アーキテクチャ

デバイスA(ワークステーション)       デバイスB(Cloud PC)
┌──────────────────┐           ┌──────────────────┐
│ SQLite(ローカル)  │           │ SQLite(ローカル)  │
│ device_id: ws-1  │           │ device_id: w365  │
└────────┬─────────┘           └────────┬─────────┘
         │                              │
         └──────────┬───────────────────┘
                    │
         ┌──────────▼──────────┐
         │  Qdrant Cloud       │
         │  (共有バックエンド)  │
         │  コンテンツ + ベクトル│
         │  device_idインデックス│
         └─────────────────────┘

各デバイスは独自のSQLiteデータベースを維持します。Qdrantが共有レイヤーです。すべての書き込みは両方のストアにコンテンツを保存し、起源のdevice_idでメモリにタグを付けます。

デバイスID

各インスタンスは起動時に安定したdevice_idを解決します:

  1. 明示的な設定: config.json内のdevice.id
  2. 環境変数: BHGBRAIN_DEVICE_ID
  3. 自動生成: os.hostname()から導出、小文字化とサニタイズ済み

解決されたIDは初回実行時にconfig.jsonに永続化されます。すべてのQdrantペイロード、すべてのSQLiteレコード、すべての検索結果に現れるため、どのデバイスがメモリを作成したかを常に把握できます。

クロスデバイス可視性

両デバイスはすべてのメモリを確認できます。デバイスBがデバイスAが保存した何かを検索すると、Qdrant検索がマッチを返します。デバイスBのSQLiteにレコードがない場合、検索フォールバックがQdrantペイロードから結果を構築します。データが見えないことはありません。

ソースデバイスAの見え方デバイスBの見え方
デバイスAのメモリ(SQLite)完全レコードQdrantフォールバック
デバイスBのメモリ(SQLite)Qdrantフォールバック完全レコード

完全なローカル機能(全文検索、ライフサイクル追跡、アクセス数)のために、デバイスでrepairを実行してQdrantからSQLiteに取り込みます。

設定

両デバイスを異なるデバイスIDで同じQdrantクラスターに向けます:

// デバイスA
{
  "device": { "id": "workstation" },
  "qdrant": {
    "mode": "external",
    "external_url": "https://your-cluster.cloud.qdrant.io",
    "api_key_env": "QDRANT_API_KEY"
  }
}
// デバイス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. マルチデバイストポロジー — デバイスごとのローカルSQLiteと共有Qdrant
  3. 書き込みパイプライン — 完全な重複排除決定フローチャート
  4. ティア割り当て — 優先度順の分類ロジック
  5. ティアライフサイクル — 昇格、TTL、期限切れフローを含む状態図
  6. ハイブリッド検索 — RRF融合を通る並列セマンティック/フルテキストパス
  7. バックアップと復元 — 作成・復元フローのシーケンス図
  8. 修復フロー — ディザスタリカバリフローチャート

4つの翻訳(ドイツ語、スペイン語、フランス語、簡体字中国語)すべてが、すべての図と新しいマルチデバイスセクションを含む英語READMEとの完全パリティに更新されました。


1.3へのアップグレード

手動マイグレーションは不要です。アップグレード後の初回起動時に:

  • SQLiteにNULL可能なdevice_idカラムが追加されます(既存メモリはnullのまま)
  • Qdrantコレクションにdevice_idキーワードインデックスが作成されます
  • 設定にホスト名から自動解決されたdevice.idフィールドが追加されます
  • すべての新規書き込みがQdrantペイロードにコンテンツを保存します

Qdrantにコンテンツがない1.3以前のメモリは、引き続きSQLite経由で通常通り機能します。ただしSQLiteが失われた場合repairで復元できません — 書き込まれた時点でコンテンツがQdrantになかったためです。


まとめ

SQLiteインシデントからの最大の教訓: システムに2つのストアがあり、片方がサイレントに劣化する場合、オペレーターの介入なしにもう片方が穴埋めする必要があります。BHGBrain 1.3はQdrantをSQLiteのセーフティネットにし、逆も同様です。検索は自動的にフォールバックします。復元は単一ツール呼び出しです。マルチデバイス共有は両ストアが完全な全体像を持つことで機能します。

複数マシンでBHGBrainを実行している場合は1.3にアップグレードし、各デバイスでrepairを実行してください。1マシンで実行している場合もアップグレードを推奨します — Qdrant内コンテンツの変更により、次のSQLiteトラブルはデータ損失ではなく無事に済みます。


BHGBrainはオープンソース(MITライセンス)で、github.com/Big-Hat-Group-Inc/BHGBrainで利用できます。


Kevin KaminskiはBig Hat Groupのプリンシパルで、エンタープライズAIインフラ、Microsoft 365、Windows 365に注力しています。彼は職場でAIエージェントを実行するチーム向けのオープンソースツールを構築しています。