昨天,Agent 已確認 order 123 符合退款資格,問使用者要不要送出退款。使用者說:「我晚點確認。」Session 隨後結束。隔天,他只說:「繼續昨天那筆。」對話紀錄明明還在,Agent 卻不知道「繼續」是重新查資格、再次詢問,還是直接退款。
問題不是記憶不夠長,而是系統只記得說過什麼,沒有決定目前做到哪裡,以及誰有權改變這個答案。
這篇不談如何塞進更多 Context,也不展開長期 Memory 或 Checkpoint 引擎。我們只處理一個實作問題:讓執行控制器持有一份唯一可信、可驗證的任務狀態(canonical State,後文簡稱 State),並守住它的寫入權。
Agent 的可靠性,不只取決於記住什麼,更取決於誰有權改變系統相信的事實。
- 模型看過某件事,為什麼不等於系統已經相信它
- 模型、工具與執行控制器,誰能提案、誰能提供證據、誰能提交 State
- State 最少需要記住哪些資訊,才能安全跨過 Session
- 為什麼 Memory 與 Checkpoint 都不能取代 State 的寫入規則

先回到隔天那句「繼續」。若 Agent 只有昨天的 transcript 或一段摘要,它也許會讀到:
使用者:幫我處理 order 123 的退款。
Agent:已確認符合資格,要現在送出嗎?
使用者:我晚點確認。
摘要:使用者詢問 order 123 退款,訂單符合資格。
人類很容易從語境推測「還沒退款,正在等確認」。但執行控制器不能把推測當成控制資料。摘要沒有回答:退款工具是否曾被呼叫?目前輪到誰採取動作?資格由哪個系統、在什麼時候確認?今天的「繼續」是否仍對應同一位使用者與同一筆訂單?
Transcript 忠實保存了發生過的話,卻沒有決定哪個值已驗證、哪個值已失效,以及什麼動作現在可以執行。
因此,Conversation History 適合回答「發生過哪些互動」,不適合單獨回答「下一個動作是否合法」。OpenAI Agents SDK 的 Sessions 文件也把 Session 定位為保存對話歷史的機制;歷史能讓下一輪讀到先前內容,卻不會替內容加上權威性。
可以用一個問題檢查自己的 Agent:如果暫時拿掉 transcript,只留下結構化欄位,系統還能不能說出做到哪裡、正在等誰、尚未做什麼? 如果不能,你保存的是對話,不是可控制的任務進度。
Context 與 State 都可能出現 order_id=123,差別不在格式,而在權力。
Context 回答「這一輪模型需要看見什麼?」 它可以包含 instructions、最近對話、工具結構、政策證據與任務摘要,每輪依需要重新組裝。內容進入 Context,只代表模型能讀取。
State 回答「系統目前相信什麼?」 它保存會影響下一步的最小事實,例如任務階段、已驗證結果、等待原因與資料版本。只有通過規則提交的內容,才能改變它。
OpenAI Agents SDK 的 Context 文件區分本地 application context 與模型可見的 Context。這提醒我們:執行資料與模型看見的內容,本來就可以分開管理。
AgentScope 2.x也把可恢復的對話與執行狀態放在 AgentStateStore,再由 Workspace保存任務紀錄與長期 Memory。這個切法值得參考的不是名稱,而是資料生命週期:正在執行的任務、跨 Session 的記憶與外部業務結果,不應混成同一份文字。
外部業務系統仍是訂單、付款與庫存的結果真相。τ-bench在對話結束時,比對資料庫最終狀態與目標狀態,而不是相信 Agent 的最後一句回答。對話說「退款完成」是一項陳述;付款系統真的產生退款紀錄,才是結果。
State 也不需要複製整個資料庫。它只保存控制下一步所需的工作資訊,並記住這些資訊來自哪裡;資料不夠新時,就回到權威系統查詢。
模型看過,不等於系統相信。 如果一個值出現在 prompt 裡之後,就能被模型直接寫成任務事實,Context 已經越過了它原本的邊界。

若模型輸出「使用者已確認退款」,系統就把 user_confirmation=true 寫入 State,那只是在自然語言外套了一層 JSON。Schema 可以約束格式,不能證明這句話是真的。
更安全的設計把一次狀態轉移拆成三層:
隔天使用者說「繼續」,模型可以提案 confirm_pending_refund,卻不能直接把確認寫入 State。執行控制器還要檢查:登入者是否擁有 order 123?State 是否仍在等待確認?退款資格是否仍有效?「繼續」若不夠明確,就只能再問一次。通過後,這次確認才成為新的任務事實。
舊 State + 提案 + 外部觀察 → 驗證前置條件與不變條件 → 新版 State,否則拒絕或補問。
結構化輸出不等於合法提交。 模型摘要、工具回傳與 Memory 內容都先停在提交閘門外;只有通過可測試的應用規則,才可以改變 State。
這也代表不同欄位需要不同的權威來源。訂單擁有者由 Order Service 提供;退款資格來自資料與規則;「使用者看起來很急」只是模型推論。它們可以同時進入 Context,卻不能擁有相同寫入權。
提交閘門不一定很龐大。SWE-agent 的 Agent-Computer Interface提供了一個小型類比:模型提出 edit 命令後,介面會先執行 linter,語法無效的修改就被丟棄。模型提出動作,不代表環境必須接受;差別只在於業務 Agent 還要驗證身分、前置條件與外部結果,而不只是語法。

