iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Claude AI

從 LLM 到 Agent:用 Claude 拆解現代 AI 工程的每一層系列 第 12 篇

選擇性記憶:對代理來說,記得對的比記得多重要

  • 分享至 

  • xImage
  •  

上一篇收在一個數字上:二十輪對話裡,之後還用得到的可能只有三句話,而你每一輪都把那三句跟另外九十七句一起重送。

把場景換成代理,這件事會更痛。昨天你讓代理修一個測試,它花了半小時才發現這個專案的測試要用 make test 跑、不能直接跑 pytest,你也糾正過它兩次:不要動 migration 檔。今天開一個新任務,它又從 pytest 開始試,又去改 migration。昨天學到的東西,今天全部歸零。

第 5 篇講過,權重是凍結的,模型本身不會因為用過一次就變聰明。所以對一個代理來說,記憶是它唯一能「學」的地方:這一次學到的東西,只有被寫到某個下一次讀得到的地方,才算學到。

這個動作叫寫入(memory write),而它最難的部分在動手存之前:你得先決定哪些東西值得存。 在寫入的那一刻決定什麼能進記憶,研究裡叫寫入時過濾(write-time gating)。這一篇講的是這道過濾怎麼設,以及怎麼把它接到你自己的代理上。


30 秒實驗

找一段你剛讓 Claude 做完的任務,最好是除錯、改程式或 code review,至少十輪,中間有它做錯、你糾正過的地方。

第一步:在同一段對話的最後,貼上這段提示詞:

這個任務結束後,對話會被清掉。請列出下次接新任務時,你需要知道的東西。
只能列四種:
1. 事實:關於我、這個專案、這個環境,之後不會隨便變的
2. 約束:我明確說過的規則或偏好
3. 結論:這次最後定案的決定(不是討論過程)
4. 教訓:這次試過、但不行的做法,以及為什麼不行
每一條一行,寫上今天的日期。沒把握的不要寫。

第二步:開一個新對話,把清單貼進去,交給它一個類似的新任務。

第三步:看它有沒有再犯上一次被你糾正過的錯。

它會不會重蹈覆轍,完全取決於第一步有沒有把那件事寫下來。 清單上沒有的,新任務就沒有。代理的記憶能做到多少,上限就是寫入時寫了什麼。


代理的記憶有四種

「記憶」這個詞太大了,先拆開。CoALA(Sumers et al., 2023)是一篇替語言代理整理架構的論文,借認知科學的分法,把代理的記憶拆成四種:

種類 CoALA 的定義 在你的代理上是什麼
工作記憶(working memory) 這一個決策週期裡正在用的資訊,跨模型呼叫延續 窗口裡的東西,也就是上一篇每一輪重送的那一整段
情節記憶(episodic memory) 先前決策週期的經驗 「上次試過 pytest,不行」
語意記憶(semantic memory) 代理對世界與對自己的知識 「這個專案用 make test」「不要動 migration」
程序記憶(procedural memory) 模型權重裡的隱性知識,加上代理程式碼裡的顯性知識 權重(凍結的)與你寫的系統提示、工具、流程

第 5 篇的知識庫,CoALA 也算成語意記憶;上一篇的帳單,是工作記憶的成本。這一層要處理的是中間兩列:情節與語意記憶,怎麼從這一次的工作記憶裡寫出來。

CoALA 對學習的定義很直接:語言代理的學習,就是把資訊寫進長期記憶。 它也提醒,改寫程序記憶(代理自己的程式碼與規則)比寫情節或語意記憶風險更高,容易引入錯誤。

回到第 1 篇那個請求結構。加上記憶之後,新任務的第一個請求長這樣:

{
  "model": "claude-opus-5",
  "max_tokens": 1024,
  "system": "(你原本的系統提示)\n\n<memory>\n2026-09-25 事實:這個專案的測試用 make test 跑\n2026-09-25 約束:不要修改 migrations/ 底下的檔案\n2026-09-25 教訓:直接跑 pytest 會缺環境變數而失敗\n</memory>",
  "messages": [
    { "role": "user", "content": "修好 test_checkout 那個失敗的測試" }
  ]
}

昨天的歷史沒有跟過來,跟過來的只有 <memory> 那三行。這一層加上去的,就是那三行,以及決定寫哪三行的判斷。

判準:留事實、約束、結論和教訓,不留過程

第 4 篇的評估集裡本來就有兩題在等這一層:第 5 題「上一個對話說過我負責 order-api」是一個事實,第 6 題「上一個對話說過團隊 review 不寫風格意見」是一個約束。層二的基準線上兩題都沒答對,第 5 篇接上知識庫以後也還是沒答對,因為它們不在任何文件裡,只是你在某一段對話裡順口講過的話。

代理還需要第四種:教訓。Reflexion(Shinn et al., 2023)做的就是這件事:代理做完一次任務、拿到回饋(測試沒過、答案錯了)之後,用文字反省自己哪裡做錯,把這段反省存進一個情節記憶緩衝區,下一次嘗試時帶著它。整件事不更新任何權重,只靠寫下來的文字。在 HumanEval 這個程式碼基準上,它的 pass@1 做到 91%,當時 GPT-4 是 80%。

所以我的判準是這張表:

該寫 記憶種類 不該寫
事實:測試用 make test、資料庫是 PostgreSQL 16 語意 為了找出這件事而試過的五個指令
約束:不要動 migration、review 不寫風格意見 語意 它第一次怎麼違反、你怎麼糾正它
結論:最後決定用 cursor 分頁,不用 offset 情節 討論過 offset 分頁的三個缺點
教訓:直接跑 pytest 會缺環境變數而失敗 情節 那半小時裡每一次失敗的完整輸出

右邊那一欄的用途在任務結束時就結束了。寫進去有兩個問題。

第一,錯的東西會被當成對的讀回來。 過程裡一定有被推翻的假設,寫進記憶之後,代理分不出那是定案還是當時的猜測,第 6 篇的雪球效應就跨過任務繼續滾。教訓是過程的結論:「試過 X,不行,因為 Y」,不含那一串嘗試。

第二,它會回到上一篇的帳單。 記憶每次任務都要讀,寫進去的過程越多,每次讀回來的也越多。

「沒把握的不要寫」也是這個道理:沒寫,代理會去查或問你;寫錯,它會很有把握地照著錯的做。

這道過濾在寫入時做,比在讀取時做更穩。Selective Memory for Artificial Intelligence: Write-Time Gating with Hierarchical Archiving(arXiv, 2026)直接比了這兩種做法:寫入時依顯著性(salience)分數決定一筆資訊能不能進記憶,被取代的舊版本封存而不刪除,留成一條版本鏈。干擾資料與有用資料的比例拉到 8 比 1 時,讀取時過濾(Self-RAG)的準確率掉到 0%,寫入時過濾仍然是 100%。原因很直觀:雜訊一旦寫進去,讀的時候就得跟有用的東西一起被撈出來比。


記憶可以長成三種形狀

決定好寫什麼之後,還有「寫成什麼樣子」。LongMemEval(Wu et al., ICLR 2025)把記憶**存成多大的粒度(granularity)**列為第一個設計控制點,比了整段對話一筆、一輪一筆、壓成摘要或事實三種。結果是取捨:一輪一筆整體最好;壓成事實整體變差(資訊流失),但跨對話推理反而變好。 壓得越小越好串,也越會掉細節,下面三種形狀就站在這條軸的不同位置。

事實清單:抽出來之後,還要能改

Mem0(Chhikara et al., 2025)的寫入分兩步:從最新的一問一答抽取候選事實,再拿它比對最相近的舊記憶,由模型選一個更新操作:ADD(新的)、UPDATE(補充舊的)、DELETE(推翻舊的)、NOOP(不動)。最值得抄的是 DELETE 和 NOOP:自己做的清單多半只有 ADD,所以才越加越長、新舊打架。

作者有利益關係。 Mem0 也是一家公司的產品,論文裡「省下超過 90% 詞元成本」這類數字是他們自己的量測。設計可以抄,數字打折看。

滾動摘要:用上一版的記憶,寫出下一版

Wang et al.(2023)把它寫成遞迴(recursive):每次拿上一版記憶加上新的對話,產生新一版,而且能跟長脈絡、檢索增強疊加;更早的 Beyond Goldfish Memory(Xu et al., 2021)也量到摘要再回想的方法勝過當時的標準架構。弱點同樣有記錄:LongMemEval 說摘要會流失細節,Mem0 也因此在摘要之外另給最近幾則訊息。遞迴讓損失累積,第三版掉的,第四版不會知道。

檔案:分層,而且彼此連得起來

MemGPT(Packer et al., 2023)借作業系統的分層記憶,叫虛擬脈絡管理(virtual context management):窗口是快而小的一層,外部儲存是慢而大的一層,在兩層之間搬資料。A-MEM(Xu et al., 2025)借卡片盒筆記法(Zettelkasten),每筆記憶都帶描述、關鍵字與連結,新筆記進來時還會回頭更新舊筆記。Claude 的記憶工具就是這個形狀:一個目錄底下的檔案,也就是 MemGPT 那個慢而大的一層。

放在一起看

