記憶

OpenClaw 的記憶就是 agent 工作區裡的純 Markdown 檔案。檔案是唯一的真實來源——模型「記得」的只有寫到磁碟上的東西。

記憶搜尋工具由啟用的記憶外掛提供(預設:memory-core)。用 plugins.slots.memory = "none" 停用記憶外掛。

記憶檔案(Markdown)

預設的工作區結構使用兩層記憶:

  • memory/YYYY-MM-DD.md
    • 每日日誌(只追加)。
    • 在 session 開始時讀取今天和昨天的記錄。
  • MEMORY.md(可選)
    • 經過整理的長期記憶。
    • 只在主要的私人 session 載入(不在群組情境中使用)。

這些檔案放在工作區目錄下(agents.defaults.workspace,預設 ~/.openclaw/workspace)。請參閱 Agent workspace 瞭解完整結構。

記憶工具

OpenClaw 為 agent 提供兩個操作 Markdown 記憶檔案的工具:

  • memory_search — 對已索引的片段進行語意搜尋。
  • memory_get — 針對特定 Markdown 檔案/行範圍進行定向讀取。

memory_get 現在在檔案不存在時會優雅降級(例如在第一次寫入前讀取今天的日誌)。內建管理器和 QMD 後端都會回傳 { text: "", path } 而非拋出 ENOENT,讓 agent 可以處理「還沒有記錄」的情況並繼續工作流程,不需要把工具呼叫包在 try/catch 裡。

何時寫入記憶

  • 決策、偏好和持久性事實寫到 MEMORY.md
  • 日常筆記和進行中的上下文寫到 memory/YYYY-MM-DD.md
  • 如果有人說「記住這個」,就寫下來(不要只放在記憶體裡)。
  • 這個領域仍在演進中。提醒模型儲存記憶是有幫助的——它會知道該怎麼做。
  • 如果你希望某件事被記住,叫機器人把它寫進記憶。

自動記憶刷寫(壓縮前提醒)

當 session 接近自動壓縮時,OpenClaw 會觸發一個靜默的 agentic 回合,提醒模型在上下文被壓縮之前寫入持久性記憶。預設的提示詞明確說模型_可以回覆_,但通常 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.",
        },
      },
    },
  },
}

細節:

  • 軟門檻:當 session token 估算超過 contextWindow - reserveTokensFloor - softThresholdTokens 時觸發刷寫。
  • 預設靜默:提示詞包含 NO_REPLY,不會傳送任何內容。
  • 兩段提示:一個使用者提示加上一個系統提示詞附加提醒。
  • 每個壓縮週期只刷寫一次(在 sessions.json 中追蹤)。
  • 工作區必須可寫:如果 session 在沙盒中以 workspaceAccess: "ro""none" 執行,刷寫會被跳過。

完整的壓縮生命週期請參閱 Session management + compaction

向量記憶搜尋

