跳到內容

非壓縮自動重試策略

本文件描述 AgentSession 中標準的 API 錯誤重試路徑。

本文件明確排除透過自動壓縮進行的上下文溢位恢復。溢位由壓縮邏輯處理,並另行記載於 compaction.md

重試與壓縮從相同的 agent_end 路徑進行檢查,但它們被刻意分離:

  1. agent_end 檢查最後一則助理訊息。
  2. #isRetryableError(...) 首先執行。
  3. 如果啟動重試,該回合會跳過壓縮檢查。
  4. 上下文溢位錯誤被硬性排除在重試分類之外(isContextOverflow(...) 會短路重試)。
  5. 因此溢位會落入 #checkCompaction(...) 而非標準重試。

所以:過載/速率限制/伺服器/網路類型的失敗使用此重試策略;上下文視窗溢位則使用壓縮恢復。

#isRetryableError(...) 需要同時滿足以下所有條件:

  • 助理的 stopReason === "error"
  • errorMessage 存在
  • 訊息不是上下文溢位
  • errorMessage 匹配 #isRetryableErrorMessage(...)

目前可重試的模式集合(基於正規表達式):

  • overloaded
  • rate limit / usage limit / too many requests
  • 類 HTTP 伺服器狀態碼:429、500、502、503、504
  • service unavailable / server error / internal error
  • connection error / fetch failed
  • retry delay 相關措辭

這是字串模式分類,而非型別化的提供者錯誤碼。

重試使用的工作階段狀態:

  • #retryAttempt: number0 表示閒置)
  • #retryPromise: Promise<void> | undefined(追蹤進行中的重試生命週期)
  • #retryResolve: (() => void) | undefined(解決 #retryPromise
  • #retryAbortController: AbortController | undefined(取消退避等待)

流程(#handleRetryableError):

  1. 讀取 retry 設定群組。
  2. 如果 retry.enabled === false,立即停止(false,不啟動重試)。
  3. 遞增 #retryAttempt
  4. 首次嘗試時建立 #retryPromise(鏈中的第一次嘗試)。
  5. 如果嘗試次數超過 retry.maxRetries,發出最終失敗事件並停止。
  6. 計算延遲:retry.baseDelayMs * 2^(attempt-1)
  7. 對於用量限制錯誤,解析重試提示並呼叫認證儲存(markUsageLimitReached(...));如果提供者/模型切換成功,將延遲強制設為 0
  8. 發出 auto_retry_start
  9. 從代理執行時狀態中移除尾端的助理錯誤訊息(保留在持久化的工作階段歷史中)。
  10. 以支援中止的方式進入等待。
  11. 喚醒後,透過 setTimeout(..., 0) 排程 agent.continue()

#retryAttempt 在以下情況重設為 0

  • 重試開始後首次成功的非錯誤、非中止助理訊息(發出 auto_retry_end { success: true }
  • 退避等待期間的重試取消
  • 最大重試次數已超過的路徑

#retryPromise 在重試鏈結束時(成功、取消或超過最大次數)透過 #resolveRetry() 解決/清除。

設定:

  • retry.enabled(預設 true
  • retry.maxRetries(預設 3
  • retry.baseDelayMs(預設 2000

嘗試次數編號:

  • 嘗試計數器在最大值檢查之前遞增
  • 開始事件使用目前的嘗試次數(從 1 開始)
  • 超過最大次數的結束事件回報 attempt: this.#retryAttempt - 1(最後嘗試的重試次數)

使用預設設定的退避序列:

  • 第 1 次嘗試:2000 毫秒
  • 第 2 次嘗試:4000 毫秒
  • 第 3 次嘗試:8000 毫秒

延遲覆寫輸入僅用於用量限制處理路徑,且僅用於影響認證儲存的模型/帳戶切換決策。在主要的非壓縮重試路徑中,退避保持本地指數延遲,除非切換成功(delayMs = 0)。

abortRetry()

  • 中止 #retryAbortController(如果存在)
  • 解決重試 Promise(#resolveRetry()),使等待者不被阻塞

如果中止發生在等待期間,捕獲路徑會發出:

  • auto_retry_end { success: false, finalError: "Retry cancelled" }
  • 重設嘗試次數/控制器

abort() 在中止活動的代理串流之前呼叫 abortRetry()。這確保當使用者發出一般性中止時,重試退避會被取消。

收到 auto_retry_start 時,EventController:

  • Esc 處理器切換為 session.abortRetry()
  • 呈現載入文字:Retrying (attempt/maxAttempts) in Ns… (esc to cancel)

收到 auto_retry_end 時,恢復先前的 Esc 處理器並清除載入狀態。

prompt() 最終在 agent.prompt(...) 回傳後等待 #waitForRetry()

效果:

  • 一次提示呼叫在任何已啟動的重試鏈完成(成功/失敗/取消)之前不會完全解決
  • 重試生命週期是一次邏輯提示執行邊界的一部分

這防止呼叫者過早將正在重試的回合視為已完成。

定義在設定結構描述的 retry 群組下:

  • retry.enabled
  • retry.maxRetries
  • retry.baseDelayMs

工作階段中的程式化切換:

  • setAutoRetryEnabled(enabled) 寫入 retry.enabled
  • autoRetryEnabled 讀取 retry.enabled
  • isRetrying 回報重試生命週期 Promise 是否活動中

RPC 命令介面:

  • set_auto_retrysession.setAutoRetryEnabled(command.enabled)
  • abort_retrysession.abortRetry()

客戶端輔助方法:

  • RpcClient.setAutoRetry(enabled)
  • RpcClient.abortRetry()

兩個命令都回傳成功回應;重試進度/失敗詳情來自串流的工作階段事件,而非命令回應的酬載。

工作階段層級的重試事件:

  • auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }
  • auto_retry_end { success, attempt, finalError? }

傳播:

  • 透過 AgentSession.subscribe(...) 發出
  • 以擴充功能事件的形式轉發至擴充功能執行器
  • 在 RPC 模式中,直接以 JSON 事件物件轉發(session.subscribe(event => output(event))
  • 在 TUI 中,由 EventController 消費以呈現載入/錯誤 UI

最終失敗呈現:

  • 超過最大次數或取消時,auto_retry_end.success === false
  • TUI 顯示:Retry failed after N attempts: <finalError>
  • 擴充功能/鉤子收到具有相同欄位的 auto_retry_end
  • RPC 消費者在 stdout 串流上收到相同的事件物件

當發生以下任何情況時,重試會停止且不會自動繼續:

  • retry.enabled 為 false
  • 錯誤未被分類為可重試
  • 錯誤為上下文溢位(委派至壓縮路徑)
  • 超過最大重試次數
  • 使用者取消重試(重試載入器期間使用 abort_retryEsc
  • 全域中止(abort)會先取消重試

在計數器重設後,未來遇到可重試錯誤時仍可啟動新的重試鏈。

  • 分類是正規表達式文字匹配;此處未使用提供者特定的結構化錯誤。
  • 重試在重新繼續前會從執行時上下文中移除失敗的助理錯誤,但工作階段歷史仍保留該錯誤條目。
  • RpcSessionState 目前公開 autoCompactionEnabled 但未公開 autoRetryEnabled 欄位;RPC 呼叫者必須自行追蹤切換狀態,或透過其他 API 查詢設定。