メモリ

OpenClawのメモリはエージェントワークスペース内のプレーンなMarkdownです。ファイルが信頼できるソースであり、モデルはディスクに書き込まれたものだけを「記憶」します。

メモリ検索ツールはアクティブなメモリプラグイン(デフォルト: memory-core)によって提供されます。メモリプラグインを無効にするには plugins.slots.memory = "none" と設定してください。

メモリファイル(Markdown)

デフォルトのワークスペースレイアウトでは2層のメモリを使用します:

  • memory/YYYY-MM-DD.md
    • 日次ログ(追記のみ)。
    • セッション開始時に今日と昨日を読み込みます。
  • MEMORY.md(オプション)
    • キュレーションされた長期メモリ。
    • メインのプライベートセッションでのみ読み込み(グループコンテキストでは使用しない)。

これらのファイルはワークスペース(agents.defaults.workspace、デフォルト ~/.openclaw/workspace)配下にあります。完全なレイアウトについてはエージェントワークスペースを参照してください。

メモリツール

OpenClawはこれらのMarkdownファイル用に2つのエージェント向けツールを公開しています:

  • memory_search --- インデックス済みスニペットに対するセマンティック検索。
  • memory_get --- 特定のMarkdownファイル/行範囲の直接読み取り。

memory_getファイルが存在しない場合にグレースフルにデグレードするようになりました(例: 最初の書き込み前の今日の日次ログ)。組み込みマネージャーとQMDバックエンドの両方が ENOENT をスローする代わりに { text: "", path } を返すため、エージェントは「まだ何も記録されていない」を処理してtry/catchロジックなしにワークフローを続行できます。

メモリの書き込みタイミング

  • 決定事項、好み、永続的な事実は MEMORY.md へ。
  • 日々のメモと実行中のコンテキストは memory/YYYY-MM-DD.md へ。
  • 「これを覚えておいて」と言われたら書き留めます(RAMに保持しない)。
  • この領域はまだ進化中です。モデルにメモリの保存を促すと効果的です。何をすべきかはモデルが理解しています。
  • 何かを定着させたい場合は、ボットにメモリへの書き込みを依頼してください。

自動メモリフラッシュ(コンパクション前のping)

セッションが自動コンパクションに近づいているとき、OpenClawはサイレントなエージェントターンをトリガーし、コンテキストがコンパクトされる前に永続メモリを書き込むようモデルに促します。デフォルトのプロンプトではモデルが_応答してもよい_と明示していますが、通常は NO_REPLY が正しい応答であり、ユーザーにはこのターンは表示されません。

この動作は agents.defaults.compaction.memoryFlush で制御します:

{
  agents: {
    defaults: {
      compaction: {
        reserveTokensFloor: 20000,
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 4000,
          systemPrompt: "Session nearing compaction. Store durable memories now.",
          prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
        },
      },
    },
  },
}

詳細:

  • ソフト閾値: セッションのトークン推定が contextWindow - reserveTokensFloor - softThresholdTokens を超えるとフラッシュがトリガーされます。
  • デフォルトでサイレント: プロンプトに NO_REPLY が含まれるため何も配信されません。
  • 2つのプロンプト: ユーザープロンプトとシステムプロンプトがリマインダーを追加します。
  • コンパクションサイクルごとに1回のフラッシュsessions.json で追跡)。
  • ワークスペースが書き込み可能であること: セッションが workspaceAccess: "ro" または "none" でサンドボックス化されている場合、フラッシュはスキップされます。

完全なコンパクションライフサイクルについては、セッション管理+コンパクションを参照してください。

ベクトルメモリ検索

