持久執行讓 AI 代理能在程序當機、逾時、部署或長時間等待後繼續多步驟工作,而不會重複已完成的副作用。這需要持久保存的進度、可安全重複的寫入,以及處理結果未知呼叫的復原規則。
單純的重試迴圈不是持久執行。回應遺失後重試寄信或付款,可能製造出系統原本想復原的問題。
以外部副作用為中心建立工作流程模型
假設一項工作流程會建立發票、寄出發票,再將工作標記為完成。如果寄送成功,但 worker 在記錄成功前當機,下一次執行就不能安全地假設成功或失敗。
sequenceDiagram
participant W as Workflow
participant L as Operation ledger
participant E as Email provider
W->>L: Reserve send_invoice:task_442
W->>E: Send with operation key
E-->>W: Response lost
Note over W: Worker restarts
W->>L: Read operation status
W->>E: Reconcile by provider ID or key
E-->>W: Already sent
W->>L: Mark completed
W->>W: Continue from checkpoint
工作流程記錄說明系統打算做什麼,供應商或權威後端則說明實際發生什麼。復原流程會先對應兩者,再決定是否重複呼叫。
分開工作流程與活動
工作流程協調步驟、等待、分支及重試;活動則執行外部副作用,例如呼叫 API 或寫入檔案。使用以重播為基礎的引擎時,工作流程決策應維持確定性,網路呼叫、時鐘、隨機值及供應商 SDK 則隔離在活動內。
Temporal 的持久執行模型記錄事件歷史,並透過確定性重播在失敗後還原工作流程狀態。你可以採用該引擎,也可以使用操作資料表與佇列實作較小的狀態機。兩者要求相同:持久保存的歷史必須決定從哪裡繼續。
讓每項可重試寫入具備冪等性
一個邏輯副作用只指定一個鍵,並在用戶端、worker 及供應商重試時重用。儲存第一個終止結果,並拒絕用不同參數重用同一個鍵。
Stripe 的冪等請求合約會保存某個鍵第一次請求的狀態碼與回應內容,包括失敗。其保留行為是 Stripe 特有的,但設計原則可以通用:重複請求是否屬於同一項操作,應由伺服器判斷,而不是模型。
如果供應商不支援冪等性,就在 adapter 周圍加入操作帳本。這能減少自己的重試造成重複,但無法證明逾時的供應商呼叫確實失敗。應加入結果對應查詢;若高影響結果仍無法判斷,則要求人類審查。
對已驗證事實建立檢查點
實用的檢查點會記錄已完成的操作 ID 及下一個合法狀態轉換。在驗證副作用後寫入,並在可行時讓狀態轉換與自己的資料庫寫入不可分割。
不要只對模型訊息建立檢查點。對話記錄中的「電子郵件已寄出」可能只是模型聲稱的結果,而不是供應商回應。應儲存供應商訊息 ID 或操作記錄參照。
持久狀態指南會區分工作進度、記憶及業務資料。
決定每一層的重試責任
模型 SDK、HTTP 用戶端、佇列、工作流程引擎、工具伺服器及供應商都可能重試。這些設定相乘後,原本三次嘗試可能變成數十次呼叫。
每項操作都應記載:
- 哪些失敗可以重試;
- 由哪一層執行重試;
- 嘗試次數上限與最長經過時間;
- backoff 及供應商速率限制訊號;
- 每次嘗試重用的冪等鍵;
- 耗盡嘗試後的終止狀態。
驗證與授權失敗應停止;速率限制可以等待;寫入發生網路逾時時,應先對應結果再重試。模型不該從錯誤字串臨時推測這些規則。
將等待視為持久狀態
人類核准、排定時間、webhook 及長時間執行的部署都是工作流程狀態,而不是持續睡眠的程序。保存等待條件與喚醒訊號,讓重複訊號不會造成問題,並在延後的核准恢復執行時,重新驗證其代表的使用者仍有權限。
將取消公開為狀態轉換。取消應停止未來步驟,但不能抹去已完成的副作用。補償操作,例如將發票作廢,應與取消分開定義。
測試模糊的時間窗
成功路徑測試會漏掉最困難的狀態:伺服器可能完成請求,但用戶端沒有收到回應。應在外部呼叫後、完成記錄前立即注入失敗,改由另一個 worker 重新啟動,再驗證結果只會對應一次。
也要在核准期間、backoff 期間及取消後中斷。原始嘗試與恢復執行應共用工作流程及操作 ID,並記錄在同一條追蹤中。失敗復原指南會把這些情況轉成完整 runbook。
下方提示詞會在不碰觸正式環境的情況下設計第一次中斷測試。如果預期結果是「代理會自己想辦法」,這個工作流程就還沒有復原合約。