假設一個客服 Agent 收到退款申請。

它先檢查訂單,確認符合政策,再呼叫支付服務建立退款。請求送出後,連線逾時。Harness 沒有收到成功回應,也沒有收到明確失敗。

接下來該做什麼?

最危險的答案是:再試一次。

外部支付系統可能根本沒有收到第一個請求,也可能已經完成退款,只是回應在網路途中遺失。若 Harness 把 Timeout 直接解讀成「沒有執行」,第二次呼叫就可能建立重複退款。

Agent 系統的可靠性問題,常常不是模型答錯,而是系統無法回答這幾件事:

  • 這次失敗屬於哪一類?
  • 前一個操作是否已經產生副作用?
  • 這個操作能不能安全重試?
  • 下一步應該 Retry、Repair、Fallback、Replan、Compensate,還是 Stop?
  • Runtime 還剩多少時間、成本與嘗試次數?
  • Worker Crash 後,新的 Worker 要從哪個可信狀態恢復?

可靠性不是多加幾次 Retry。可靠性是讓每一種失敗都有可判斷、可限制、可恢復的路徑。

Failure Class 只回答一半,還要知道副作用確定性

在聊天產品裡,Failure 可能只是沒有答案。Agent 能呼叫 Tool、修改 State、啟動 Background Work 後,同一個「失敗」至少要從三個維度判斷:

  1. Failure Domain:模型、Schema、Policy、Dependency、State、Capacity,還是 Budget?
  2. Effect Certainty:確定未執行、可能已執行、部分完成、確定完成,還是仍無法判定?
  3. Recovery Eligibility:目前 Contract、Deadline、Cost、Idempotency 與 Authority 是否允許下一步?
Failure domain典型案例Effect certainty常見方向
ModelTimeout、Provider unavailable、截斷輸出通常沒有外部副作用Bounded Retry、Compatible Fallback、Continuation
Schema/ValidationTool Arguments 不符合 Contract尚未執行Repair、重新產生 Arguments
Permission/Policy權限不足、Policy Deny尚未執行Stop、等待 Approval、改走允許路徑
Transient DependencyRate Limit、短暫網路故障依 Operation 而定Backoff、Jitter、Bounded Retry 或 Reconcile
Permanent Business餘額不足、訂單不可退款通常已知失敗回報原因,不 Retry
State ConflictVersion Conflict、Stale Claim本地 Commit 未成立,外部狀態需另查Reload Canonical State、Repair、Replan
Unknown OutcomeRequest 已送出後 Timeout可能已產生副作用Reconcile,不盲目重送
Partial OutcomeBatch 或 Saga 只完成一部分已有部分副作用Forward Repair、Compensate、Human Review
Capacity/OverloadQueue 過舊、Sandbox 不足工作尚未開始或停在中途Admission Control、Backpressure、Load Shedding
Budget ExhaustionCost、Time、Step 或 Retry Budget 用盡視目前步驟而定Degrade、Checkpoint、Human Takeover 或 Stop

這個 Taxonomy 不是為 Log 增加漂亮欄位。它決定下一個 State Transition 是否安全。

INVALID_ARGUMENTSRATE_LIMIT 都可能讓 Tool Call 失敗,但原封不動重送第一種不會修好任何事情。PERMISSION_DENIED 也不是等待幾秒就會消失的網路抖動。最需要單獨處理的是 UNKNOWN_OUTCOME:呼叫端沒有收到結果,外部世界卻可能已經改變。

Error classification and recovery action matrix

Figure 8-1|Recovery Decision 同時考慮 Failure Domain、Side-effect Certainty、Retry Safety 與剩餘 Budget,不把每種 Error 固定映射成同一個 Action。

Recovery State Machine:Action 也是受控的 State Transition

Recovery 不應散落在每個 Tool Wrapper 的 try/except 裡,也不應把所有 Error 原樣丟回模型臨場決定。

常見 Recovery Action 包括:

  • Retry:Fault 可能是暫時性的,Operation 可安全重送,且 Budget 尚未用盡。
  • Repair:先修正 Schema、Arguments、Query 或缺少的輸入。
  • Fallback:切換到已知相容的 Model、Provider、Tool Implementation 或 Degraded Service Path。
  • Replan:目前路徑阻塞,但 Task 仍有其他合法路徑。
  • Reconcile:結果未知,先查 External Authority。
  • Forward Repair:副作用已成立,不回頭重做,改修後續 State。
  • Compensate:執行一個新的校正動作,而不是假裝時間倒流。
  • Quarantine:隔離 Poison Message、未知版本或反覆失敗的 Work Unit,等待人工或離線修復。
  • Ask Human:商業判斷、Approval 或高風險歧義阻止自動恢復。
  • Stop:Policy、永久錯誤、資訊遺失或 Budget 不允許繼續。