面向 事實清單 滾動摘要(rolling summary) 檔案
代表研究 Mem0 Wang et al.;Beyond Goldfish Memory MemGPT;A-MEM
在粒度軸上的位置 最小,一條一個事實 整段歷史壓成一段 按主題分,一份裡可以很細
寫入時的決定 ADD/UPDATE/DELETE/NOOP 新一版要保留舊一版的哪些部分 寫進哪一份、要不要回頭改舊的
讀的時候 整張貼進去 整段貼進去 只讀跟這次有關的那一份
論文量到或指出的弱點 壓成事實,整體表現下降(LongMemEval) 摘要會流失細節(LongMemEval、Mem0) 要有人決定分類與連結,分錯就找不到

三種形狀,實際寫出來長什麼樣子

事實清單:Mem0 的更新那一步,在 Claude 上就是一個工具定義。四個操作做成 enum,再用 tool_choice 強制模型用它回答,拿回來的一定是一個結構化的操作:

update_tool = {
    "name": "update_memory",
    "description": "比對候選事實與現有記憶,選一個操作",
    "input_schema": {
        "type": "object",
        "properties": {
            "operation": {"type": "string", "enum": ["ADD", "UPDATE", "DELETE", "NOOP"]},
            "target_id": {"type": "integer"},
            "text": {"type": "string"},
        },
        "required": ["operation"],
    },
}
# client.messages.create(..., tools=[update_tool],
#     tool_choice={"type": "tool", "name": "update_memory"})

模型只負責選操作,照著它去改那份 JSON 的是你的程式碼。有一個細節要顧:刪掉的 id 不要回收,否則別處還記著舊 id 的地方,會默默指到新的那一條。

滾動摘要:Wang et al. 的遞迴,寫成程式就是一行:

for turns in sessions:
    memory = summarize(memory, turns)  # 新一版 = f(上一版, 這一段對話)

過去的一切只能透過 memory 這個字串傳下去,某一次沒寫進去的,之後每一次都拿不到。

檔案:按主題拆開,更新時改掉舊的那一條、留一行取代了什麼:

/memories/
├── preferences.md    # 2026-10-02 回答用繁體中文(取代 2026-09-24:中英並列)
└── projects/api.md   # 2026-09-24 資料庫是 PostgreSQL 16

在 Claude 上,讓模型自己讀寫這個目錄,tools 裡放一行 {"type": "memory_20250818", "name": "memory"} 就夠了(對照官方 Python SDK 0.112.0 的原始碼,2026-09-26 查)。檔案實際存在哪、誰看得到,是你的程式碼決定的,那是下一篇的事。

條目少的時候清單最省事;條目一多、主題一雜,就會長成檔案的形狀。不管哪一種,事實都要寫得夠精確,才經得起壓縮。


另一派:全部存下來,再往上綜合

上面都是寫入時過濾。Generative Agents(Park et al., 2023)走另一條路:25 個代理住在沙盒小鎮裡,每一筆經驗都存進記憶流(memory stream),寫入當下請模型打一個 1 到 10 的重要性分數(importance score);近期分數加總超過 150,就觸發一次反思(reflection):拿最近 100 條紀錄問出 3 個高層問題,歸納出見解,並標明根據哪幾條紀錄。反思也存回記憶流,所以常被讀到的,是模型自己歸納出來的那幾條。消融實驗顯示觀察、規劃、反思各自拿掉都會明顯變差。

它跟 Reflexion 是同一個動作:讓代理自己把一堆經驗歸納成幾條用得上的話。 最值得抄的是標出處,一條結論指不回它的依據,錯了也查不出來,跟第 8 篇講的是同一件事。150、100 這些數字是小鎮場景的,不要照抄。


用五種能力,回頭檢查你寫了什麼

LongMemEval 把長期記憶拆成五種能力。它們本來是用來量的,倒過來看,每一種都對應到寫的時候要做對的一件事:

LongMemEval 的能力 寫入時要做對的事 用第 5、6 題來看
資訊抽取(information extraction) 細節要留得夠精確 寫「order-api」,不是「一個訂單相關的服務」
跨對話推理(multi-session reasoning) 同一個東西每次都用同一個名字 不要這次寫 order-api、下次寫 orders 服務
時間推理(temporal reasoning) 每一條帶日期 「2026-09-24 約束:…」,才答得了「上個月的規則是什麼」
知識更新(knowledge updates) 新的要取代舊的,不是再加一條 團隊改成要寫風格意見了,舊的那條要改掉
知道自己不知道(abstention) 沒把握的不寫,沒寫的就讓它說不知道 沒聽你說過負責哪個服務,就不要猜一個寫進去