設計 State 時,第一步不是先挑資料庫,而是列出關鍵欄位,逐一回答:誰能提出新值、誰能提供證據、哪一道規則可以正式提交。
State 不必一開始就很複雜,但至少要回答三個問題:
order 123 可以先從這樣的最小結構開始:
task_id: refund_order_123
status: awaiting_user_confirmation
state_version: 4
facts:
order_owner: verified,來源 order_service/123
refund_eligibility: verified_true,來源 policy/v7
milestones:
user_confirmation: unknown
refund_dispatched: false
awaiting_user_confirmation 是合法狀態,不是「流程還沒寫完」;unknown 也不等於 false。前者表示現在不准自動前進,後者表示尚未取得足以提交的事實。把等待藏在最後一句對話,換 Session 或壓縮時就可能消失。
LangGraph要求先定義 State schema;StateFlow則把任務表示成 states 與 transitions。這些方法讓流程更容易表示,卻不會自動決定高風險轉移該由誰批准。框架提供容器,業務規則仍要定義合法值與寫入權。
State 還要守住少量但重要的不變條件(invariants):等待確認時,退款必須尚未送出;資格未確認時,不能建立可執行意圖;已完成任務不能悄悄回到等待。這些規則能把模糊錯誤變成可測試的拒絕。
若一個布林欄位的 null 可能同時表示尚未取得、等待確認或不適用,它就還不足以控制下一步。會導向不同動作的情況,應成為不同的合法狀態。
即使每項觀察單獨看都正確,抵達順序仍可能讓 State 出錯。使用者可能已在另一個裝置取消退款,昨天的資格查詢也可能在今天才回傳。若舊結果直接覆蓋新決定,Agent 就會用過期的世界繼續執行。
因此,每次提案與觀察都應帶著所依據的 state_version。若提案建立於 version 4,最新 State 已是 version 6,就拒絕這次舊版本覆寫並重新讀取。版本不是為了讓資料看起來更完整,而是避免昨天的正確覆蓋今天的正確。
LangGraph 對平行 State 更新的錯誤說明也呈現同一問題:兩個節點同時更新同一欄位,卻沒有合併規則時,結果存在歧義。技術上可以定義如何合併,業務上仍要決定哪個寫入者有權提交。
所以 order 123 隔天恢復時,安全順序不是「載入摘要 → 直接退款」,而是:
若期間政策改版,或訂單已由其他管道處理,昨天的待處理意圖就必須失效。
OpenAI Agents SDK 的 RunState也以 schema version 處理不相容狀態,並限制同一份 Session history 的並行恢復。這不是所有系統都要照抄的實作,但它說明了同一件事:能載入一份資料,不代表多個執行者可以安全地同時繼續。
不需要一開始就導入複雜的分散式協調。先加入 state_version、updated_by 與 based_on_version,寫入前比對版本;版本不符就拒絕並重讀,已能擋住大量舊結果覆蓋新決定的錯誤。
模型不必每輪讀完整 State。內部識別碼、權限與歷史轉移全放進 Context,不只浪費 Token,也擴大資料暴露與錯誤影響面。
Context Builder 應依這一輪要做的決策,產生最小的唯讀投影。對隔天的「繼續」,模型真正需要的可能只有:
order 123 退款;模型不需要看見所有內部欄位,更不應覆寫 state_version。執行控制器保留完整來源,在 Context 中只標示「已驗證/需重驗」與必要資訊。模型回傳的結果仍然是提案,還要再走一次驗證與提交。
於是資料流形成一條清楚的控制邊界:State 依規則投影成 Context,模型產生提案,外部觀察與驗證規則決定是否建立新 State。
Context 可以由 State 生成,卻不能因為模型說了一句話,就反向成為 State。
LangGraph Persistence會在 thread 的執行步驟保存 State snapshot,再把跨 thread 的資料交給 Store。重點不是照搬它的元件名稱,而是不要讓模型輸入、任務進度與跨任務記憶混成同一份資料。

替每種模型呼叫定義需要的 State 投影,而不是共用「整包 State」。需要更多資訊時,再由工具取得,不讓 prompt 同時兼任資料庫、任務進度與權限系統。
Checkpoint 只負責持久保存某一版 State、執行位置與待處理工作;如何 Resume、重新驗證並繼續執行,留給後篇。
耐久不等於正確。Checkpoint 只會忠實保存 State,不會修正錯誤信念。 如果 refund_eligible=true 只是模型從模糊對話猜出來的,做十份備份也不會讓它變成權威事實。持久保存解決資料會不會消失;權威來源與提交規則才決定資料能不能相信。
同樣地,Memory 可以記得使用者偏好原付款方式,或過去曾處理退款;它不能只憑歷史摘要宣告目前 order 123 已獲確認。當前任務仍要回到最新 State 與外部權威來源。
最後,用五個問題檢查自己的 Agent:
unknown、awaiting_user、rejected 與 completed 是否能被區分?state_version,避免舊結果覆蓋新決定?如果第 1、3、4 題有任何一題答不出來,先別急著加更長 Context 或更強 Memory。挑一個會等待使用者的真實任務,把「目前狀態、等待誰、尚未做什麼、誰有權提交下一步」寫成 State,再做一次中斷前後測試。
你也可以挑一個關鍵欄位,直接問:誰有權寫它? 如果答案是「模型、工具、任何流程都可以」,那通常就是 Agent 下一個最值得補上的控制邊界。