recovery_policy:
  RATE_LIMIT:
    action: retry
    owner: dependency_adapter
    max_attempts: 3
    backoff: exponential_with_jitter

  INVALID_TOOL_ARGUMENTS:
    action: repair
    owner: agent_controller
    max_repairs: 2

  PERMISSION_DENIED:
    action: stop

  UNKNOWN_SIDE_EFFECT_OUTCOME:
    action: reconcile
    blind_resend: false

  POISON_MESSAGE:
    action: quarantine

  BUDGET_EXCEEDED:
    action: stop

這份 YAML 只能說明 Recovery Decision 應有 Owner、Budget 與 Outcome。它不能證明 Durable State、Idempotency、External Reconciliation、Concurrency Control 或 Error Mapping 已正確實作。

RunState 至少要保存:

  • failure domain 與 effect certainty
  • logical attempt 與 transport attempt
  • recovery owner
  • last delay 與 Retry-After
  • provider/model/tool 是否改變
  • side-effect status 與 reconciliation cursor
  • remaining budget
  • terminal/quarantine reason

Recovery Action 本身也可能失敗。Compensation、Status Query、Fallback 與 Notification 都需要自己的 Attempt、Idempotency 與 Stop Policy。否則系統只是把無限迴圈從主流程搬到「修復流程」。

Timeout、Retry 與 Backoff 解決的是不同問題

這三個詞常被綁在一起,但責任不同。

Timeout 限制等待時間

Timeout 回答:一個操作最多等多久?

至少要區分:

  • Model Call Timeout
  • Individual Tool Timeout
  • Workflow/Run Deadline
  • User Approval Expiry
  • Queue Waiting Deadline

Timeout 是呼叫端的觀察結果。它通常只代表「在期限內沒有收到答案」,不代表遠端沒有開始,也不代表遠端一定失敗。

Retry 用另一次嘗試交換成功機率

Retry 適合暫時性錯誤,例如短暫網路故障、Rate Limit 或可恢復的 Provider Error。它會增加 Latency、Cost 與下游負載。

AWS Builders’ Library 特別提醒,Retry 具有 selfish 的特性:呼叫端提高自己的成功機率,同時讓已經過載的下游承受更多工作。多層服務若每層各自 Retry,失敗流量可能被乘法放大。1

因此 Retry Policy 至少要限制:

  • Maximum Attempts
  • Total Retry Time
  • 哪一層擁有 Retry Authority
  • 哪些 Error Code 可重試
  • 操作是否 Idempotent
  • Deadline 是否仍允許下一次嘗試
  • 是否遵守 Provider Retry-After

Backoff 與 Jitter 避免同步重試

Exponential Backoff 讓每次等待時間逐漸增加。Jitter 再加入隨機性,避免大量 Client 在同一時間失敗,又在同一時間醒來,形成一波一波的 Retry Storm。

它們不能把不可重試的錯誤變成可重試,也不能替副作用操作提供 Idempotency。把 Jitter 加在重複付款上,只會讓事故比較有節奏。

Budget 與 Retry Authority 必須跨層可見

Agent 可以重新規劃、重新檢索、重生 Tool Arguments、切換模型並再次驗證。每條恢復路徑都會消耗資源。

常見 Budget 包括:

  • Max Steps
  • Max Model/Tool Calls
  • Logical Retry Count
  • Transport Retry Count
  • Token/Monetary Budget
  • Wall-clock Deadline
  • Retrieval Depth
  • Context Information-loss Limit

Runtime 必須強制並持久化這些限制。Prompt 裡寫「最多試三次」不是控制。

更麻煩的是隱藏重試:

  • SDK 自動 Retry 三次
  • Tool Wrapper 再試三次
  • Agent Loop 重新呼叫三次
  • Queue 在 Consumer Crash 後 Redeliver

每一層單獨看都很保守,組合起來卻可能放大成數十次呼叫。可靠設計應指定 One Retry Owner per Boundary,或至少把 Library Retry 納入同一個 Attempt Ledger。

每個下游呼叫還應傳遞剩餘 Deadline,而不是讓內層擁有比外層 Run 更長的 Timeout。當剩餘時間已不足以完成 Backoff+Execution+Verification 時,下一次 Attempt 不應被排入。