OpenClawは MEMORY.mdmemory/*.md に対して小規模なベクトルインデックスを構築でき、表現が異なっていてもセマンティッククエリで関連メモを見つけられます。

デフォルト:

  • デフォルトで有効。
  • メモリファイルの変更を監視(デバウンス付き)。
  • メモリ検索の設定は agents.defaults.memorySearch で行います(トップレベルの memorySearch ではありません)。
  • デフォルトではリモートエンベディングを使用。memorySearch.provider が未設定の場合、OpenClawが自動選択:
    1. memorySearch.local.modelPath が設定されファイルが存在する場合は local
    2. OpenAIキーが解決できる場合は openai
    3. Geminiキーが解決できる場合は gemini
    4. Voyageキーが解決できる場合は voyage
    5. Mistralキーが解決できる場合は mistral
    6. いずれも該当しない場合、設定されるまでメモリ検索は無効のまま。
  • ローカルモードではnode-llama-cppを使用し、pnpm approve-builds が必要な場合があります。
  • 利用可能な場合、sqlite-vecを使用してSQLite内のベクトル検索を高速化します。
  • memorySearch.provider = "ollama" もサポートされており、ローカル/セルフホストのOllamaエンベディング(/api/embeddings)に対応しますが、自動選択はされません。

リモートエンベディングにはエンベディングプロバイダーのAPIキーが必要です。OpenClawは認証プロファイル、models.providers.*.apiKey、または環境変数からキーを解決します。Codex OAuthはchat/completionsのみをカバーし、メモリ検索用のエンベディングには対応していません。Geminiの場合は GEMINI_API_KEY または models.providers.google.apiKey を使用してください。Voyageの場合は VOYAGE_API_KEY または models.providers.voyage.apiKey。Mistralの場合は MISTRAL_API_KEY または models.providers.mistral.apiKey。Ollamaは通常実際のAPIキーを必要としません(ローカルポリシーで必要な場合は OLLAMA_API_KEY=ollama-local のようなプレースホルダーで十分)。 カスタムOpenAI互換エンドポイントを使用する場合は、memorySearch.remote.apiKey(およびオプションで memorySearch.remote.headers)を設定してください。

QMDバックエンド(実験的)

memory.backend = "qmd" に設定すると、組み込みのSQLiteインデクサーをQMDに置き換えられます。QMDはBM25+ベクトル+リランキングを組み合わせたローカルファーストの検索サイドカーです。Markdownが信頼できるソースのままで、OpenClawは検索時にQMDをシェル経由で呼び出します。主なポイント:

前提条件

  • デフォルトでは無効。設定ごとにオプトイン(memory.backend = "qmd")。
  • QMD CLIを別途インストール(bun install -g https://github.com/tobi/qmd またはリリースを取得)し、qmd バイナリがGatewayの PATH にあることを確認。
  • QMDには拡張機能を許可するSQLiteビルドが必要(macOSでは brew install sqlite)。
  • QMDはBun+node-llama-cppで完全にローカルで動作し、初回使用時にHuggingFaceからGGUFモデルを自動ダウンロード(別途Ollamaデーモンは不要)。
  • Gatewayは XDG_CONFIG_HOMEXDG_CACHE_HOME を設定して、~/.openclaw/agents/<agentId>/qmd/ 配下の自己完結型XDGホームでQMDを実行。
  • OS対応: macOSとLinuxはBun+SQLiteをインストールすればすぐに動作。WindowsはWSL2経由が最適。

サイドカーの実行方法

  • Gatewayは ~/.openclaw/agents/<agentId>/qmd/ 配下に自己完結型QMDホーム(設定+キャッシュ+sqlite DB)を書き込みます。
  • コレクションは memory.qmd.paths(+デフォルトのワークスペースメモリファイル)から qmd collection add で作成され、起動時と設定可能な間隔(memory.qmd.update.interval、デフォルト5分)で qmd updateqmd embed が実行されます。
  • Gatewayは起動時にQMDマネージャーを初期化するため、最初の memory_search 呼び出し前でも定期更新タイマーが作動します。
  • 起動時のリフレッシュはデフォルトでバックグラウンドで実行されるため、チャット起動がブロックされません。以前のブロッキング動作を維持するには memory.qmd.update.waitForBootSync = true を設定してください。
  • 検索は memory.qmd.searchMode で実行(デフォルトは qmd search --jsonvsearchquery もサポート)。選択したモードがQMDビルドでフラグを拒否した場合、OpenClawは qmd query でリトライします。QMDが失敗するかバイナリが見つからない場合、OpenClawは自動的に組み込みSQLiteマネージャーにフォールバックし、メモリツールは動作し続けます。
  • OpenClawは現在QMDのエンベッドバッチサイズのチューニングを公開していません。バッチ動作はQMD自体で制御されます。
  • 最初の検索は遅い場合があります: QMDは最初の qmd query 実行時にローカルGGUFモデル(リランカー/クエリ拡張)をダウンロードする場合があります。
    • OpenClawはQMD実行時に XDG_CONFIG_HOME/XDG_CACHE_HOME を自動設定します。

    • モデルを手動で事前ダウンロードしたい場合(OpenClawが使用するのと同じインデックスをウォームアップ)、エージェントのXDGディレクトリで1回限りのクエリを実行してください。

      OpenClawのQMD状態は状態ディレクトリ(デフォルト ~/.openclaw)配下にあります。OpenClawが使用するのと同じXDG変数をエクスポートすることで、qmd をまったく同じインデックスに向けられます:

      # OpenClawが使用するのと同じ状態ディレクトリを選択
      STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
      
      export XDG_CONFIG_HOME="$STATE_DIR/agents/main/qmd/xdg-config"
      export XDG_CACHE_HOME="$STATE_DIR/agents/main/qmd/xdg-cache"
      
      # (オプション)インデックスのリフレッシュ+エンベディングを強制
      qmd update
      qmd embed
      
      # ウォームアップ / 初回モデルダウンロードのトリガー
      qmd query "test" -c memory-root --json >/dev/null 2>&1

設定サーフェス(memory.qmd.*

  • command(デフォルト qmd): 実行ファイルパスのオーバーライド。
  • searchMode(デフォルト search): memory_search のバックとなるQMDコマンドを選択(searchvsearchquery)。
  • includeDefaultMemory(デフォルト true): MEMORY.mdmemory/**/*.md の自動インデックス。
  • paths[]: 追加ディレクトリ/ファイルを指定(path、オプションの pattern、オプションの安定した name)。
  • sessions: セッションJSONLインデックスのオプトイン(enabledretentionDaysexportDir)。
  • update: リフレッシュ頻度とメンテナンス実行を制御(intervaldebounceMsonBootwaitForBootSyncembedIntervalcommandTimeoutMsupdateTimeoutMsembedTimeoutMs)。
  • limits: 検索結果ペイロードの制限(maxResultsmaxSnippetCharsmaxInjectedCharstimeoutMs)。
  • scope: session.sendPolicyと同じスキーマ。デフォルトはDMのみ(deny全体、allowダイレクトチャット)。グループ/チャネルでQMDヒットを表示するには緩和が必要。
    • match.keyPrefix正規化されたセッションキー(小文字化、先頭の agent:<id>: を除去)にマッチ。例: discord:channel:
    • match.rawKeyPrefix生のセッションキー(小文字化、agent:<id>: を含む)にマッチ。例: agent:main:discord:
    • レガシー: match.keyPrefix: "agent:..." は引き続き生キープレフィックスとして扱われますが、明確さのため rawKeyPrefix を推奨。
  • scope が検索を拒否した場合、OpenClawは派生した channel/chatType 付きの警告をログに記録し、空の結果をデバッグしやすくします。
  • ワークスペース外のソースからのスニペットは memory_search 結果で qmd/<collection>/<relative-path> として表示されます。memory_get はそのプレフィックスを理解し、設定されたQMDコレクションルートから読み取ります。
  • memory.qmd.sessions.enabled = true の場合、OpenClawはサニタイズされたセッショントランスクリプト(User/Assistantターン)を ~/.openclaw/agents/<id>/qmd/sessions/ 配下の専用QMDコレクションにエクスポートし、組み込みSQLiteインデックスに触れることなく memory_search で最近の会話を呼び出せます。
  • memory_search スニペットは memory.citationsauto/on の場合、Source: <path#line> フッターを含むようになりました。memory.citations = "off" に設定するとパスメタデータを内部に留めます(エージェントは引き続き memory_get 用のパスを受け取りますが、スニペットテキストからはフッターが省略され、システムプロンプトでエージェントに引用しないよう警告されます)。