最後兩列在寫的當下看不出問題:知識更新做錯,記憶裡會有兩條矛盾的約束;abstention 做錯,它會很有把握地講出你沒說過的事。兩種錯都不會報錯。

這一篇大部分是通用的。 什麼該記、記成什麼形狀,換成任何一家模型都成立,我用 Claude 做示範。下一節的記憶工具與快取位置,才是 Claude 專屬的部分。


怎麼替你的代理接上記憶

一個有記憶的代理,每一次任務都是同一個循環:開工時讀,工作中用,收工時寫。 真正要設計的是誰來讀、誰來寫、什麼時候寫。常見的接法有三種:

面向 預先載入 讓代理自己管 收工後另外整理
怎麼讀 開工時整張放進系統提示 代理自己決定什麼時候去讀哪個檔案 同預先載入
誰決定寫什麼 你的程式碼 代理,在工作過程中 任務結束後,另一次模型呼叫
適合的形狀 事實清單 檔案 事實清單或檔案
容易壞在哪 清單一長,每次都整張讀 代理在任務中途寫下還沒驗證的猜測 多一次呼叫的成本

預先載入最適合 Claude 的快取。第 10 篇講過快取只認前綴,記憶在同一次任務裡不變,所以開工讀一次放進系統提示,任務中不要動它。

讓代理自己管就是 Claude 記憶工具的做法。官方文件寫明,打開記憶工具之後,Claude 會在開始任務前自動檢查它的記憶目錄,工作中把學到的東西存成檔案(2026-09-26 查)。讀寫都是工具呼叫,發生在 messages 裡,系統提示的前綴不受影響。代價是寫入的判斷交給了代理本身。

收工後另外整理就是 30 秒實驗做的事,最接近 Reflexion:寫的時候結果已經出來,知道哪個做法成功、哪個失敗。

不管哪一種,系統提示裡都該有一份寫入規則。這一篇的判準寫成代理讀得懂的樣子,可以直接拿去用:

<memory_rules>
開工前:先讀記憶。記憶跟這次任務的指示衝突時,以這次為準,收工時把舊的那條改掉。
收工時:只寫四種:事實、約束、結論、教訓(試過什麼、為什麼不行)。
每條一行,開頭寫日期與種類。沒把握、還沒驗證的不寫。
已經有相關的一條,改舊的,不要加新的。不要寫任務過程。
</memory_rules>

這麼做的代價

第一,寫入要花模型呼叫。 收工整理要讀完整段過程,Generative Agents 那一派還要逐條打分與反思。省下的是之後每次任務的重新摸索。

第二,判斷錯了沒有人會知道。 不該寫的寫了,代理會在你沒在看的時候一直照做:「不要動 migration」寫進去之後,它每次都遵守,包括你真的需要它改 migration 的那一天。

第三,有些記憶會變成規則。 「以後部署都先跑 make check」改變的是代理怎麼做事,碰到了 CoALA 說風險更高的程序記憶。比較穩的做法是讓代理只能提議,由你確認後才寫進系統提示。

第四,記憶要有人維護。 知識更新那一列說的「新的要取代舊的」,不會自己發生。第 5 篇的知識庫會沉默地過期,記憶也會,而且更難發現,因為它是一條一條在不同的對話裡長出來的,沒有人記得是哪一天寫進去的。所以每條都要帶日期。

我現在的習慣是:一段任務結束才寫,一次只寫幾條,寫之前先看一眼現有的清單,有衝突就改舊的,不要加新的。


這一篇多了什麼,又多付了什麼

多了什麼能力:你的代理跨任務學得到東西了。你有 CoALA 的四種記憶當地圖、一個判斷該不該寫的準則(留事實、約束、結論、教訓,不留過程)、三種接法的取捨,以及一份可以直接放進系統提示的寫入規則。

多付了什麼代價:每次任務結束時多一次模型呼叫,以及一個不會自己更新、寫錯了也不會報錯、而且代理會在你沒看的時候照做的狀態。


下一篇

決定好要記什麼以後,那些東西實際上存在哪裡?又是誰負責存?

在 Claude 上,這個問題的答案有點出乎意料:模型只負責提出「我要寫這一條」的請求,真正把它存下來、存在哪、誰看得到,全部是你的程式碼決定的。下一篇就真的接一次,看那個請求長什麼樣子,以及它把哪些責任留給了你。


延伸閱讀


上一篇
貼心的代理記得你講過的小事:短期記憶每一輪都在長大
下一篇
記憶不在 Claude 那裡:它只負責開口要,存在哪是你的事
系列文
從 LLM 到 Agent:用 Claude 拆解現代 AI 工程的每一層 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言