Retry Traffic 也是 Load。AWS Builders’ Library 提醒,Retry 會提高呼叫端成功機率,同時增加可能已過載的下游工作量;多層 Retry 會把 Failure 放大。1

Fallback 要保留 Contract,否則只是換一種失敗

Fallback 的目標是維持可接受服務,不是找到任何能回應的 Provider。

切換前至少要檢查:

  • Tool/Structured Output Compatibility
  • Context Capacity
  • Safety、Policy、Privacy 與 Data Residency
  • Required Credential、Region 與 Network Path
  • Quality Floor 與 Verification Requirement
  • Cost、Deadline 與 Rate Limit
  • Current RunState/Checkpoint 是否可被新 Adapter 解讀
  • 新路徑是否改變 Side-effect 或 Audit Semantics

例如原模型支援 Tool Calling 與大型 Context,Fallback Model 只支援純文字。API 回應成功,不代表原 Task Contract 還能成立。

Fallback 也可能是服務降級:

  • 即時搜尋改用標示 Freshness 的 Cache
  • 高精度 Reranker 改用基礎排序
  • Browser Automation 改成 Read-only API
  • 全量分析改成 Sampling

這些結果需要 degradedpartial 或其他明確狀態,並重新經過 Completion Gate。若 Fallback 降低了 Evidence Quality,舊路徑的 Verification 結論不能自動沿用。

Fallback 本身也要防止 Oscillation。兩個 Provider 若互相被設成備援,沒有 Cooldown 與 Attempt Budget 時,系統可能在兩個故障面之間來回彈跳。

Idempotency:相同 Intent、相同 Scope、相同 Operation Identity

Idempotency 是安全 Retry 的基礎之一,但它不等於「任何時候都可以重送」。

一個高風險 Operation Identity 通常至少綁定:

  • Tenant/Actor/Business Intent
  • Canonical Target 與 Arguments Digest
  • Operation Version
  • Idempotency Key
  • Provider/Account Scope
  • Dedupe Retention Window
  • Preconditions 與 Expected Receipt
{
  "operation_id": "refund-op-8f31",
  "idempotency_key": "order-4821-refund-v1",
  "request_hash": "sha256:illustrative-only",
  "actor": "support-agent-17",
  "tenant_id": "tenant-a",
  "action": "create_refund",
  "target": "payment_9027",
  "amount": 12800,
  "currency": "TWD",
  "precondition_version": 18,
  "status": "intent_committed",
  "provider_request_id": null,
  "dedupe_valid_until": "2026-07-09T10:00:00Z",
  "expected_receipt": "refund_id"
}

這個例子說明 Side Effect 前應先保存 Intent、Canonical Request Hash 與去重邊界。它不能證明 Provider 支援 Idempotency、Clock/Retention 正確,或 Business Preconditions 仍有效。

Stripe 的 Idempotency Key 會保存第一次請求的結果,並拒絕同一 Key 搭配不同參數;官方文件也說明 Key 有保留期限,過期後重用可能被視為新請求。這是 Provider Contract,不是跨系統的永久保證。23

因此「同一 Business Intent 永遠重用同一 Key」仍不夠精確。安全條件應是:

  • Canonical Intent 沒有改變
  • Request Hash 一致
  • Key 仍在 Provider 的 Dedupe Window 內
  • Actor/Tenant/Account Scope 一致
  • Preconditions 沒有過期
  • Retry Policy 明確允許

若其中一項改變,系統需要新的 Operation Version,不能偷偷把新 Intent 偽裝成舊 Retry。

Idempotent 也不必然等於 Retry-safe。 一個 API 最終狀態可能具備 Idempotency,但目前 Approval 已過期、Inventory 已改變、Credential 被撤銷,或 Deadline 已經失效。Retry Safety 是更大的判斷。

Unknown Outcome:先查 Authority,再判斷是否可重送

Unknown Outcome 最容易被「再試一次」放大。

Unknown outcome reconciliation flow

Figure 8-2|Timeout 後先保存 Unknown Outcome,依 Provider Contract 查詢 External Authority;not_found 只有在一致性與 Dedupe 條件成立時,才可能支持 Retry。

Intent committed
→ request submitted
→ external system may execute
→ response lost or deadline exceeded
→ local state = unknown

Harness 應使用 Operation ID、Idempotency Key、Provider Request ID、Business Key 或 Status API 做 Reconciliation,結果可能是:

  • completed
  • not_started_confirmed
  • not_found_inconclusive
  • partial
  • failed
  • unknown

這裡最容易教錯的是把 not_found 直接等同 not_started。Status Index 可能 Eventually Consistent,查詢 API 也可能只看得到部分 Region 或時間範圍。Negative Result 是否足以支持 Retry,必須由 Provider Contract 決定。

安全的下一步通常是:

  • completed:保存 Receipt,繼續後續 State Transition。
  • not_started_confirmed:在 Budget、Precondition 與 Approval 仍有效時 Retry。
  • not_found_inconclusive:等待 Consistency Window、使用第二種 Lookup,或保持 Unknown。
  • partial:停止後續不可逆步驟,進入 Forward Repair、Compensation 或人工處理。
  • unknown:保持 Unknown,不宣告 Success/Failure。

若 Provider 明確保證同一 Idempotency Key 在有效 Window 內安全去重,系統可能在 Outcome 仍 Unknown 時以同一 Canonical Request 重送。這不是 Blind Retry,而是依 Provider Contract 執行的 Deduplicated Resubmission。若沒有這項保證,就應先 Reconcile 或 Escalate。

Stripe 的 Low-level Error Guidance 也指出,Network Fault 可能出現在 Request Lifecycle 的不同位置,因此需要 Idempotency 與後續 State Inspection。3

Exactly-once 通常只能在局部邊界成立

單一 Database Transaction 內,可以用 Constraint 與 Atomic Commit 控制寫入。跨越 Network、Queue、Worker 與第三方 API 後,端到端 Exactly-once 往往無法由一個元件單獨保證。

更實際的設計組合是:

  • At-least-once Delivery
  • Stable Operation/Occurrence ID
  • Idempotent Consumer
  • Inbox/Outbox 或 Durable Operation Record
  • Deduplication Window
  • Reconciliation
  • Compensation/Forward Repair
  • Audit Receipt

Queue 可能 Redeliver。Worker 可能完成 External Operation,卻在 Ack 前 Crash。Scheduler 也可能在 Failover 後重送 Trigger。每個 Boundary 都需要回答「這是不是同一個 Intent」,不能靠「我第一次看到這個 Message」判定。

因此,文章中的 Exactly-once 應被理解為某個明確 Scope 內的 Property,例如「同一 Database Unique Key 只 Commit 一次」,而不是一條穿越所有系統的魔法保證。

Compensation 與 Saga:修復動作也可能失敗

Email 已送出、退款進入清算、Deployment 已服務流量、Webhook 已抵達第三方。這些 Effect 無法靠 Database Rollback 回到完全相同的世界。

系統可能選擇:

  • Forward Repair
  • Reversal Transaction
  • Snapshot Restore
  • Disable/Quarantine
  • Corrective Notification
  • Manual Reconciliation

Saga compensation chain

Figure 8-3|每個 Forward Step 與 Compensation 都保存自己的 Receipt、Idempotency 與 Failure State;後續修復不保證能回到原始世界,也不一定永遠採 Reverse Order。

Saga 將長交易拆成 Local Transaction。每個 Step 至少要定義:

  • forward action 與 precondition
  • durable receipt
  • retry owner
  • compensation 或 forward-repair action
  • compensation idempotency key
  • compensation receipt/failure state
  • dependency order
  • point of no return
  • manual escalation path

Compensation 是新的 Side Effect,因此也可能 Timeout、Partial 或 Unknown。它需要自己的 Recovery State Machine,不能在 Audit Log 裡只寫一個 rolled_back: true

補償順序常常是 Reverse Order,但不是定律。若 Email 已送出,先「撤銷 Email」通常不可能;若退款已完成而本地 Order State 更新失敗,最合理的路徑是 Forward Repair 本地狀態,而不是再做一次反向金流。

Create refund
→ update order state
→ send customer email
  • Refund 成功、Order Update 失敗:保存 Refund Receipt,修本地 State。
  • Email 失敗:Retry Notification,不重做 Refund。
  • Refund 只能部分完成:凍結後續通知,進入 Compensation/Manual Review。

Saga 的價值不是自動倒帶,而是把「整個 Workflow 失敗」拆成可以判斷、追蹤與個別恢復的 Local State。

Circuit Breaker:保護依賴,不替 Retry 做決定

當 Provider 持續失敗時,Retry 會增加 Queue、Latency 與 Cost。Circuit Breaker 在錯誤率或其他健康訊號跨過門檻後,讓新 Call Fail Fast。

常見狀態是:

  • closed:正常放行
  • open:短時間拒絕新 Call
  • half_open:只允許少量 Probe

Circuit Breaker 與 Retry 解決不同問題:Retry 嘗試跨過短暫故障,Breaker 則阻止系統持續呼叫大概率失敗的依賴。Microsoft 的官方 Pattern 也明確區分這兩種責任。4

可靠實作需要:

  • 明確 Failure Taxonomy,排除 Business Deny、Validation Error 等非健康故障
  • 依 Provider、Operation Class、Region 或 Tenant 選擇合理 Scope
  • Rolling Window、Cooldown 與 Probe Budget
  • Half-open Probe 的隔離容量
  • Shared State 或明確的 Instance-local Semantics
  • Fallback、Queue、Reject 或 Degraded Path
  • Recovery Evidence

Scope 太粗,一個 Endpoint 的故障可能讓整個 Provider 被封鎖;Scope 太細,又可能讓每個 Worker 都重新撞一次故障。Breaker State、Retry Budget 與 Admission Control 需要協調,否則一層說「停止呼叫」,另一層仍持續排 Background Retry。

Admission Control:不是所有工作都應該進入系統

Agent Workload 可能占用模型、Sandbox、Browser、外部 API 與長時間 Worker。只用一般 API Rate Limit,無法反映一個 Run 可能活十分鐘、開十個 Subagent、呼叫數百次 Tool 的成本。

開始 Run 前可以檢查:

  • Tenant Quota
  • Global/Per-tenant Concurrency
  • Model 與 Tool Capacity
  • Sandbox Availability
  • Estimated Cost
  • Priority
  • Deadline
  • Required Region/Credential
  • Side-effect Risk

若系統明知沒有資源,仍把每個工作都接受進 Queue,只是把即時拒絕換成延遲很久的失敗。

Backpressure、Bulkhead 與 Load Shedding

Admission control, backpressure and bulkheads

Figure 8-4|Admission Control 限制入口,Backpressure 讓上游減速,Bulkhead 隔離資源池,Load Shedding 在過載時保留控制與關鍵工作。

Backpressure 讓上游知道下游吃不下

當下游變慢時,可以:

  • 使用 Bounded Queue
  • 降低 Parallelism
  • 暫停新 Subagent
  • 延遲低優先工作
  • 拒絕已不可能在 Deadline 前完成的 Task
  • 回傳 Queue Position 或 Retry-After
  • 切換相容且有容量的 Provider

Google SRE 將 Overload 視為需要主動處理的可靠性問題,包含早期拒絕、Load Shedding 與 Degraded Response。系統若讓過量工作持續排隊,Latency、Timeout 與 Retry 會互相放大。5

Bulkhead 隔離故障範圍

可以依以下維度分開 Resource Pool:

  • Tenant
  • Interactive vs Batch
  • Read-only vs Side-effect Work
  • High-cost vs Low-cost Model
  • External Provider
  • Evaluation Traffic
  • Region

這樣一個租戶的巨大 Batch Job,不會吃掉所有 Interactive Approval 與 Cancellation 能力。

Load Shedding 要保留控制路徑

過載時的優先順序不應只看誰先進 Queue。通常應優先保留:

  1. Kill Switch 與 Cancellation
  2. Approval Response
  3. 已執行高風險副作用的 Reconciliation
  4. Active Critical Work
  5. Interactive Request
  6. Batch 與 Speculative Work

最糟的過載策略,是把取消工作與查詢事故狀態也一起塞在同一條爆滿 Queue 裡。系統不只失控,連煞車線都排隊。

Async Work Lifecycle:Acceptance、Execution、Retention、Delivery 分開

Slow Tool、Indexing Job、Benchmark、Background Agent 與大型 Extraction 都需要 Durable Work Identity。

常見執行狀態包括:

accepted → queued → running → completed
                            ↘ failed
                            ↘ cancelled
                            ↘ expired
                            ↘ unknown

Work Unit 至少保存:

  • work ID、type、parent run
  • owner/claim generation
  • created、queued、started、heartbeat、terminal timestamps
  • current state 與 state version
  • output/error reference
  • retry、idempotency 與 cancellation metadata
  • notification/delivery state
  • retention policy

Background Acceptance 是原始 Tool Call 的 Terminal Result

{
  "work_id": "work-391",
  "status": "accepted",
  "inspection_uri": "artifact://work-391/status",
  "cancellable": true,
  "side_effect_may_have_started": false
}

後續 completedfailedcancelled 是 Work Lifecycle Event,不是原始 Tool Request 的第二個 Result。