OpenClaw 可以在 MEMORY.mdmemory/*.md 上建立小型向量索引,讓語意查詢即使在用詞不同時也能找到相關筆記。

預設值:

  • 預設啟用。
  • 監視記憶檔案的變更(有 debounce)。
  • agents.defaults.memorySearch 下設定記憶搜尋(不是頂層的 memorySearch)。
  • 預設使用遠端嵌入向量。如果未設定 memorySearch.provider,OpenClaw 會自動選擇:
    1. 如果設定了 memorySearch.local.modelPath 且檔案存在,使用 local
    2. 如果有 OpenAI key,使用 openai
    3. 如果有 Gemini key,使用 gemini
    4. 如果有 Voyage key,使用 voyage
    5. 如果有 Mistral key,使用 mistral
    6. 否則記憶搜尋保持停用,直到完成設定。
  • 本地模式使用 node-llama-cpp,可能需要 pnpm approve-builds
  • 可用 sqlite-vec 加速 SQLite 內的向量搜尋。
  • 也支援 memorySearch.provider = "ollama" 做本地/自架的 Ollama 嵌入向量(/api/embeddings),但不會被自動選取。

遠端嵌入向量需要嵌入 provider 的 API key。OpenClaw 從驗證設定檔、models.providers.*.apiKey 或環境變數解析 key。Codex OAuth 只涵蓋 chat/completions,無法用於記憶搜尋的嵌入向量。Gemini 請用 GEMINI_API_KEYmodels.providers.google.apiKey。Voyage 請用 VOYAGE_API_KEYmodels.providers.voyage.apiKey。Mistral 請用 MISTRAL_API_KEYmodels.providers.mistral.apiKey。Ollama 通常不需要真正的 API key(本地原則需要時用 OLLAMA_API_KEY=ollama-local 這樣的佔位符就夠了)。 使用自訂 OpenAI 相容端點時,設定 memorySearch.remote.apiKey(以及可選的 memorySearch.remote.headers)。

QMD 後端(實驗性)

設定 memory.backend = "qmd" 可將內建的 SQLite 索引器替換為 QMD:一個本地優先的搜尋附屬程式,結合了 BM25 + 向量 + 重排序。Markdown 仍然是真實來源;OpenClaw 透過 shell 呼叫 QMD 進行檢索。重點如下:

前置條件

  • 預設停用。在設定中選擇啟用(memory.backend = "qmd")。
  • 需要另外安裝 QMD CLI(bun install -g https://github.com/tobi/qmd 或下載 release),確保 qmd 執行檔在 gateway 的 PATH 上。
  • QMD 需要允許擴充套件的 SQLite 建置(macOS 上用 brew install sqlite)。
  • QMD 透過 Bun + node-llama-cpp 完全在本地執行,首次使用時會從 HuggingFace 自動下載 GGUF 模型(不需要另外跑 Ollama daemon)。
  • Gateway 在 ~/.openclaw/agents/<agentId>/qmd/ 下以獨立的 XDG home 執行 QMD,設定 XDG_CONFIG_HOMEXDG_CACHE_HOME
  • 作業系統支援:macOS 和 Linux 安裝好 Bun + SQLite 後即可使用。Windows 建議透過 WSL2。

附屬程式的執行方式

  • Gateway 在 ~/.openclaw/agents/<agentId>/qmd/ 下建立獨立的 QMD home(設定 + 快取 + sqlite DB)。
  • 透過 qmd collection addmemory.qmd.paths(加上預設的工作區記憶檔案)建立集合,然後在啟動時和可設定的間隔(memory.qmd.update.interval,預設 5 分鐘)執行 qmd update + qmd embed
  • Gateway 現在在啟動時就初始化 QMD 管理器,所以定期更新的計時器在第一次 memory_search 呼叫前就已啟動。
  • 啟動時的重新整理預設在背景執行,不會阻塞聊天啟動;設定 memory.qmd.update.waitForBootSync = true 可保持先前的阻塞行為。
  • 搜尋透過 memory.qmd.searchMode 執行(預設 qmd search --json;也支援 vsearchquery)。如果選定的模式在你的 QMD 版本上不接受某些參數,OpenClaw 會用 qmd query 重試。QMD 失敗或執行檔缺失時,OpenClaw 自動回退到內建的 SQLite 管理器,確保記憶工具繼續運作。
  • OpenClaw 目前不提供 QMD embed 批次大小的調整介面;批次行為由 QMD 自行控制。
  • 首次搜尋可能較慢:QMD 可能在第一次 qmd query 時下載本地 GGUF 模型(重排序器/查詢擴展)。
    • OpenClaw 在執行 QMD 時會自動設定 XDG_CONFIG_HOME/XDG_CACHE_HOME

    • 如果你想手動預先下載模型(並預熱 OpenClaw 使用的同一個索引),可以用 agent 的 XDG 目錄執行一次性查詢。

      OpenClaw 的 QMD 狀態放在你的狀態目錄(預設 ~/.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):選擇哪個 QMD 指令支撐 memory_searchsearchvsearchquery)。
  • includeDefaultMemory(預設 true):自動索引 MEMORY.md + memory/**/*.md
  • paths[]:新增額外的目錄/檔案(path,可選的 pattern,可選的穩定 name)。
  • sessions:選擇加入 session JSONL 索引(enabledretentionDaysexportDir)。
  • update:控制重新整理頻率和維護執行: (intervaldebounceMsonBootwaitForBootSyncembedIntervalcommandTimeoutMsupdateTimeoutMsembedTimeoutMs)。
  • limits:限制召回 payload(maxResultsmaxSnippetCharsmaxInjectedCharstimeoutMs)。
  • scope:與 session.sendPolicy 相同的 schema。 預設為僅限私訊(deny all,allow 直接聊天);放寬它可在群組/頻道中顯示 QMD 結果。
    • match.keyPrefix 匹配正規化的 session key(小寫,去除前導的 agent:<id>:)。範例:discord:channel:
    • match.rawKeyPrefix 匹配原始 session key(小寫),包含 agent:<id>:。範例:agent:main:discord:
    • 相容:match.keyPrefix: "agent:..." 仍被視為原始 key 前綴,但建議改用 rawKeyPrefix 以求明確。
  • scope 拒絕搜尋時,OpenClaw 會記錄帶有 channel/chatType 的警告,方便除錯空結果。
  • 來自工作區外部的片段會在 memory_search 結果中顯示為 qmd/<collection>/<relative-path>memory_get 能理解這個前綴,從設定的 QMD 集合根目錄讀取。
  • memory.qmd.sessions.enabled = true 時,OpenClaw 會將清理過的 session 對話記錄(User/Assistant 回合)匯出到 ~/.openclaw/agents/<id>/qmd/sessions/ 下的專用 QMD 集合中,讓 memory_search 能召回最近的對話,無需觸及內建的 SQLite 索引。
  • memory_search 片段現在會在 memory.citations 設為 auto/on 時包含 Source: <path#line> 頁尾;設定 memory.citations = "off" 可讓路徑中繼資料保持內部使用(agent 仍然接收路徑供 memory_get 使用,但片段文字省略頁尾,系統提示詞會警告 agent 不要引用它)。

