長任務 Agent 的狀態持久化:checkpoint、冪等性與斷點續跑

長任務 Agent 的狀態持久化:checkpoint、冪等性與斷點續跑

前言

跑一次三分鐘的 Agent,程序掛掉就整趟重跑,頂多浪費幾塊錢。但當 Agent 要跑三小時、要等人工審核、要跨程序重啟時,「整趟重跑」就不再是選項:工具呼叫已經產生的副作用——寄出的信、扣的款、寫進資料庫的資料——沒有倒帶鍵。

這篇文章談長任務 Agent 的狀態持久化:checkpoint 怎麼做、冪等性怎麼保證,以及如何設計斷點續跑。

一、兩種失敗,兩種解法

先分清楚兩件事:

  • **進程死了**:需要能從「最後完成的步驟」繼續,而不是從頭開始。這是 checkpoint 的工作。
  • **重跑的步驟又執行了一次副作用**:需要「重複執行結果不變」。這是冪等性的工作。
  • 兩者缺一不可:只有 checkpoint 沒有冪等性,續跑的那一步會把郵件寄兩次;只有冪等性沒有 checkpoint,每次 crash 都要把幾十個步驟從頭重跑一遍。

    二、三種持久化架構(如圖一)

    **1. 框架級 checkpoint(LangGraph)**

    LangGraph 的設計受 Google Pregel 啟發:每個 superstep 結束後,把完整狀態序列化,寫進 checkpointer。開發用 SQLite,生產換成 Postgres 或 DynamoDB。每次呼叫帶上穩定的 `thread_id`,crash 之後用同一個 `thread_id` 就能從最後一個 checkpoint 續跑。AWS 官方部落格示範了用 DynamoDBSaver 讓多個 worker 共享同一份狀態,這是 in-memory 儲存做不到的:in-memory 死了就沒了,多 worker 各自為政。

    **2. 事件日誌重演(append-only log)**

    把每一次模型回應、工具呼叫、工具結果,依序 append 到一條 durable event log。重啟時重播這條日誌,就能重建出完整狀態——推理迴路本身保持無狀態,所有狀態都從日誌長出來。Anthropic 在介紹 Managed Agents 時用的 Brain/Hands/Session 隱喻,Session 層指的就是這一條持久化的事件日誌:只要能從 session id 喚醒,harness 怎麼掛都救得回來。

    **3. 持久化工作流引擎(Temporal)**

    把 workflow 函數交給引擎託管,工具呼叫(activity)的執行結果由引擎記錄。一個 worker 死了,另一個 worker 接手繼續。有開發者實測:一個 workflow 被強制 kill 八次仍然正確完成——但要看清楚細節:完成的是 workflow(exactly-once 效果),而裡面的 activity 被執行了四次(at-least-once)。這就引出下一節。

    三、冪等性:重跑那一步的安全帶(如圖二)

    持久化引擎保證的是 **at-least-once**(至少執行一次),不是 exactly-once。經典的雙重扣款就是這樣發生的:activity 呼叫扣款 API 成功了,但在回報完成之前 worker 死了,引擎看不到結果於是重試,客戶被扣兩次。

    正規解法只有一句話:**at-least-once 執行 + 冪等接收 = exactly-once 效果**。實作上有四條鐵律:

  • 1. **key 從 run 身份+步驟身份確定性推導**:`run_id + step_id`(或 tool_call_id)。絕對不要在會重跑的節點裡用 `uuid4()` 現場生成 key,否則重試會產生新 key,dedup 直接失效。
  • 2. **下游若支援 idempotency key 就傳下去**:例如 Stripe 的 `Idempotency-Key` header;不支援的自己建一張 dedup 表——先用 unique constraint 插入 `(key, pending)`,做完更新為 `done` 並存結果;重試看到 `done` 就直接回傳存好的結果。
  • 3. **重複請求要回傳「第一次的成功結果」,不要回錯誤**:模型會把「已存在」解讀成失敗,然後另尋他路,反而製造更多副作用。
  • 4. **timeout 之後先查再重試**:timeout 只代表「不知道結果」,不代表「副作用沒發生」。先向下游查詢該 key 是否已完成,查不到才重試。
  • 四、實作要點:LangGraph checkpoint 範例

    from langgraph.checkpoint.sqlite import SqliteSaver
    
    memory = SqliteSaver.from_conn_string("agent.db")  # 生產換 Postgres
    graph = builder.compile(checkpointer=memory)
    
    # 關鍵:thread_id 穩定、可由任務推導,而非每次隨機
    config = {"configurable": {"thread_id": f"{user_id}:{task_id}"}}
    graph.invoke({"messages": [...]}, config=config)
    
    # 程序 crash 後,用同一個 config 再次 invoke,
    # 會從最後一個 checkpoint 續跑,而非從頭開始
    

    搭配冪等 key 的工具包裝:

    import hashlib
    
    def idem_key(run_id: str, step_id: str) -> str:
        # 確定性推導:同一次 run 的同一歩,重試時 key 不變
        return hashlib.sha256(f"{run_id}:{step_id}".encode()).hexdigest()
    
    def charge(key: str, amount: int):
        if (r := receipts.get(key)) is not None:
            return r  # 已執行過:回傳第一次的結果,不再重做
        r = stripe.Charge.create(amount=amount, idempotency_key=key)
        receipts.put(key, r)
        return r
    

    五、上線前的持久化 checklist

  • `thread_id`/`run_id` 穩定、由外部(使用者+任務)決定,不要每次隨機產生。
  • 生產環境不要用 in-memory checkpoint;為 checkpoint 表設定 retention,已完成的 thread 定期清理。
  • checkpoint 內容保持小且可序列化:存 id 與摘要,不存大 blob。
  • 每個節點都要能安全重跑;副作用一律走冪等路徑。
  • 人工審核(HITL)批准點天然就是 checkpoint:暫停幾小時再續跑,不需要額外 plumbing。
  • 上線前真的 kill 一次程序,驗證續跑是從最後一步、而不是從頭。
  • 結論

    長任務 Agent 的可靠性,來自兩條簡單的紀律:**每步結束留痕**(checkpoint),**每步重跑無害**(冪等)。做到這兩點,crash 就只是「換個 worker 繼續」,而不是「整趟重來」。下次你的 Agent 要跑超過十分鐘,先問自己:如果現在拔掉電源,它回得來嗎?

    參考來源

  • AWS Database Blog:Build durable AI agents with LangGraph and Amazon DynamoDB — https://aws.amazon.com/blogs/database/build-durable-ai-agents-with-langgraph-and-amazon-dynamodb/
  • Agent Engineering Curriculum:Durable Execution and Long-Running Agents — https://github.com/amangupta05/agent-engineering-curriculum/blob/HEAD/22-Durable-Execution-and-Long-Running-Agents.md
  • AI Engineering Handbook:Durable runs deep-dive — https://github.com/nikolaisachok/ai-engineering-handbook/blob/HEAD/docs/part-2-agents/durable-runs/deep-dive.md
  • 「Your AI Agent Sent That Email Twice. Here's the Idempotency Fix」— https://dev.to/rakesh_kumar_04012c337851/your-ai-agent-sent-that-email-twice-heres-the-idempotency-fix-3hli
  • 「Temporal finished my workflow exactly once and ran its first step four times」— https://dev.to/remdore/temporal-finished-my-workflow-exactly-once-and-ran-its-first-step-four-times-59dh
  • Anthropic Managed Agents 三層架構(Brain/Hands/Session)解析 — https://techcommunity.microsoft.com/blog/azuredevcommunityblog/when-anthropic’s-managed-agents-meet-microsoft-hosted-agents/4514337
  • Anthropic dynamic workflows 進度保存與續跑 — https://itbrief.co.nz/story/anthropic-launches-dynamic-workflows-in-claude-code
  • Uploaded Image Uploaded Image

    沒有留言:

    張貼留言