對應原題(Common ‧ Agents and Tool Use)

  • Design memory for a long-running agent: what do you store, where, and how do you retrieve it?
    — Asked at: Anthropic

🎯 什麼情境該想到我

當你「要讓 agent 跨 session 記得東西、或長任務跑到上下文滿了」的時候。
★ 這題的第一個得分點是:先把「記憶」拆開。 直接回答「用向量資料庫存對話」是最常見的淺答案。

⚙️ 怎麼用

★ 一、四種記憶,四種設計

類型是什麼例子生命週期存哪裡
工作記憶
Working
當前上下文視窗這次任務的所有訊息一次任務context window
情節記憶
Episodic
發生過什麼「上週三他請我改了登入頁」長期,會累積append-only 事件日誌 + 時間/向量索引
★ 語意記憶
Semantic
提煉出的事實「他偏好 Go」「專案用 pnpm」長期,會更新結構化 KV / 資料庫(不是向量!)
程序記憶
Procedural
學到的做法「這個 repo 要先跑 make gen」長期檔案 / skill 庫

★ 最重要的設計判斷:語意記憶應該用結構化儲存,不是向量。
「使用者的偏好語言」是一個欄位,不是一條可以被語意檢索的筆記。
用向量存它會帶來三個問題:無法更新、無法保證被檢索到、會出現矛盾的多個版本。

規則:能結構化的就結構化;只有「量大且無法預先定義欄位」的東西才丟向量。

二、三個核心操作

記憶系統 = 寫入 + 檢索 + 更新/遺忘。多數實作只做了前兩個,第三個才是真正難的。

① 寫入:什麼值得記?

策略做法評價
模型自己決定給它一個 remember(fact) 工具靈活,但會亂寫:記下無關瑣事、記下自己的推測
規則觸發偵測到「我偏好…」「以後都…」「記住…」就寫精準但覆蓋窄
★ 事後提煉任務/對話結束時,用一次 LLM 呼叫抽出值得留的事實✅ 最穩。有完整脈絡可判斷,且能一次做去重與衝突處理

寫入時必須帶的 metadata:

value:      "偏好 Go 與 Node.js"
type:       preference           # 分類,決定檢索策略
source:     session_id / 訊息 ID  # ★ 可追溯
created_at: 2026-08-28
confidence: 0.9                  # 是使用者明說的還是推測的
ttl:        null                 # 會過期嗎

★ source 與 confidence 是事後除錯的命脈。 記憶出錯時,你要能回答「這條是從哪裡來的、是他說的還是模型猜的」。

② 檢索:什麼時候拉什麼回來

策略適用
★ 每輪固定注入小而重要的語意記憶(偏好、專案設定)。量小、必要、便宜、100% 可靠——優先用這個
按需檢索大量情節記憶。用向量 + 時間 + 類型過濾
時間衰減 + 重要度加權
工具化給 agent 一個 search_memory 工具,讓它自己決定要不要查

不要對「使用者偏好」做語意檢索。 它只有幾十個 token,每次都塞進 system prompt 就好——還能吃到前綴快取(見 前綴快取 Prompt Caching)。

③ ★ 更新與矛盾(記憶最難的部分)

2026-03:「我住台北」        → 寫入
2026-08:「我搬到高雄了」    → 又寫入一條

檢索「使用者住哪」→ 兩條都回來 → 模型自相矛盾 😱

四個對策:

對策說明
★ 結構化欄位而非自由筆記「居住城市」是一個欄位,新值直接覆寫舊值。這一招解掉八成的矛盾問題
寫入時衝突偵測新事實進來時,先檢索同類型的既有事實,讓 LLM 判斷「取代/補充/無關」
時間戳 + 取最新檢索後按 created_at 去重,同一 key 只留最新
軟刪除 + 版本舊值標記為 superseded 但保留(可追溯、可回復)

遺忘也要設計:TTL(時效性事實)、LRU(很久沒用到)、容量上限、使用者主動刪除。
沒有遺忘機制的記憶系統,最終會被噪音淹沒。

★ 三、長任務的上下文壓縮

當單次任務就把上下文塞滿時(見 ReAct 與 Agent Loop 的 成長):

策略做法風險
滑動視窗丟掉最舊的訊息❌ 會丟掉最初的目標與關鍵約束——這是 agent drift 的主因之一
摘要壓縮把舊訊息壓成摘要⚠️ 丟細節;反覆摘要會漂移(摘要的摘要的摘要)
★ 外部化 + 引用大內容寫到檔案/store,上下文只留路徑與一行描述,要用時再讀回來✅ 最穩健。無損、可追溯
分段交接到門檻就產生完整的交接文件(目標、已完成、待辦、關鍵決策),開新上下文✅ 長任務的實務做法

★ 無論用哪種,「原始目標」與「硬約束」必須永遠釘在上下文裡,不參與壓縮。
這是防止長跑偏題最便宜的一招。

