iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0

開場故事

如果你有整理過一個超亂的資料夾,你大概會懂一種很矛盾的痛苦。

裡面不是沒東西,而是東西太多了。截圖、草稿、舊版本、會議記錄、臨時備忘、還有你根本忘了自己為什麼存下來的檔案,全都混在一起。你明明知道某一天會回來找它,但等真的回來時,反而先被資料淹死。

Agent 的記憶也很像這件事。

OpenClaw 不是沒有記憶,而是它非常清楚一件事:

  • 不是所有東西都值得永久記住
  • 不是所有回憶都應該被塞進同一個地方
  • 真正重要的是,下一次需要時能不能剛好找得到

第 7 天我想看的,就是這個問題:

  • OpenClaw 到底把記憶放在哪裡?
  • 哪些內容會變成長期記憶,哪些只會留在日常筆記?
  • memory_searchmemory_get 是怎麼把舊東西撈回來的?
  • 為什麼 compaction 前還要先跑一次 memory flush?
  • OpenClaw 怎麼避免「記太多」反而把自己搞亂?

這一篇我想把它寫成「OpenClaw 怎麼學會只記對的東西」。

今天要解的問題

  • OpenClaw 的記憶分成哪些層?
  • MEMORY.mdmemory/YYYY-MM-DD.mdDREAMS.md 各自扮演什麼角色?
  • memory_search 為什麼不是單純全文搜尋?
  • /new/reset 觸發時,日常記憶是怎麼被寫下來的?
  • compaction 前的 memory flush 在保護什麼?
  • 什麼樣的內容才有機會升級成真正的長期記憶?

架構總覽

OpenClaw 的記憶系統,我會把它想成三層加一個保險機制。

第一層是「長期記憶」:MEMORY.md
這層放的是能跨 session 保留下來的東西,例如偏好、穩定決策、長期規則。它不是逐字轉錄,而是整理過的精華版。

第二層是「日常筆記」:memory/YYYY-MM-DD.md,或者帶 slug 的 memory/YYYY-MM-DD-<slug>.md
這層比較像工作草稿,記今天發生什麼、做過什麼、看到了什麼。它可以很雜,但要可回收。

第三層是「召回機制」:memory_searchmemory_get、以及可選的 active-memory
這些工具負責把過去的內容在對的時候拉回來,不是讓模型一直憑空猜。

最後還有一個保險機制:compaction 前的 memory flush。
當上下文快爆了,OpenClaw 會先提醒系統把重要東西寫進記憶檔,再去做摘要壓縮,避免剛好把還沒落地的重點一起壓掉。

如果畫成流程,大概是這樣:

日常互動
  -> 需要時查 memory_search / memory_get
  -> /new 或 /reset 時寫入 daily note
  -> 長期有價值的內容經 dreaming promotion 進 MEMORY.md
  -> compaction 前先 memory flush,避免資訊被壓縮吞掉

所以 OpenClaw 的思路不是「記得越多越強」。
它比較像是在做資料分流:

  • 重要且穩定的,放長期
  • 還在變動的,放日常
  • 需要時再召回
  • 快要丟失時先救援

這個設計很務實,因為真正的問題從來不是「能不能記」,而是「記了之後會不會害自己更亂」。

原始碼節錄

先看最核心的文件辨識。OpenClaw 對 root memory 的態度很直接:MEMORY.md 是正規答案,舊的 memory.md 只算歷史包袱。

📄 原始碼:src/plugin-sdk/memory-host-core.ts:51-66

/** Canonical root memory file name used by current workspaces. */
const CANONICAL_ROOT_MEMORY_FILENAME = "MEMORY.md";
/** Legacy root memory file name kept out of auxiliary scans. */
const LEGACY_ROOT_MEMORY_FILENAME = "memory.md";