memory: {
  backend: "qmd",
  citations: "auto",
  qmd: {
    includeDefaultMemory: true,
    update: { interval: "5m", debounceMs: 15000 },
    limits: { maxResults: 6, timeoutMs: 4000 },
    scope: {
      default: "deny",
      rules: [
        { action: "allow", match: { chatType: "direct" } },
        // 正規化されたセッションキープレフィックス(`agent:<id>:` を除去)。
        { action: "deny", match: { keyPrefix: "discord:channel:" } },
        // 生のセッションキープレフィックス(`agent:<id>:` を含む)。
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ]
    },
    paths: [
      { name: "docs", path: "~/notes", pattern: "**/*.md" }
    ]
  }
}

引用とフォールバック

  • memory.citations はバックエンドに関係なく適用されます(auto/on/off)。
  • qmd 実行時は status().backend = "qmd" をタグ付けし、どのエンジンが結果を提供したかを診断で表示します。QMDサブプロセスが終了したりJSON出力がパースできない場合、検索マネージャーは警告をログに記録し、QMDが回復するまで組み込みプロバイダー(既存のMarkdownエンベディング)を返します。

追加メモリパス

デフォルトのワークスペースレイアウト外のMarkdownファイルをインデックスに含めたい場合は、明示的にパスを追加:

agents: {
  defaults: {
    memorySearch: {
      extraPaths: ["../team-docs", "/srv/shared-notes/overview.md"]
    }
  }
}

注意:

  • パスは絶対パスまたはワークスペース相対パスが使用可能。
  • ディレクトリは .md ファイルを再帰的にスキャン。
  • デフォルトではMarkdownファイルのみがインデックスされます。
  • memorySearch.multimodal.enabled = true の場合、OpenClawは extraPaths 配下のサポートされる画像/音声ファイルもインデックスします。デフォルトのメモリルート(MEMORY.mdmemory.mdmemory/**/*.md)はMarkdownのみのまま。
  • シンボリックリンクは無視されます(ファイル・ディレクトリ問わず)。

マルチモーダルメモリファイル(Gemini画像+音声)

Gemini embedding 2を使用する場合、OpenClawは memorySearch.extraPaths の画像・音声ファイルをインデックスできます:

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-2-preview",
      extraPaths: ["assets/reference", "voice-notes"],
      multimodal: {
        enabled: true,
        modalities: ["image", "audio"], // or ["all"]
        maxFileBytes: 10000000
      },
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意:

  • マルチモーダルメモリは現在 gemini-embedding-2-preview でのみサポートされています。
  • マルチモーダルインデックスは memorySearch.extraPaths で発見されたファイルにのみ適用されます。
  • このフェーズでサポートされるモダリティ: 画像と音声。
  • マルチモーダルメモリが有効な間、memorySearch.fallback"none" のままにする必要があります。
  • 一致する画像/音声ファイルのバイトはインデックス時に設定されたGeminiエンベディングエンドポイントにアップロードされます。
  • サポートされる画像拡張子: .jpg.jpeg.png.webp.gif.heic.heif
  • サポートされる音声拡張子: .mp3.wav.ogg.opus.m4a.aac.flac
  • 検索クエリはテキストのままですが、Geminiはそのテキストクエリをインデックス済みの画像/音声エンベディングと比較できます。
  • memory_get は引き続きMarkdownのみを読み取ります。バイナリファイルは検索可能ですが、生のファイル内容としては返されません。

Geminiエンベディング(ネイティブ)

プロバイダーを gemini に設定してGemini Embeddings APIを直接使用:

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-001",
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意:

  • remote.baseUrl はオプション(デフォルトはGemini APIベースURL)。
  • remote.headers で必要に応じて追加ヘッダーを指定可能。
  • デフォルトモデル: gemini-embedding-001
  • gemini-embedding-2-preview もサポート: 8192トークン制限、設定可能な次元(768 / 1536 / 3072、デフォルト3072)。

Gemini Embedding 2(プレビュー)

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-2-preview",
      outputDimensionality: 3072,  // optional: 768, 1536, or 3072 (default)
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意: 再インデックスが必要です。 gemini-embedding-001(768次元)から gemini-embedding-2-preview(3072次元)に切り替えるとベクトルサイズが変わります。outputDimensionality を768、1536、3072の間で変更した場合も同様です。 OpenClawはモデルまたは次元の変更を検出すると自動的に再インデックスを行います。

カスタムOpenAI互換エンドポイント(OpenRouter、vLLM、またはプロキシ)を使用したい場合は、OpenAIプロバイダーで remote 設定を使用:

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      remote: {
        baseUrl: "https://api.example.com/v1/",
        apiKey: "YOUR_OPENAI_COMPAT_API_KEY",
        headers: { "X-Custom-Header": "value" }
      }
    }
  }
}