但 Terminal Work State 也不等於 Result 已交給 Parent。至少要分開:

  1. Work Terminal State committed
  2. Result Artifact retained
  3. Completion Event written to Outbox/Event Log
  4. Subscriber Delivery attempted
  5. Subscriber Ack/Parent Acceptance if the protocol supports it
  6. Retention/Tombstone transition

Result Retention 不應依賴一個模糊的「已通知」

有些 Protocol 沒有可靠 End-to-end Ack。此時系統不能假裝知道使用者已看見結果。它至少應保存 Delivery Record、Attempt、Subscriber Cursor 與 Result Expiry,允許 Pull/Replay。

清理可採分階段:

  • 先釋放 Sandbox、Browser、Worker 等大型資源
  • 保留 Terminal State 與 Result Artifact
  • 透過 Durable Outbox 發送 Completion Event
  • 依 Ack/Cursor/Retention Policy 決定何時縮成 Audit Tombstone

notification_sent 只能證明通知服務接受了 Message,不證明 Parent 已處理、使用者已看見,或結果已被接受。

Durable Execution:Replay Workflow,不等於 Replay 外部副作用

Durable Execution 讓 Workflow 在 Process、Worker 或 Infrastructure Failure 後,從 Event History 與已提交進度恢復。Temporal 以 Durable Workflow Execution、Event History 與 Deterministic Replay 為核心模型。6

這個能力仍有明確邊界:

  • Workflow Orchestration Code 需要 Determinism/Versioning
  • External API、Database 與其他 Non-deterministic Work 應放在 Activity/Tool Boundary
  • Activity 仍可能 At-least-once 執行
  • Side Effect 仍需要 Idempotency 與 Unknown-outcome Reconciliation
  • Workflow Code Upgrade 需要 Versioning/Patching
  • Invalid Business Input 不會因 Durable Retry 變有效

Temporal 的官方 Versioning 文件也明確指出,Replay 要求 Workflow Code 維持 Determinism,長時間執行需要 Patching 或 Worker Versioning。7

Durability 保存的是「流程知道自己走到哪裡」。它不會自動替外部世界提供 Exactly-once、Compensation 或正確 Business Decision。

Layered Context Degradation:Context 超限也要有恢復順序

Context Failure 也是 Reliability 問題。

當 Model Context 接近上限,不應直接把整段 Transcript 丟進一次黑箱摘要。比較安全的降級順序是:

  1. 先持久化大型 Raw Artifact
  2. 替換可重建的舊 Tool Payload
  3. 移除低價值中間對話
  4. 建立符合 Schema 的 Semantic Summary
  5. 必要時執行 Emergency Compaction
  6. 已無法保留關鍵狀態時 Safe Stop

裁剪時不能拆開:

  • Tool Request 與 Correlated Result
  • Approval Request 與 Decision
  • Background Acceptance 與 Work ID
  • Side-effect Operation 與 Receipt
  • Task Claim 與 Ownership Record

Summary 至少要保存:

  • Current Objective
  • User Constraint
  • Decision 與理由
  • Modified Resource
  • Test/Evidence
  • Open Failure
  • Active Operation ID
  • Remaining Work
  • Next Safe Action

若摘要後仍超限,或 Compaction 反覆失敗,Harness 應有 Circuit Breaker。繼續叫模型壓縮模型剛剛壓縮過的摘要,通常不會得到更濃縮的真理,只會得到更薄的證據。

Scheduled Work:每次 Occurrence 都需要獨立 Identity

Scheduled Agent Task 不應在 Timer Callback 裡直接跑完整 Workflow。

Schedule definition
→ occurrence created
→ queue delivery
→ agent consumer executes
→ terminal result retained
→ completion delivered
→ subscriber notified

Async and scheduled delivery lifecycle

Figure 8-5|Schedule Definition、Occurrence、Queue Delivery、Work Execution、Result Retention 與 Notification 是不同狀態;Redelivery 不會建立新的 Business Intent。

Job Definition 至少包含:

  • job ID、schedule、timezone
  • tenant、owner、payload reference
  • recurring/one-shot mode
  • misfire/catch-up policy
  • overlap/concurrency policy
  • expiry/deadline
  • allowed agent config version
  • approval requirement
  • schedule version

每次觸發還需要:

  • occurrence ID
  • scheduled-for timestamp
  • dedupe/idempotency key
  • definition version
  • delivery attempt
  • work ID

這樣 Queue Redelivery 或 Scheduler Failover 才能辨認「同一個 Occurrence 再送一次」,而不是建立兩份報告或兩次付款。