/** Lists public memory artifacts for one workspace, including notes and event logs. */
async function listMemoryWorkspacePublicArtifacts(params) {
	const artifacts = [];
	if (new Set((await fs.readdir(params.workspaceDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isFile()).map((entry) => entry.name)).has("MEMORY.md")) {
		const absolutePath = path.join(params.workspaceDir, "MEMORY.md");
		artifacts.push({
			kind: "memory-root",
			workspaceDir: params.workspaceDir,
			relativePath: "MEMORY.md",
			absolutePath,
			agentIds: [...params.agentIds],
			contentType: "markdown"
		});
	}
	const memoryDir = path.join(params.workspaceDir, "memory");
	for (const absolutePath of await listMarkdownFilesRecursive(memoryDir)) {
		const relativePath = path.relative(params.workspaceDir, absolutePath).replace(/\\/g, "/");
		artifacts.push({
			kind: relativePath.startsWith("memory/dreaming/") ? "dream-report" : "daily-note",
			workspaceDir: params.workspaceDir,
			relativePath,
			absolutePath,
			agentIds: [...params.agentIds],
			contentType: "markdown"
		});
	}
}

這段很像 OpenClaw 在說:「我不會把所有檔案都當記憶,但我知道哪些檔案屬於記憶。」

  • MEMORY.md 是長期層
  • memory/ 下面的 markdown 是日常層
  • dream-report 代表它還有一條審核與整理的路

這裡最重要的不是檔名本身,而是層級感。
OpenClaw 不把記憶當成一坨資料,而是當成不同成熟度的資料。

再看日常筆記是怎麼被寫下來的。當使用者下 newreset,OpenClaw 會把最近的 session 內容抓出來,整理成一個新的記憶檔。

📄 原始碼:src/hooks/bundled/session-memory/handler.ts:239-347

const saveSessionToMemory = (event) => {
	const isResetCommand = event.action === "new" || event.action === "reset";
	if (event.type !== "command" || !isResetCommand) return;
	const writePromise = saveSessionMemoryNow(event);
	pendingSessionMemoryWrites.add(writePromise);
	writePromise.finally(() => {
		pendingSessionMemoryWrites.delete(writePromise);
	});
};

async function saveSessionMemoryNow(event) {
	...
	const hookConfig = resolveHookConfig(cfg, "session-memory");
	const messageCount = typeof hookConfig?.messages === "number" && hookConfig.messages > 0 ? hookConfig.messages : 15;
	let slug = null;
	let sessionContent = null;
	if (sessionFile) {
		sessionContent = await getRecentSessionContentWithResetFallback(sessionFile, messageCount);
		...
	}
	if (!slug) {
		slug = localTimestamp.timeSlug;
	}
	const filename = await resolveAvailableMemoryFilename({
		memoryDir,
		dateStr,
		slug
	});
	...
	const entry = entryParts.join("\n");
	await (await root(memoryDir)).write(filename, entry, { encoding: "utf-8" });
}

這段我很喜歡,因為它不搞神秘。

  • new / reset 不是單純清空
  • 它會先抓最近的 session 內容
  • 再寫成 memory/YYYY-MM-DD-<slug>.md
  • 還會避免同名檔案衝突

這代表 OpenClaw 的「忘記舊 session」不是把東西丟掉,而是先把能留下的東西收納起來。
這也是為什麼它不是只有聊天系統,而是有一點像工作筆記系統。

接著看召回。memory_search 不是只有字串比對,它會走混合搜尋:語意相似度加上關鍵字匹配。

📄 文件:docs/concepts/memory-search.md:68-68

OpenClaw runs two retrieval paths in parallel and merges the results:

Query -> Embedding -> Vector search
Query -> Tokenize -> BM25 search
Vector search + BM25 search -> Weighted merge -> Top results

這個設計很像人在找資料時的兩種腦內模式:

  • 你忘了 exact 名稱,但還記得意思,靠語意找
  • 你記得某個 ID、錯誤訊息、設定鍵,靠關鍵字找

只靠其中一種都不夠。
只靠語意會把精準字串漏掉,只靠關鍵字又會把「意思相近但字不一樣」的東西完全錯過。

所以 OpenClaw 不是問「你有沒有記住原文」,而是問「你能不能把有用的東西找回來」。

再來是 active memory。這個機制更像是「先幫你想一下」,而不是等你自己想到才去搜。

📄 文件:docs/concepts/active-memory.md:32-38

{
  plugins: {
    entries: {
      "active-memory": {
        enabled: true,
        config: {
          agents: ["main"],
          allowedChatTypes: ["direct"],
          modelFallback: "google/gemini-3-flash",
          queryMode: "recent",
          promptStyle: "balanced",
          timeoutMs: 15000,
          maxSummaryChars: 220,
          persistTranscripts: false,
          logging: true
        }
      }
    }
  }
}

這裡的重點不是設定長什麼樣,而是它代表一個流程事實:

  • 這是一個 blocking 的 recall sub-agent
  • 它會在主回覆前先跑一次
  • 如果找到相關記憶,再把摘要塞回主流程
  • 找不到就直接放行,不要拖慢整體回覆

也就是說,OpenClaw 想做的是「剛剛好提早一步」,不是「每次都要先翻十頁筆記」。

最後是 compaction 前的 memory flush。這一段直接把「記憶」跟「壓縮」接在一起。

Before compaction, OpenClaw runs a silent turn that reminds the agent to save important context to memory files. This is on by default.

The memory flush prevents context loss during compaction. If your agent has important facts in the conversation that are not yet written to a file, they are saved automatically before the summary happens.

這裡很像系統在說:

「你剛剛講的東西很重要,我先幫你抄下來,然後再去整理聊天記錄。」

這個動作很小,但實際上很關鍵。
因為一旦進入 compaction,很多細節都會被濃縮掉。沒有 flush 的話,剛冒出來的關鍵偏好、決策、限制條件,可能就這樣消失。

白話拆解

1. MEMORY.md 不是聊天紀錄,而是已經篩過的結論

OpenClaw 很像一個有整理習慣的人。
它不會把每次對話都塞進長期記憶,而是先問:

  • 這是偏好嗎?
  • 這是固定規則嗎?
  • 這是會影響未來決策的事嗎?

如果答案是肯定的,才值得進 MEMORY.md

這也解釋了為什麼長期記憶不應該太肥。
太肥的記憶不是更聰明,而是更難用。

2. memory/YYYY-MM-DD.md 是工作草稿,不是垃圾桶

日常筆記的價值在於「暫時先收著」。

今天可能只是做了一個測試、改了一個 hook、觀察到一個奇怪行為,這些內容未必值得永久保存,但也不該立刻丟掉。
所以 OpenClaw 先把它放進 daily note,之後再由 dreaming 或人工整理決定哪些能升級。

這很像你在桌上先放一疊待整理文件。
不是永遠擺著,而是先別讓它掉地上。

3. memory_search 的目標不是「查到所有」,而是「查到對的」

這是我覺得最像真實工作流的一點。

很多系統的搜尋只問命中率,但 OpenClaw 還要問相關性、時效性、重複性。
它不想把一堆很像的結果一起丟回來,因為那只會讓模型更猶豫。

所以 hybrid search 的意義不是炫技,而是為了讓召回結果既準又不那麼吵。

4. active memory 不是偷看答案,而是先幫你補背景

如果你是熟悉某個專案的人,你會知道一件事:
很多回覆之所以卡住,不是因為資訊不存在,而是因為背景沒先對齊。

active memory 就是做這件事。
它先用一次有限的 recall,看看這次回覆有沒有必要補上過去的偏好、歷史、限制或慣例。

這樣主回覆就不用每次都從零開始猜。

5. memory flush 的價值,是避免「整理」把「重點」一起整理掉

壓縮上下文很像打包行李。
你如果一直等到最後一分鐘才收,最容易發生的事情就是:

  • 真正重要的東西沒收進去
  • 反而把一堆沒用的雜物塞滿

memory flush 就是在打包前先把貴重物品拿去保管。
它不保證萬無一失,但至少降低「剛想到的重點被壓掉」的機率。

6. dreaming 的本質是 promotion,不是自動升官

OpenClaw 的長期記憶不是想到就直接寫進 MEMORY.md
它比較像一個審核流程:

  • 先在日常層留下候選
  • 再讓 dreaming 收集短期信號、評分、看頻率與多樣性
  • 通過門檻才進長期層

這代表 OpenClaw 對「值得記住」這件事是保守的。
它寧可少記一點,也不要把長期記憶灌滿噪音。

設計取捨

  • 好處是記憶層級很清楚,長期層不會被日常雜訊淹沒
  • 好處是召回時可以同時兼顧語意和關鍵字
  • 好處是 compaction 前有保護機制,不容易把關鍵資訊一起壓掉
  • 代價是整套流程比「直接把聊天丟進一個檔案」複雜很多
  • 代價是 promotion 與 recall 都有門檻,維護成本比純聊天 bot 高

如果換成另一種做法,也就是所有東西都直接寫進同一個記憶庫,短期會很爽,長期通常會爛掉。

你會開始遇到這些問題:

  • 舊偏好蓋掉新偏好
  • 關鍵決策和臨時想法混在一起
  • 搜尋結果太吵,模型每次都被一堆半相關內容干擾

OpenClaw 顯然不想走這條路。

今天的結論

  • OpenClaw 的記憶不是單一儲存區,而是 MEMORY.md、daily notes、召回工具、promotion 流程共同組成的系統
  • MEMORY.md 放的是長期、穩定、值得反覆參考的東西,不是原始聊天紀錄
  • memory/YYYY-MM-DD.md 是工作記錄層,先保存現場,再決定要不要升級
  • memory_search 用 hybrid retrieval,把語意與關鍵字一起考慮
  • active-memory 會在主回覆前先做一次 bounded recall,幫忙補背景
  • compaction 前的 memory flush 是保險機制,避免重要內容剛好在壓縮時流失
  • dreaming 不是把所有東西都記下來,而是把真正有價值的東西慢慢挑出來

下一步

第 6 天我們看的是 OpenClaw 怎麼阻止自己一直重複做同一件事。

第 7 天則是另外一個更深的問題:

它不只要避免亂做,還要避免亂記。

因為對一個 Agent 來說,真正難的不是「會不會記得」。
真正難的是「知道什麼該留、什麼該放、什麼只是暫時路過」。

下一篇我想接著往下看:

短期記憶和長期記憶,誰先說話?

這會是記憶系統裡更有趣的一層。


上一篇
第 6 天:一次把工具叫太多,Agent 會怎麼壞掉
下一篇
第 8 天:短期記憶和長期記憶,誰先說話
系列文
30 天走進 OpenClaw:一個 AI Agent 的誕生、掙扎與進化14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言