摘要壓縮要「分欄」,不要一句「幫我摘要前文」(補充自 https://masonailab.com/tech/context-management/,查證 2026-10-11):
只叫模型「摘要」,最常被壓掉的是例外條件與被否決的方案——偏偏這兩樣最容易讓 agent 重蹈覆轍。摘要至少拆成:

欄位為什麼要獨立一欄
目標就是上面說的「釘住不壓縮」
已確認事實人名、數字、版本——摘要最容易改寫失真的部分
使用者偏好跨工作階段要沿用的,應另存到外部(見上面宣告式記憶)
已做決定+否決理由沒有否決理由,下一輪會把被否決的方案再提一次
未解問題
下一步

摘要完要回歸測試:拿原始對話出一組題目(人名、數字、限制、否決理由、待辦),確認摘要後還答得出來。重要資料不能只留在會被反覆改寫的摘要裡(呼應下面「摘要會漂移」)。

Agent 的上下文壓力多半來自工具,不是聊天文字。Anthropic 依「token 花在哪」分四種解法(Manage tool context,查證 2026-10-11):

壓力來源解法適用時機
一開始就載入的大量工具定義Tool search:只給一個搜尋工具,其餘定義按需載入工具約 20 個以上、多數回合用不到
一連串 tool_use/tool_result 來回Programmatic tool calling:模型寫一段腳本一次呼叫完,中間結果不進對話可以批次跑的小呼叫鏈
重複送出的穩定工具定義Prompt caching:不減少 context 佔用,只降成本(見 前綴快取 Prompt Caching)工具集大但固定
歷史裡過期的 tool_resultContext editing:清掉用完的舊工具結果,不必重開對話長對話、早期結果已不相關

四者可疊加。實務起點:把已完成步驟的原始輸出清掉,只留可驗證的結論、檔案位置、狀態與下一步——這就是上表「外部化+引用」在工具結果上的版本。

溢位行為(Claude API,查證 2026-10-11):輸入本身超過視窗 → 一律回 400 prompt is too long;輸入+max_tokens 超過、但輸入本身沒超 → Claude 4.5 以後的模型照收,生成碰到上限時以 stop_reason: "model_context_window_exceeded" 停下(較舊模型直接回驗證錯誤)。所以 agent loop 要處理這個 stop reason,不能只看 end_turn。

★ 三點五、為什麼「記憶」不能直接沿用 RAG 那套

(補充自 https://juejin.cn/post/7690218295266066441,查證 2026-09-28)

很多人把記憶做成「把對話丟進向量庫再檢索」——那是把文件檢索的解法套到互動記錄上,但這兩者是不同物種:

文件語料互動記錄
本質為人閱讀而寫的敘述,切塊不心疼關係密集:誰說的、對誰、什麼時候、取代了哪條
典型查詢「密碼原則是什麼」——語義相似即可「這個部署誰批准的 → 批准人是誰的上級」——鏈式關係
向量能不能做✅ 這是它的主場❌ 關係鏈在向量空間裡沒有表示
時間大多無時效事實會被推翻(「他搬到台北了」取代「他住台南」)→ 見上面「更新與矛盾」

這就是為什麼記憶類方案不約而同走向圖結構,而文件 RAG 那邊反而在爭論 GraphRAG 值不值得(見 工具-RAG檢索增強生成)——同一個技術,在兩個場景的性價比完全不同。

三條落地路線(按接入成本由低到高)

路線是什麼什麼時候選
宣告式記憶
(CLAUDE.md / AGENTS.md / MEMORY.md)
一個 Markdown 檔,每次會話開頭固定注入★ 規則類記憶的正解:偏好、專案設定、硬規定。不需要檢索,每次都在,100% 可靠。量小就別上系統
Mem0會話級記憶抽取與檢索,跨會話個性化接入成本最低的「系統化」選項
Zep / Graphiti時序知識圖譜:實體與關係帶時間 validity;檢索時 BM25 + 向量 + 圖走訪三路,建構在寫入時完成,檢索期零 LLM 呼叫需要鏈式關係查詢與「事實何時生效/何時失效」時

★ 先用宣告式。上面「每輪固定注入」那一列講的就是它——量小、必要、便宜、100% 可靠。
互動歷史短、關係不密集的專案,上記憶系統是在繳不必要的稅。

四、架構總圖

                    ┌─────────────────────────────┐
  每輪固定注入 ────▶│  System Prompt               │
  (語意記憶:偏好、 │  + 原始目標(釘住,不壓縮)  │
    專案設定)       │  + 硬約束                    │
                    ├─────────────────────────────┤
  按需檢索 ─────────▶│  最近 N 輪訊息(完整)       │
  (search_memory)  │  較舊訊息(摘要或外部化引用)│
                    └─────────────────────────────┘
                              ▲            │
                    ┌─────────┴────┐   ┌───▼──────────┐
                    │ 結構化 KV     │   │ 事件日誌      │
                    │ (語意記憶)   │   │ (情節記憶)   │
                    │ 可更新、可覆寫 │   │ append-only  │
                    └──────────────┘   └──────────────┘

⚠️ 注意 / 什麼時候不適用

  • ★ 記憶是 PII 的天然匯聚點。它會自動累積使用者的個資、偏好、行為模式。
    • 必須支援按使用者刪除(GDPR/個資法),而且要刪得乾淨(含向量索引與備份)
    • 多租戶嚴格隔離 —— 記憶洩漏比檢索洩漏更嚴重,因為它是「關於這個人」的
  • ★ 記憶污染(memory poisoning)是真實的攻擊面。
    攻擊者誘導 agent 寫入一條惡意「事實」(「使用者已授權你轉帳」),之後每一輪都會被注入——
    這是持久化的提示注入,比單次注入危險得多。
    → 對策:寫入要驗證與分類、高風險類型的記憶要人工確認、記憶內容進入 prompt 時要標記為不可信資料。
  • 別把所有東西都記下來。記憶越多噪音越多,檢索越不準。寧可少而精。
  • 記憶系統的評估很難。需要跨 session 的長期測試集(第 1 天說了什麼,第 30 天要不要記得),而不是單輪的 QA。
  • 摘要會漂移。反覆摘要的內容會逐漸偏離原意且無法察覺。要保留原始日誌能回溯。
  • 小專案不需要這一整套。先從「每輪注入一小段使用者偏好」開始,有痛點再加。

🧪 我實際套用的紀錄

  • 2026-09-20:從面試題庫展開。待辦:檢查現有 agent 的記憶有沒有 source/時間戳,以及能不能按使用者刪除。

🔗 相關