Durable Definition 不等於 Durable Execution

  • Durable Definition:Schedule 在 Restart 後仍存在。
  • Durable Trigger:到期 Occurrence 會被外部 Scheduler/Store 持久化。
  • Durable Execution:Worker Crash 後仍能從已提交進度恢復。
  • Durable Delivery:Terminal Result 可被 Pull/Replay,不因 Notification 暫時失敗而遺失。

保存 Cron Expression 只證明系統記得「想在什麼時候工作」。

Misfire、Catch-up 與 Overlap 都要明確

系統錯過時間點後可能:

  • skip
  • fire once now
  • replay missed occurrences
  • coalesce into one run

前一個 Occurrence 尚未完成時,還要決定:

  • skip new occurrence
  • buffer one/all
  • cancel previous
  • allow overlap

Temporal 的 Schedule 文件同樣把 Overlap Policy 與 Catch-up Window 視為不同控制,選擇不當會造成漏跑或恢復後的突發重放。8

Timezone 與 DST 也不能只留給 UI。02:30 在某些日期可能不存在或出現兩次;Occurrence Identity 應保存實際 Instant 與原始 Schedule Semantics。

Failure Testing:驗證 Invariant,不只看最後有沒有成功

Reliability Test 應在精確 Boundary 注入 Fault:

  • Intent Commit 前後
  • Request Send 前後
  • External Side Effect 完成但 Receipt 未保存
  • Queue Ack 前 Worker Crash
  • Compensation 中途
  • Circuit Half-open Probe
  • Schedule Trigger Persist 前後
  • Notification Outbox Commit 前後

至少覆蓋:

  • Model/Tool Timeout
  • Rate Limit/Provider Outage
  • Duplicate Delivery
  • Partial Batch Failure
  • Worker Crash/Checkpoint Recovery
  • Unknown Outcome/Eventually Consistent Status Query
  • Stale Claim Submission
  • Queue Backlog/Retry Storm
  • Circuit Open/Half-open
  • Context Compaction Failure
  • Scheduled Misfire/Overlap
  • Notification Redelivery

驗收不只問「最後成功了嗎」,還要檢查:

  • 是否有 Duplicate Side Effect
  • Attempt/Cost/Deadline 是否有界
  • RunState 是否可恢復
  • Receipt/Unknown State 是否完整
  • Queue Load 是否被 Retry 放大
  • User-visible State 是否誠實
  • Stop/Quarantine 是否生效
  • Compensation 是否有自己的 Audit Trail

Chaos Testing 不是隨機 Kill 幾個 Worker 就結束。每個 Test 應有 Hypothesis、Injected Fault、Expected State Transition、Forbidden Outcome 與 Recovery Evidence。若無法說出哪個 Invariant 被驗證,這次 Fault Injection 只是在製造噪音。

用退款案例走一次完整 Recovery

回到開頭的退款任務。

1. 建立 Intent

Harness 先建立 operation_id 與 Idempotency Key,保存訂單、金額、租戶、Actor、Policy Decision 與預期 Receipt。

2. 發送退款請求

Tool Adapter 呼叫支付服務。RunState 記錄 submitted,但尚未標記完成。

3. 發生 Timeout

Recovery Classifier 將錯誤標記為 UNKNOWN_SIDE_EFFECT_OUTCOME,不是普通 TOOL_TIMEOUT

4. Reconciliation

Harness 使用 Idempotency Key、Provider Request ID 或 Payment ID 查詢退款狀態。

  • 若已完成:保存 Refund Receipt,更新訂單狀態。
  • 若確認未開始:在 Budget 內以同一 Idempotency Key 重試。
  • 若 Partial:凍結後續 Email,交給補償或人工處理。
  • 若仍 Unknown:保持 unknown,定時 Poll,不宣告完成。

5. 訂單狀態更新衝突

資料庫回報 Version Conflict。這不是重新退款的理由。Harness Reload Canonical State,重新執行本地 State Transition。

6. 客戶通知失敗

Email Provider Rate Limit。Email Tool 使用自己的 Idempotency/Message ID,按 Retry-After 重試。退款不需要重做。

7. Completion Gate

Verifier 檢查:

  • 支付服務的 Refund Receipt
  • 訂單的 Canonical Refund State
  • Notification State
  • Operation Record 與 Audit Trail

只有模型說「退款已處理」不構成完成證據。

這個案例裡,同一個 Run 出現三種失敗:Unknown Outcome、State Conflict 與 Rate Limit。三者都叫 Error,但 Recovery Action 完全不同。

