概要
OpenClawのメモリシステムは、エージェントに永続的な記憶を与える
RAG(Retrieval-Augmented Generation)パイプラインです。
SQLiteデータベースにベクトル拡張(sqlite-vec)とFTS5(全文検索)を組み合わせた
ハイブリッド検索を実現し、「覚えている」AIアシスタントを可能にしています。
アーキテクチャ概要
メモリシステム 3層アーキテクチャ:
┌─────────────────────────────────────────────┐
│ 検索レイヤー │
│ ハイブリッド検索 = ベクトル類似度 + BM25キーワード │
│ + MMR多様性再ランキング + 時間的減衰 │
├─────────────────────────────────────────────┤
│ インデックスレイヤー │
│ ファイル監視 + セッション監視 + チャンキング │
│ + Embedding生成 + 同期オーケストレーション │
├─────────────────────────────────────────────┤
│ ストレージレイヤー │
│ SQLite + sqlite-vec (ベクトル検索) │
│ + FTS5 (全文検索) │
└─────────────────────────────────────────────┘
メモリソース
src/memory/types.ts
| ソース | 内容 | デフォルト |
| memory |
MEMORY.md, memory.md, memory/ディレクトリのMarkdownファイル |
有効 |
| sessions |
過去の会話トランスクリプト(JSONLファイル) |
無効(experimental.sessionMemory: true で有効化) |
ストレージアーキテクチャ
src/memory/sqlite.ts
Node.jsの実験的組み込みモジュール node:sqlite を使用。
DBは ~/.openclaw/state/memory/{agentId}.sqlite に保存されます。
| テーブル | 用途 |
files | インデックス対象ファイルのメタデータ |
chunks | テキストチャンク(ID、パス、開始/終了行、ハッシュ、モデル、テキスト、Embedding) |
chunks_vec | ベクトル検索用仮想テーブル(vec0) |
chunks_fts | 全文検索用テーブル(FTS5) |
embedding_cache | Embeddingのキャッシュ(コンテンツハッシュ→ベクトル) |
meta | メタデータ(モデル、プロバイダ、チャンク設定) |
Embeddingプロバイダ
src/memory/embeddings.ts
| プロバイダ | モデル | 優先度(autoモード) |
| ローカル | embeddinggemma-300m-qat-Q8_0.gguf | 1(最優先) |
| OpenAI | text-embedding-3-small | 2 |
| Gemini | gemini-embedding-001 | 3 |
| Voyage | voyage-4-large | 4 |
優雅な劣化:すべてのプロバイダが利用不可の場合、システムはFTS-onlyモード
(キーワード検索のみ)に自動フォールバックします。
sqlite-vecが利用不可の場合はブルートフォースコサイン類似度計算にフォールバック。
メモリ検索は常に何らかのレベルで機能します。
すべてのEmbeddingは sanitizeAndNormalizeEmbedding() でL2正規化(単位ベクトル化)されます。
チャンキングシステム
src/memory/internal.ts:167-248
Markdownチャンキング
- 行単位で分割、デフォルトチャンクサイズ: 400トークン(約1,600文字)
- オーバーラップ: 80トークン(チャンク境界のコンテキスト保持)
- 長い行はチャンクサイズ境界でセグメントに分割
- 各チャンクにstartLine/endLineとテキストのSHA-256ハッシュを付与
セッションファイル処理
src/memory/session-files.ts:74-131
- JSONLトランスクリプトを解析し、ユーザー/アシスタントメッセージを抽出
redactSensitiveText() で機密テキストを自動編集
lineMap でコンテンツ行をオリジナルJSONL行番号にマッピング
ハイブリッド検索パイプライン
src/memory/manager.ts:203-289
検索クエリ
│
├─ [Embeddingあり] ──────────────────────────┐
│ │ │
│ ▼ ベクトル検索 ▼ キーワード検索
│ embed(query) → cosine距離 tokenize → FTS5 BM25
│ score = 1 - cosine_distance score = 1/(1+rank)
│ │ │
│ └─────────┬───────────────────────────────┘
│ ▼
│ ハイブリッドマージ
│ score = 0.7 × vectorScore + 0.3 × textScore
│ │
│ ▼ 時間的減衰適用(有効時)
│ ▼ MMR多様性再ランキング(有効時)
│ ▼ minScore=0.35 フィルタ
│ ▼ maxResults=6 制限
│ │
│ ▼ 結果返却
│
├─ [Embeddingなし(FTS-onlyモード)] ─────────┐
│ │ │
│ ▼ extractKeywords() │
│ 各キーワードで独立検索 │
│ 結果をマージ・重複排除 │
│ │ │
│ └──────────────────────────► 結果返却
ベクトル検索
src/memory/manager-search.ts:20-94
- sqlite-vec拡張あり:
vec_distance_cosine() SQL関数で高速近傍検索
- sqlite-vec拡張なし: 全チャンクに対するブルートフォースコサイン類似度計算
- スコア =
1 - cosine_distance
キーワード検索
src/memory/manager-search.ts:136-191、src/memory/hybrid.ts:33-44
- FTS5クエリを構築:トークン化 → クォート付きAND結合(例:
"API" AND "endpoint")
- BM25ランキングを利用
- スコア変換:
1/(1+rank)
ハイブリッドマージ
src/memory/hybrid.ts:51-149
- チャンクIDでベクトル結果とキーワード結果を統合
- 重み付きスコア:
score = 0.7 × vectorScore + 0.3 × textScore
- 最小スコア: 0.35(これ以下は除外)
- 最大結果数: 6
時間的減衰(Temporal Decay)
src/memory/temporal-decay.ts
人間的な記憶を模倣:「最近の記憶ほど鮮明」という特性を数学的に実装しています。
- 指数減衰:
multiplier = e^(-λ × ageInDays)
- λ =
ln(2) / halfLifeDays
- デフォルト半減期: 30日(30日でスコアが半減)
スマートタイムスタンプ抽出
| ファイルタイプ | タイムスタンプ | 減衰 |
日付付きファイル(memory/YYYY-MM-DD.md) | パスから日付を解析 | あり |
エバーグリーンファイル(MEMORY.md等) | なし(null返却) | なし |
| その他のファイル | ファイルシステムのmtime | あり |
核心設計:コアナレッジ(MEMORY.md)は永久に減衰しませんが、
日々のノートは時間とともに関連性が低下します。
MMR多様性再ランキング
src/memory/mmr.ts
Maximal Marginal Relevance(Carbonell & Goldstein, 1998)アルゴリズムにより、
検索結果の多様性を確保します。
- 計算式:
MMR = λ × relevance - (1-λ) × max_similarity_to_selected
- デフォルト λ = 0.7(関連性を多様性より優先)
- 類似度: トークン化コンテンツのJaccard係数で測定
- 反復的に「関連性が高く、かつ既選択項目と異なる」ものを選択
クエリ拡張
src/memory/query-expansion.ts
FTS-onlyモード用のクエリ前処理:
- 英語・中国語のストップワードフィルタリング(英語約120語、中国語約70語)
- CJK処理: 文字レベルのユニグラム + バイグラムでフレーズマッチング
- オプション: LLMベースのクエリ拡張(
expandQueryWithLlm())
- 例: 「that thing we discussed about the API」→
["discussed", "API"]
同期・インデックスパイプライン
src/memory/manager-sync-ops.ts
同期トリガー
| トリガー | タイミング |
| セッション開始 | 新しい会話が始まるとき |
| 検索時 | ダーティなら検索前に同期 |
| ファイルウォッチャー | Chokidarによる監視、1.5秒デバウンス |
| セッションリスナー | トランスクリプト更新(100KB or 50メッセージの差分閾値) |
| インターバルタイマー | 設定された周期での定期同期 |
同期プロセス
- ベクトル拡張の読み込み(必要時)
- メタデータチェック(モデル変更、プロバイダ変更、チャンク設定変更→フルリインデックス)
- メモリファイル: .mdファイルを列挙、変更されたファイルをインデックス(ハッシュ比較)
- セッションファイル: JSONLトランスクリプトを列挙、ダーティファイルをインデックス
- 古いインデックスパスを削除(存在しないファイル)
- 安全なリインデックス: 一時DBで新インデックスを構築 → ファイルリネームでアトミックに交換
Embeddingキャッシング
src/memory/manager-embedding-ops.ts:77-175
- コンテンツハッシュで
embedding_cache テーブルにキャッシュ
- インデックス時: まずキャッシュを確認、欠損チャンクのみEmbedding生成
- LRU退去: キャッシュが最大エントリ数を超えたら古いものから削除
バッチEmbedding
src/memory/manager-embedding-ops.ts:246-494
- OpenAI、Gemini、VoyageのバッチAPIをサポート
- バッチごとに8,000トークン上限
- レート制限時は指数バックオフでリトライ
- 2回失敗後にバッチを自動無効化し、個別Embeddingにフォールバック
パーソナルアシスタントとしてのメモリの意義
- 永続的知識ベース — MEMORY.mdとmemory/ディレクトリがセッション横断で蓄積される
- セッション想起 — 過去の会話を検索可能にし「あのAPIについて話したとき」の想起を実現
- エバーグリーン vs 時限的知識 — コア知識は永遠に保持、日付付きノートは徐々に忘却
- ワークスペーススコープ — エージェント+ワークスペースごとに独立したインデックス
- 自動同期 — ファイルウォッチャーとセッションリスナーで常に最新
- プライバシー配慮 — セッションコンテンツは編集済み、toolResult.detailsは要約から除去