APIキーを設定したくない場合は、memorySearch.provider = "local" を使用するか、memorySearch.fallback = "none" を設定してください。

フォールバック:

  • memorySearch.fallbackopenaigeminivoyagemistralollamalocalnone のいずれか。
  • フォールバックプロバイダーはプライマリエンベディングプロバイダーが失敗した場合にのみ使用されます。

バッチインデックス(OpenAI+Gemini+Voyage):

  • デフォルトでは無効。大規模コーパスのインデックスには agents.defaults.memorySearch.remote.batch.enabled = true で有効化(OpenAI、Gemini、Voyage対応)。
  • デフォルトの動作はバッチ完了を待機。必要に応じて remote.batch.waitremote.batch.pollIntervalMsremote.batch.timeoutMinutes を調整。
  • remote.batch.concurrency で並行バッチジョブ数を制御(デフォルト: 2)。
  • memorySearch.provider = "openai" または "gemini" でバッチモードが適用され、対応するAPIキーを使用。
  • GeminiバッチジョブはAsync Embeddingsバッチエンドポイントを使用し、Gemini Batch APIの利用可能性が必要。

OpenAIバッチが高速かつ低コストな理由:

  • 大規模バックフィルでは、OpenAIは通常サポートする中で最も高速なオプションです。多くのエンベディングリクエストを単一のバッチジョブにまとめて送信し、OpenAIに非同期処理させることができます。
  • OpenAIはBatch APIワークロードに対して割引価格を提供しているため、大規模インデックスの実行は通常、同じリクエストを同期的に送信するより安くなります。
  • 詳細はOpenAI Batch APIのドキュメントと価格を参照:

設定例:

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      fallback: "openai",
      remote: {
        batch: { enabled: true, concurrency: 2 }
      },
      sync: { watch: true }
    }
  }
}

ツール:

  • memory_search --- スニペットをファイル+行範囲付きで返します。
  • memory_get --- パスでメモリファイルの内容を読み取ります。

ローカルモード:

  • agents.defaults.memorySearch.provider = "local" に設定。
  • agents.defaults.memorySearch.local.modelPath(GGUFまたは hf: URI)を指定。
  • オプション: リモートフォールバックを避けるため agents.defaults.memorySearch.fallback = "none" を設定。

メモリツールの動作

  • memory_searchMEMORY.mdmemory/**/*.md のMarkdownチャンク(約400トークン目標、80トークンオーバーラップ)をセマンティック検索します。スニペットテキスト(約700文字上限)、ファイルパス、行範囲、スコア、プロバイダー/モデル、local → remoteエンベディングへのフォールバック有無を返します。完全なファイルペイロードは返されません。
  • memory_get は特定のメモリMarkdownファイル(ワークスペース相対)を読み取ります。オプションで開始行とN行を指定可能。MEMORY.md / memory/ 外のパスは拒否されます。
  • 両ツールはエージェントに対して memorySearch.enabled がtrueに解決された場合のみ有効です。

インデックス対象とタイミング

  • ファイルタイプ: Markdownのみ(MEMORY.mdmemory/**/*.md)。
  • インデックスストレージ: エージェントごとのSQLite ~/.openclaw/memory/<agentId>.sqliteagents.defaults.memorySearch.store.path で設定可能、{agentId} トークンをサポート)。
  • 鮮度: MEMORY.mdmemory/ のウォッチャーがインデックスをダーティマーク(1.5秒デバウンス)。同期はセッション開始時、検索時、または一定間隔でスケジュールされ非同期実行。セッショントランスクリプトはデルタ閾値を使用してバックグラウンド同期をトリガー。
  • 再インデックストリガー: インデックスはエンベディングのプロバイダー/モデル+エンドポイントフィンガープリント+チャンキングパラメータを保存。いずれかが変更されると、OpenClawは自動的にストア全体をリセットして再インデックス。

ハイブリッド検索(BM25+ベクトル)

有効にすると、OpenClawは以下を組み合わせます:

  • ベクトル類似度(セマンティックマッチ、表現が異なっていても対応)
  • BM25キーワード関連度(ID、環境変数、コードシンボルなどの正確なトークン)