範例

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" } },
        // 正規化的 session key 前綴(去除 `agent:<id>:`)。
        { action: "deny", match: { keyPrefix: "discord:channel:" } },
        // 原始 session key 前綴(包含 `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 輸出無法解析,搜尋管理器會記錄警告並回退到內建 provider(現有的 Markdown 嵌入向量),直到 QMD 恢復。

額外的記憶路徑

如果你想索引預設工作區結構以外的 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。
  • Symlink 會被忽略(無論是檔案或目錄)。

多模態記憶檔案(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"], // 或 ["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 嵌入向量(原生)

將 provider 設為 gemini 以直接使用 Gemini 嵌入 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 token 上限,可設定維度(768 / 1536 / 3072,預設 3072)。

Gemini Embedding 2(預覽版)

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-2-preview",
      outputDimensionality: 3072,  // 可選:768、1536 或 3072(預設)
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意:需要重新索引。gemini-embedding-001(768 維度)切換到 gemini-embedding-2-preview(3072 維度)會改變向量大小。在 768、1536 和 3072 之間變更 outputDimensionality 也是一樣。 OpenClaw 偵測到模型或維度變更時會自動重新索引。

如果你想使用自訂 OpenAI 相容端點(OpenRouter、vLLM 或代理),可以搭配 OpenAI provider 使用 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 key,使用 memorySearch.provider = "local" 或設定 memorySearch.fallback = "none"

回退機制:

  • memorySearch.fallback 可以是 openaigeminivoyagemistralollamalocalnone
  • 回退 provider 只在主要嵌入 provider 失敗時使用。

批次索引(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 key。
  • Gemini 批次作業使用非同步嵌入批次端點,需要 Gemini Batch API 可用。

OpenAI 批次為什麼又快又便宜:

設定範例:

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.md + memory/**/*.md 的 Markdown 區段(目標 ~400 token,80 token 重疊)進行語意搜尋。回傳片段文字(上限 ~700 字元)、檔案路徑、行範圍、分數、provider/模型,以及是否從本地回退到遠端嵌入向量。不回傳完整檔案內容。
  • memory_get 讀取特定的記憶 Markdown 檔案(工作區相對路徑),可選指定起始行和行數。MEMORY.md / memory/ 以外的路徑會被拒絕。
  • 兩個工具只有在 memorySearch.enabled 對該 agent 為 true 時才啟用。

索引什麼(以及何時索引)

  • 檔案類型:僅 Markdown(MEMORY.mdmemory/**/*.md)。
  • 索引儲存:每個 agent 的 SQLite,位於 ~/.openclaw/memory/<agentId>.sqlite(可透過 agents.defaults.memorySearch.store.path 設定,支援 {agentId} token)。
  • 新鮮度:watcher 監視 MEMORY.md + memory/ 並標記索引為 dirty(debounce 1.5 秒)。同步在 session 開始時、搜尋時或定期間隔觸發,非同步執行。Session 對話記錄使用差量門檻觸發背景同步。
  • 重新索引觸發:索引儲存嵌入provider/模型 + 端點指紋 + 分段參數。如果任何一項改變,OpenClaw 會自動重設並重新索引整個儲存區。

混合搜尋(BM25 + 向量)

啟用時,OpenClaw 結合:

  • 向量相似度(語意匹配,用詞可以不同)
  • BM25 關鍵字相關性(精確 token,如 ID、環境變數、程式碼符號)

如果你的平台不支援全文搜尋,OpenClaw 會回退到純向量搜尋。

為什麼要混合?

向量搜尋擅長「這兩個意思一樣」的場景:

  • “Mac Studio gateway host” vs “the machine running the gateway”
  • “debounce file updates” vs “avoid indexing on every write”

但面對精確、高訊號的 token 時可能較弱:

  • ID(a828e60b3b9895a…
  • 程式碼符號(memorySearch.query.hybrid
  • 錯誤訊息(“sqlite-vec unavailable”)

BM25(全文搜尋)正好相反:精確 token 很強,語義轉述較弱。 混合搜尋是務實的中間路線:同時使用兩種檢索訊號,讓「自然語言」查詢和「大海撈針」查詢都能得到好結果。

如何合併結果(目前的設計)

實作概述:

  1. 從兩邊擷取候選池:
  • 向量:前 maxResults * candidateMultiplier 個,按餘弦相似度排序。
  • BM25:前 maxResults * candidateMultiplier 個,按 FTS5 BM25 排名(越低越好)。
  1. 將 BM25 排名轉為 0..1 的分數:
  • textScore = 1 / (1 + max(0, bm25Rank))
  1. 依 chunk id 聯集候選並計算加權分數:
  • finalScore = vectorWeight * vectorScore + textWeight * textScore

注意事項:

  • vectorWeight + textWeight 在設定解析時會正規化為 1.0,行為上像百分比。
  • 如果嵌入向量不可用(或 provider 回傳零向量),仍會執行 BM25 並回傳關鍵字匹配結果。
  • 如果無法建立 FTS5,保持純向量搜尋(不會硬性失敗)。

這不是「資訊檢索理論上完美的」做法,但簡單、快速,在實際筆記上確實能改善召回率/精確度。如果之後要更精進,常見的下一步是 Reciprocal Rank Fusion (RRF) 或分數正規化(min/max 或 z-score)後再混合。

後處理管線

合併向量與關鍵字分數後,兩個可選的後處理階段會在結果送達 agent 前進一步精煉:

向量 + 關鍵字 → 加權合併 → 時間衰減 → 排序 → MMR → Top-K 結果

兩個階段預設都關閉,可獨立啟用。

MMR 重排序(多樣性)

當混合搜尋回傳結果時,多個區段可能包含相似或重疊的內容。例如,搜尋「home network setup」可能回傳五個幾乎相同的片段,來自不同的每日筆記,全都提到相同的路由器設定。

MMR(Maximal Marginal Relevance) 會重新排序結果,平衡相關性與多樣性,確保排名靠前的結果涵蓋查詢的不同面向,而非重複相同資訊。

運作方式:

  1. 結果按原始相關性評分(向量 + BM25 加權分數)。
  2. MMR 迭代選擇最大化以下值的結果:λ × relevance − (1−λ) × max_similarity_to_selected
  3. 結果間的相似度使用 tokenized 內容的 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)  ← 路由器 + VLAN
2. memory/2026-02-08.md  (score: 0.89)  ← 路由器 + VLAN(幾乎重複!)
3. memory/network.md     (score: 0.85)  ← 參考文件

使用 MMR(λ=0.7) — 前 3 個結果:

1. memory/2026-02-10.md  (score: 0.92)  ← 路由器 + VLAN
2. memory/network.md     (score: 0.85)  ← 參考文件(多樣!)
3. memory/2026-02-05.md  (score: 0.78)  ← AdGuard DNS(多樣!)

2 月 8 日的近似重複被淘汰,agent 得到三個不同面向的資訊。

適合啟用的時機: 當你注意到 memory_search 回傳冗餘或近似重複的片段,尤其是每日筆記經常跨天重複類似資訊時。

時間衰減(近期加權)

長期使用每日筆記的 agent 會累積數百個帶日期的檔案。沒有衰減的話,六個月前一篇寫得很好的筆記可能排在昨天同一主題的更新之上。

時間衰減根據每個結果的年齡套用指數乘數,讓近期記憶自然排名更高,而舊記憶逐漸淡出:

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)使用從檔名擷取的日期。 其他來源(如 session 對話記錄)回退到檔案修改時間(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 天前)
memory/2026-02-10.md  → "Rod has standup at 14:15, 1:1 with Zeb at 14:45"    (今天)
memory/2026-02-03.md  → "Rod started new team, standup moved to 14:15"        (7 天前)

不用衰減:

1. memory/2025-09-15.md  (score: 0.91)  ← 語意匹配最佳,但已過時!
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)  ← 今天,無衰減
2. memory/2026-02-03.md  (score: 0.80 × 0.85 = 0.68)  ← 7 天,輕微衰減
3. memory/2025-09-15.md  (score: 0.91 × 0.03 = 0.03)  ← 148 天,幾乎歸零

過時的九月筆記掉到最底部,儘管它有最好的原始語意匹配分數。

適合啟用的時機: 當你的 agent 有數月的每日筆記,且舊的過時資訊排名高於近期上下文時。半衰期 30 天適合以每日筆記為主的工作流程;如果你經常參考較舊的筆記,可以加大(例如 90 天)。

設定

兩個功能都在 memorySearch.query.hybrid 下設定:

agents: {
  defaults: {
    memorySearch: {
      query: {
        hybrid: {
          enabled: true,
          vectorWeight: 0.7,
          textWeight: 0.3,
          candidateMultiplier: 4,
          // 多樣性:減少冗餘結果
          mmr: {
            enabled: true,    // 預設:false
            lambda: 0.7       // 0 = 最大多樣性,1 = 最大相關性
          },
          // 近期加權:提升較新的記憶
          temporalDecay: {
            enabled: true,    // 預設:false
            halfLifeDays: 30  // 分數每 30 天減半
          }
        }
      }
    }
  }
}

你可以獨立啟用任一功能:

  • 僅 MMR — 當有很多相似筆記但時間不重要時。
  • 僅時間衰減 — 當近期性很重要但結果已經夠多樣時。
  • 兩者皆啟用 — 建議用於有大量、長期每日筆記歷史的 agent。

嵌入快取

OpenClaw 可以在 SQLite 中快取區段嵌入向量,避免重新索引和頻繁更新(尤其是 session 對話記錄)時重新嵌入未變更的文字。

設定:

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

Session 記憶搜尋(實驗性)

你可以選擇索引 session 對話記錄並透過 memory_search 呈現。此功能受實驗性旗標控制。

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

注意事項:

  • Session 索引是選擇加入的(預設關閉)。
  • Session 更新有 debounce 且非同步索引,超過差量門檻後才處理(盡力而為)。
  • memory_search 不會阻塞等待索引;在背景同步完成前,結果可能略有延遲。
  • 結果仍然只包含片段;memory_get 仍限於記憶檔案。
  • Session 索引按 agent 隔離(只索引該 agent 的 session 記錄)。
  • Session 記錄存放在磁碟上(~/.openclaw/agents/<agentId>/sessions/*.jsonl)。任何有檔案系統存取權限的程式/使用者都能讀取,因此磁碟存取就是信任邊界。要更嚴格的隔離,可在獨立的 OS 使用者或主機上執行 agent。

差量門檻(顯示預設值):

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

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-cpp 會解析 modelPath;如果 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 標頭合併;key 衝突時 remote 優先。省略 remote.headers 則使用 OpenAI 預設值。