記憶
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.md 和 memory/*.md 上建立小型向量索引,讓語意查詢即使在用詞不同時也能找到相關筆記。
預設值:
- 預設啟用。
- 監視記憶檔案的變更(有 debounce)。
- 在
agents.defaults.memorySearch下設定記憶搜尋(不是頂層的memorySearch)。 - 預設使用遠端嵌入向量。如果未設定
memorySearch.provider,OpenClaw 會自動選擇:- 如果設定了
memorySearch.local.modelPath且檔案存在,使用local。 - 如果有 OpenAI key,使用
openai。 - 如果有 Gemini key,使用
gemini。 - 如果有 Voyage key,使用
voyage。 - 如果有 Mistral key,使用
mistral。 - 否則記憶搜尋保持停用,直到完成設定。
- 如果設定了
- 本地模式使用 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_KEY 或 models.providers.google.apiKey。Voyage 請用 VOYAGE_API_KEY 或 models.providers.voyage.apiKey。Mistral 請用 MISTRAL_API_KEY 或 models.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_HOME和XDG_CACHE_HOME。 - 作業系統支援:macOS 和 Linux 安裝好 Bun + SQLite 後即可使用。Windows 建議透過 WSL2。
附屬程式的執行方式
- Gateway 在
~/.openclaw/agents/<agentId>/qmd/下建立獨立的 QMD home(設定 + 快取 + sqlite DB)。 - 透過
qmd collection add從memory.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;也支援vsearch和query)。如果選定的模式在你的 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_search(search、vsearch、query)。includeDefaultMemory(預設true):自動索引MEMORY.md+memory/**/*.md。paths[]:新增額外的目錄/檔案(path,可選的pattern,可選的穩定name)。sessions:選擇加入 session JSONL 索引(enabled、retentionDays、exportDir)。update:控制重新整理頻率和維護執行: (interval、debounceMs、onBoot、waitForBootSync、embedInterval、commandTimeoutMs、updateTimeoutMs、embedTimeoutMs)。limits:限制召回 payload(maxResults、maxSnippetChars、maxInjectedChars、timeoutMs)。scope:與session.sendPolicy相同的 schema。 預設為僅限私訊(denyall,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.md、memory.md、memory/**/*.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可以是openai、gemini、voyage、mistral、ollama、local或none。- 回退 provider 只在主要嵌入 provider 失敗時使用。
批次索引(OpenAI + Gemini + Voyage):
- 預設停用。設定
agents.defaults.memorySearch.remote.batch.enabled = true可為大型語料庫啟用(OpenAI、Gemini 和 Voyage)。 - 預設行為會等待批次完成;視需要調整
remote.batch.wait、remote.batch.pollIntervalMs和remote.batch.timeoutMinutes。 - 設定
remote.batch.concurrency控制並行提交的批次作業數量(預設:2)。 - 批次模式在
memorySearch.provider = "openai"或"gemini"時適用,使用對應的 API key。 - Gemini 批次作業使用非同步嵌入批次端點,需要 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_search對MEMORY.md+memory/**/*.md的 Markdown 區段(目標 ~400 token,80 token 重疊)進行語意搜尋。回傳片段文字(上限 ~700 字元)、檔案路徑、行範圍、分數、provider/模型,以及是否從本地回退到遠端嵌入向量。不回傳完整檔案內容。memory_get讀取特定的記憶 Markdown 檔案(工作區相對路徑),可選指定起始行和行數。MEMORY.md/memory/以外的路徑會被拒絕。- 兩個工具只有在
memorySearch.enabled對該 agent 為 true 時才啟用。
索引什麼(以及何時索引)
- 檔案類型:僅 Markdown(
MEMORY.md、memory/**/*.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(
a828e60、b3b9895a…) - 程式碼符號(
memorySearch.query.hybrid) - 錯誤訊息(“sqlite-vec unavailable”)
BM25(全文搜尋)正好相反:精確 token 很強,語義轉述較弱。 混合搜尋是務實的中間路線:同時使用兩種檢索訊號,讓「自然語言」查詢和「大海撈針」查詢都能得到好結果。
如何合併結果(目前的設計)
實作概述:
- 從兩邊擷取候選池:
- 向量:前
maxResults * candidateMultiplier個,按餘弦相似度排序。 - BM25:前
maxResults * candidateMultiplier個,按 FTS5 BM25 排名(越低越好)。
- 將 BM25 排名轉為 0..1 的分數:
textScore = 1 / (1 + max(0, bm25Rank))
- 依 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) 會重新排序結果,平衡相關性與多樣性,確保排名靠前的結果涵蓋查詢的不同面向,而非重複相同資訊。
運作方式:
- 結果按原始相關性評分(向量 + BM25 加權分數)。
- MMR 迭代選擇最大化以下值的結果:
λ × relevance − (1−λ) × max_similarity_to_selected。 - 結果間的相似度使用 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.md、memory/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 預設值。