フルテキスト検索がプラットフォームで利用できない場合、OpenClawはベクトルのみの検索にフォールバックします。

ハイブリッドにする理由

ベクトル検索は「同じ意味」の発見に優れています:

  • 「Mac Studioのgatewayホスト」 vs 「gatewayを実行しているマシン」
  • 「ファイル更新のデバウンス」 vs 「書き込みごとのインデックスを避ける」

しかし、正確で情報量の多いトークンには弱い面があります:

  • ID(a828e60b3b9895a…
  • コードシンボル(memorySearch.query.hybrid
  • エラー文字列(“sqlite-vec unavailable”)

BM25(フルテキスト)はその逆で、正確なトークンに強く、言い換えに弱い。ハイブリッド検索は実用的な中間地点であり、両方の検索シグナルを活用することで、「自然言語」クエリと「干し草の中の針」クエリの両方に良好な結果をもたらします。

結果のマージ方法(現在の設計)

実装の概略:

  1. 両方から候補プールを取得:
  • ベクトル: コサイン類似度上位 maxResults * candidateMultiplier 件。
  • BM25: FTS5 BM25ランク上位 maxResults * candidateMultiplier 件(低いほど良い)。
  1. BM25ランクを0..1程度のスコアに変換:
  • textScore = 1 / (1 + max(0, bm25Rank))
  1. チャンクIDで候補を和集合にし、加重スコアを計算:
  • finalScore = vectorWeight * vectorScore + textWeight * textScore

注意:

  • vectorWeighttextWeight は設定解決時に1.0に正規化されるため、重みはパーセンテージとして機能。
  • エンベディングが利用できない場合(またはプロバイダーがゼロベクトルを返す場合)、BM25を実行してキーワードマッチを返します。
  • FTS5が作成できない場合、ベクトルのみの検索を維持(ハード障害なし)。

これは「IR理論的に完璧」ではありませんが、シンプルで高速であり、実際のメモに対して再現率/精度を改善する傾向があります。 より高度にしたい場合の一般的な次のステップは、Reciprocal Rank Fusion(RRF)またはスコア正規化(min/maxまたはzスコア)を混合前に行うことです。

後処理パイプライン

ベクトルとキーワードのスコアをマージした後、2つのオプションの後処理ステージがエージェントに到達する前に結果リストを洗練します:

Vector + Keyword → Weighted Merge → Temporal Decay → Sort → MMR → Top-K Results

両ステージともデフォルトではオフであり、独立して有効にできます。

MMRリランキング(多様性)

ハイブリッド検索が結果を返す際、複数のチャンクが類似または重複するコンテンツを含む場合があります。例えば「ホームネットワーク設定」を検索すると、異なる日次メモから同じルーター設定に言及するほぼ同一のスニペットが5件返される可能性があります。

**MMR(Maximal Marginal Relevance)**は結果をリランキングして関連性と多様性のバランスを取り、上位結果が同じ情報を繰り返すのではなくクエリの異なる側面をカバーするようにします。

動作方法:

  1. 結果は元の関連性(ベクトル+BM25加重スコア)でスコアリング。
  2. MMRが反復的に結果を選択し、次を最大化: λ × relevance − (1−λ) × max_similarity_to_selected
  3. 結果間の類似度はトークン化されたコンテンツに対するJaccardテキスト類似度で測定。

lambda パラメータがトレードオフを制御:

  • lambda = 1.0 → 純粋な関連性(多様性ペナルティなし)
  • lambda = 0.0 → 最大多様性(関連性を無視)
  • デフォルト: 0.7(バランス型、やや関連性寄り)

例 --- クエリ: “home network setup”

以下のメモリファイルがある場合:

memory/2026-02-10.md  → "Configured Omada router, set VLAN 10 for IoT devices"
memory/2026-02-08.md  → "Configured Omada router, moved IoT to VLAN 10"
memory/2026-02-05.md  → "Set up AdGuard DNS on 192.168.10.2"
memory/network.md     → "Router: Omada ER605, AdGuard: 192.168.10.2, VLAN 10: IoT"

MMRなし --- 上位3件:

1. memory/2026-02-10.md  (score: 0.92)  ← router + VLAN
2. memory/2026-02-08.md  (score: 0.89)  ← router + VLAN (near-duplicate!)
3. memory/network.md     (score: 0.85)  ← reference doc

MMRあり(λ=0.7)--- 上位3件:

1. memory/2026-02-10.md  (score: 0.92)  ← router + VLAN
2. memory/network.md     (score: 0.85)  ← reference doc (diverse!)
3. memory/2026-02-05.md  (score: 0.78)  ← AdGuard DNS (diverse!)

2月8日のほぼ重複した結果が除外され、エージェントは3つの異なる情報を取得します。

有効にすべきとき: memory_search が冗長またはほぼ重複したスニペットを返す場合、特に日次メモが日ごとに類似した情報を繰り返しやすい場合。

時間減衰(新しさブースト)

日次メモを持つエージェントは時間の経過とともに何百もの日付付きファイルを蓄積します。減衰なしでは、6ヶ月前に良く書かれたメモが同じトピックについての昨日の更新を上回ることがあります。

時間減衰は各結果の経過日数に基づいて指数的な乗数をスコアに適用し、新しいメモリが自然に上位にランクされ古いものが徐々に消えるようにします:

decayedScore = score × e^(-λ × ageInDays)

ここで λ = ln(2) / halfLifeDays

デフォルトの半減期30日では:

  • 今日のメモ: 元のスコアの100%
  • 7日前: 約84%
  • 30日前: 50%
  • 90日前: 12.5%
  • 180日前: 約1.6%

常緑ファイルは減衰されません:

  • MEMORY.md(ルートメモリファイル)
  • memory/ 内の日付なしファイル(例: memory/projects.mdmemory/network.md
  • これらは常に通常どおりランクされるべき永続的な参照情報を含んでいます。

日付付き日次ファイルmemory/YYYY-MM-DD.md)はファイル名から抽出された日付を使用します。その他のソース(例: セッショントランスクリプト)はファイル更新時刻(mtime)にフォールバックします。

例 --- クエリ: “what’s Rod’s work schedule?”

以下のメモリファイルがある場合(今日は2月10日):

memory/2025-09-15.md  → "Rod works Mon-Fri, standup at 10am, pairing at 2pm"  (148 days old)
memory/2026-02-10.md  → "Rod has standup at 14:15, 1:1 with Zeb at 14:45"    (today)
memory/2026-02-03.md  → "Rod started new team, standup moved to 14:15"        (7 days old)

減衰なし:

1. memory/2025-09-15.md  (score: 0.91)  ← best semantic match, but stale!
2. memory/2026-02-10.md  (score: 0.82)
3. memory/2026-02-03.md  (score: 0.80)

減衰あり(halfLife=30):

1. memory/2026-02-10.md  (score: 0.82 × 1.00 = 0.82)  ← today, no decay
2. memory/2026-02-03.md  (score: 0.80 × 0.85 = 0.68)  ← 7 days, mild decay
3. memory/2025-09-15.md  (score: 0.91 × 0.03 = 0.03)  ← 148 days, nearly gone

9月の古いメモは最高の生セマンティックマッチスコアにもかかわらず最下位に下がります。

有効にすべきとき: エージェントに数ヶ月分の日次メモがあり、古い陳腐な情報が最近のコンテキストを上回る場合。半減期30日は日次メモの多いワークフローに適しています。古いメモを頻繁に参照する場合は増やしてください(例: 90日)。

設定

両機能とも memorySearch.query.hybrid 配下で設定:

agents: {
  defaults: {
    memorySearch: {
      query: {
        hybrid: {
          enabled: true,
          vectorWeight: 0.7,
          textWeight: 0.3,
          candidateMultiplier: 4,
          // 多様性: 冗長な結果を削減
          mmr: {
            enabled: true,    // default: false
            lambda: 0.7       // 0 = max diversity, 1 = max relevance
          },
          // 新しさ: より新しいメモリをブースト
          temporalDecay: {
            enabled: true,    // default: false
            halfLifeDays: 30  // score halves every 30 days
          }
        }
      }
    }
  }
}

どちらの機能も独立して有効にできます:

  • MMRのみ --- 類似メモが多いが経過日数が問題にならない場合に有用。
  • 時間減衰のみ --- 新しさが重要だが結果がすでに多様な場合に有用。
  • 両方 --- 大規模で長期間の日次メモ履歴を持つエージェントに推奨。

エンベディングキャッシュ

OpenClawはチャンクエンベディングをSQLiteにキャッシュでき、再インデックスや頻繁な更新(特にセッショントランスクリプト)で変更されていないテキストの再エンベッドを回避します。

設定:

agents: {
  defaults: {
    memorySearch: {
      cache: {
        enabled: true,
        maxEntries: 50000
      }
    }
  }
}

セッションメモリ検索(実験的)

オプションでセッショントランスクリプトをインデックスし、memory_search で表示できます。実験的フラグの背後で制御されています。

agents: {
  defaults: {
    memorySearch: {
      experimental: { sessionMemory: true },
      sources: ["memory", "sessions"]
    }
  }
}

注意:

  • セッションインデックスはオプトイン(デフォルトではオフ)。
  • セッション更新はデバウンスされ、デルタ閾値を超えると非同期でインデックスされます(ベストエフォート)。
  • memory_search はインデックスをブロックしません。バックグラウンド同期が完了するまで結果がやや古い場合があります。
  • 結果にはスニペットのみ含まれます。memory_get はメモリファイルに限定されたまま。
  • セッションインデックスはエージェントごとに分離されています(そのエージェントのセッションログのみがインデックスされます)。
  • セッションログはディスク上に存在します(~/.openclaw/agents/<agentId>/sessions/*.jsonl)。ファイルシステムアクセスを持つプロセス/ユーザーは読み取り可能なため、ディスクアクセスを信頼境界として扱ってください。厳密な分離が必要な場合は、エージェントを別のOSユーザーまたはホストで実行してください。

デルタ閾値(デフォルト値を表示):

agents: {
  defaults: {
    memorySearch: {
      sync: {
        sessions: {
          deltaBytes: 100000,   // ~100 KB
          deltaMessages: 50     // JSONL lines
        }
      }
    }
  }
}

SQLiteベクトル高速化(sqlite-vec)

sqlite-vec拡張機能が利用可能な場合、OpenClawはエンベディングをSQLite仮想テーブル(vec0)に格納し、データベース内でベクトル距離クエリを実行します。すべてのエンベディングをJSに読み込むことなく検索を高速に保ちます。

設定(オプション):

agents: {
  defaults: {
    memorySearch: {
      store: {
        vector: {
          enabled: true,
          extensionPath: "/path/to/sqlite-vec"
        }
      }
    }
  }
}

注意:

  • enabled のデフォルトはtrue。無効にすると、格納されたエンベディングに対するインプロセスのコサイン類似度にフォールバック。
  • sqlite-vec拡張機能が見つからないかロードに失敗した場合、OpenClawはエラーをログに記録しJSフォールバックで続行(ベクトルテーブルなし)。
  • extensionPath はバンドルされたsqlite-vecパスをオーバーライド(カスタムビルドや非標準インストール場所に有用)。

ローカルエンベディングの自動ダウンロード

  • デフォルトのローカルエンベディングモデル: hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf(約0.6 GB)。
  • memorySearch.provider = "local" の場合、node-llama-cppmodelPath を解決。GGUFが見つからない場合はキャッシュ(または local.modelCacheDir)に自動ダウンロードし、ロードします。ダウンロードはリトライ時に再開されます。
  • ネイティブビルド要件: pnpm approve-builds を実行し、node-llama-cpp を選択、その後 pnpm rebuild node-llama-cpp
  • フォールバック: ローカル設定が失敗し memorySearch.fallback = "openai" の場合、自動的にリモートエンベディング(openai/text-embedding-3-small、オーバーライドされない限り)に切り替わり、理由が記録されます。

カスタムOpenAI互換エンドポイントの例

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      remote: {
        baseUrl: "https://api.example.com/v1/",
        apiKey: "YOUR_REMOTE_API_KEY",
        headers: {
          "X-Organization": "org-id",
          "X-Project": "project-id"
        }
      }
    }
  }
}

注意:

  • remote.*models.providers.openai.* よりも優先されます。
  • remote.headers はOpenAIヘッダーとマージされ、キーが衝突した場合はremoteが優先。remote.headers を省略するとOpenAIのデフォルトが使用されます。