Part of #22 (Meridian reliability umbrella). P0.
Problem
Two stacked retry layers multiply requests into an already-degraded upstream:
- Session-level auto-retry (
packages/coding-agent/src/core/agent-session.ts): _isRetryableError treats every stopReason === "error" as retryable except a short exclusion list — 429/500/529/overloaded all retry identically with fixed exponential backoff (2s/4s/8s, default maxRetries: 3), no jitter, no Retry-After honoring, no kind-awareness, even though the failure kind is already available via _getProviderStreamFailureKind (packages/ai/src/utils/stream-failure.ts maps 429→rate_limit, 529→overloaded).
- SDK-level retries (
packages/ai/src/providers/anthropic.ts passes maxRetries to the Anthropic client) stack underneath: worst case ≈ 4 × (1 + sdkMaxRetries) upstream attempts per failed turn, per agent — multiplied by N concurrent RLM children hitting one Claude Max account.
maxRetryDelayMs is dead code: declared in packages/ai/src/types.ts, threaded through simple-options.ts, agent.ts, sdk.ts, settings-manager.ts — and never read by any provider. The documented "fail fast when the server asks for a long wait" behavior does not exist.
after_provider_response exposes response headers (sdk.ts) but nothing acts on retry-after.
Observed via Meridian telemetry: bursts of a dozen failures in ~15s during an overload/session-limit window, each retry replaying and cold-caching (see #22).
Proposed shape
- Make
_isRetryableError/_handleRetryableError kind-aware: overloaded/rate_limit get longer, jittered backoff with a ceiling and honor upstream Retry-After when present; auth failures stop retrying immediately (today an expired-login window burns the full ladder).
- Implement
maxRetryDelayMs in packages/ai for real, or delete it everywhere — no silent no-op settings.
- When the session-level retry loop is active, default provider-level
maxRetries to 0 so exactly one layer owns retry policy.
Acceptance
- A simulated 429 with
Retry-After: 30 produces one upstream attempt and a ≥30s wait, not 12 attempts in 15s.
- Auth-kind failure aborts the ladder with a clear message.
- Setting
maxRetryDelayMs observably caps/refuses long waits (test at the provider seam).
Part of #22 (Meridian reliability umbrella). P0.
Problem
Two stacked retry layers multiply requests into an already-degraded upstream:
packages/coding-agent/src/core/agent-session.ts):_isRetryableErrortreats everystopReason === "error"as retryable except a short exclusion list — 429/500/529/overloaded all retry identically with fixed exponential backoff (2s/4s/8s, defaultmaxRetries: 3), no jitter, noRetry-Afterhonoring, no kind-awareness, even though the failure kind is already available via_getProviderStreamFailureKind(packages/ai/src/utils/stream-failure.tsmaps 429→rate_limit, 529→overloaded).packages/ai/src/providers/anthropic.tspassesmaxRetriesto the Anthropic client) stack underneath: worst case ≈4 × (1 + sdkMaxRetries)upstream attempts per failed turn, per agent — multiplied by N concurrent RLM children hitting one Claude Max account.maxRetryDelayMsis dead code: declared inpackages/ai/src/types.ts, threaded throughsimple-options.ts,agent.ts,sdk.ts,settings-manager.ts— and never read by any provider. The documented "fail fast when the server asks for a long wait" behavior does not exist.after_provider_responseexposes response headers (sdk.ts) but nothing acts onretry-after.Observed via Meridian telemetry: bursts of a dozen failures in ~15s during an overload/session-limit window, each retry replaying and cold-caching (see #22).
Proposed shape
_isRetryableError/_handleRetryableErrorkind-aware:overloaded/rate_limitget longer, jittered backoff with a ceiling and honor upstreamRetry-Afterwhen present;authfailures stop retrying immediately (today an expired-login window burns the full ladder).maxRetryDelayMsinpackages/aifor real, or delete it everywhere — no silent no-op settings.maxRetriesto 0 so exactly one layer owns retry policy.Acceptance
Retry-After: 30produces one upstream attempt and a ≥30s wait, not 12 attempts in 15s.maxRetryDelayMsobservably caps/refuses long waits (test at the provider seam).