# Send 失敗與 fallback

模型呼叫逾時、被限流、額度或 context 用盡時的處理，以及 fallback 如何挑下一個模型。

## Send 失敗處理

`Agent.Send()` 失敗採逐級升高處理，而非盲目重試：

| 失敗類型 | 行為 |
|---|---|
| Timeout | 於同一模型最多重試 `MAX_RETRY_TIMES`（3）次——v1.1.2 起共 4 次嘗試，先前為 3 次——間隔 `SEND_TIMEOUT_RETRY_INTERVAL`（15 秒） |
| Rate limit（HTTP 429 或 rate-limit 訊息） | 註冊 30 分鐘 cooldown，於同一模型在 5 秒、10 秒、15 秒後重試；之後切換到下一個 fallback |
| 額度耗盡（HTTP 402 / 403 或額度 / 帳務訊息） | 註冊 30 分鐘 cooldown，立即切換到下一個 fallback |
| Context 長度超出 | v1.1.1 起先壓縮歷史（與超出 token 預算時相同的 `ExtractContextHistories` → `ToolHistory`）後重送；無法壓縮時才裁掉最舊的一組對話（`compact.TrimFallback`）重送；只有在無可裁剪時才中止 |
| 串流中無回應 | 每 30 秒 health probe（`UNRESPONSIVE_PROBE_INTERVAL`），探測失敗後每 10 秒重試（`HEALTH_CHECK_TIMEOUT`），失敗 3 次（`MAX_RETRY_TIMES`）即切換模型 |
| 其他錯誤 | 切換到下一個 fallback 模型；無可用 fallback 時中止 |

Fallback 候選來自 `exec/model.Route`（v1.2.1 前為 `ResolveAgent`）依優先順序建立的清單，自 v1.1.0 起不含 `pass` tier 模型。`exec/model.Next`（v1.2.1 前為 `nextAgent`）會略過與失敗模型同供應商的模型（略過 context window 容不下輸入之模型的行為移除於 v1.2.1），對每個候選做 health check（`HEALTH_CHECK_TIMEOUT`，10 秒），並重建清單直到跑完 `MAX_RETRY_TIMES`（3）輪重試——v1.1.2 起共 4 輪，先前為 3 輪。綁定特定模型（`auto` 以外）的 session 不會 fallback。

切換模型會清空 `ToolHistories`（或套用 `compact.RawToolFallback`）、重置去重表並歸零計數器——新模型不會繼承失敗模型的殘留狀態。

## Timeout 層級

| 層級 | 值 | 攔截的問題 |
|---|---|---|
| Provider HTTP client | 由 `go-llm-router` 各 provider 內部設定 | 傳輸層卡住 |
| `AGENT_SEND_TIMEOUT_SEC` | `config.json` 的 `limits.agent_send_timeout_seconds`，預設 `600`；v1.1.2 起小於預設的值會被忽略 | exec 層以 `context.WithTimeout` 設下的上限 |
| 無回應 watchdog | 每 30 秒探測；探測失敗後每 10 秒重試；失敗 3 次即切換模型 | 串流卡住但未報錯 |
| Health check | 10 秒 | 各 fallback 候選的存活探測 |