用十五個問題審查 Reliability Harness

  1. Failure 是否同時描述 Domain 與 Side-effect Certainty?
  2. 哪個元件持有 Recovery Decision Authority?
  3. Logical Attempt、SDK Retry、Queue Redelivery 是否進入同一 Ledger?
  4. Deadline 是否向下傳遞,還是每一層重新開始計時?
  5. 哪些操作具備明確的 Idempotency Scope、Request Hash 與 Dedupe Window?
  6. Retry-safe 是否重新檢查 Approval、Precondition、Credential 與 Budget?
  7. Timeout 後如何查 External Authority?not_found 是否可能只是尚未一致?
  8. Unknown Outcome 是否能保持 Unknown,而不被猜成 Success/Failure?
  9. Compensation 是否有自己的 Idempotency、Receipt、Failure State 與 Manual Path?
  10. Fallback 是否保留 Tool、Policy、Region、Evidence Quality 與 Completion Contract?
  11. Circuit Breaker、Retry Budget 與 Admission Control 是否共享合理 Scope?
  12. Control、Approval 與 Reconciliation Capacity 是否受到保護?
  13. Background Completion、Result Retention、Delivery 與 Parent Acceptance 是否分離?
  14. Scheduled Occurrence 是否有 Stable ID、Overlap、Misfire、Catch-up 與 Expiry Policy?
  15. 是否在 Commit Boundary 實際測過 Duplicate、Crash、Unknown、Partial 與 Overload?

若主要答案仍是「失敗就再跑一次」「Queue 會處理」「Workflow Engine 應該記得」或「最後看結果」,可靠性仍然建立在希望上。


理論轉實作:Part 08 檢查表

  • Failure Model 同時保存 Domain、Effect Certainty 與 Recovery Eligibility。
  • Recovery State Machine 為 Retry、Repair、Fallback、Reconcile、Compensate、Quarantine 與 Stop 指定 Authority。
  • Retry 有單一 Owner 或共享 Attempt Ledger,並受 Deadline、Cost、Backoff 與 Jitter 限制。
  • Fallback 會標示 Degradation,並重新通過 Compatibility 與 Completion Gate。
  • High-risk Operation 在執行前保存 Canonical Intent、Request Hash、Operation ID、Idempotency Scope 與 Expected Receipt。
  • Retry 檢查 Dedupe Window、Precondition、Approval、Credential 與相同 Canonical Request。
  • Unknown Outcome 有獨立狀態;Negative Lookup 不會未經 Provider Contract 就被當成 not_started
  • External Side Effect 具備 Receipt、Missing-receipt/Unknown State 與 Reconciliation Path。
  • Saga 支援 Forward Repair;Compensation 有自己的 Idempotency、Receipt、Failure 與 Manual Escalation。
  • Circuit Breaker 排除 Business Error,並定義 Scope、Cooldown、Half-open Probe 與 Recovery Evidence。
  • Admission、Backpressure、Bulkhead 與 Load Shedding 保留 Cancellation、Approval 與 Reconciliation Capacity。
  • Async Work 分開 Acceptance、Execution、Terminal Result、Retention、Delivery 與 Parent Acceptance。
  • Durable Execution 沒有被誤認為 External Exactly-once;Workflow Replay 與 Activity Side Effect 分開。
  • Context Degradation 先保存 Evidence,且不拆 Request/Result、Approval/Decision 或 Operation/Receipt。
  • Scheduled Work 使用 Occurrence ID,並定義 Timezone、DST、Misfire、Catch-up、Overlap、Expiry 與 Config Version。
  • Failure Test 在 Durable Boundary 驗證 State Transition、Forbidden Outcome 與 Recovery Evidence。

Part 09 會接著處理另一條獨立邊界:一個操作即使能可靠執行,也不代表它有權執行。下一篇會從 Identity、Permission、Approval、Sandbox、Credential、Tenant Boundary 與 Egress Policy 建立安全鏈。

References

Footnotes

  1. Amazon Web Services, Timeouts, retries, and backoff with jitter, accessed 8 July 2026. 2

  2. Stripe, Idempotent requests, accessed 8 July 2026.

  3. Stripe, Advanced error handling, accessed 8 July 2026. 2

  4. Microsoft Azure Architecture Center, Circuit Breaker pattern, accessed 8 July 2026.

  5. Google, Handling overload and Addressing cascading failures, accessed 8 July 2026.

  6. Temporal, Temporal Platform Documentation and Understanding Temporal, accessed 8 July 2026.

  7. Temporal, Safely deploying changes to Workflow code, accessed 8 July 2026.

  8. Temporal, Schedule and Troubleshoot missed Schedule Actions, accessed 8 July 2026.