Skip to content

fix(agent): kind-aware retry with Retry-After and jitter; implement dead maxRetryDelayMs; stop SDK-times-session retry multiplication #24

Description

@rynfar

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pkg:aiAffects packages/aipkg:coding-agentAffects packages/coding-agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions