ADRs
ADR-0076: Circuit Breaker per Provider
Dual consecutive/rolling trip model, independent per-provider breakers, and Retry-After on circuit_open
ADR-0076: Circuit Breaker per Provider
- Status: Accepted
- Date: 2026-09-12
- Authors: IBEX Harness team
- Milestone: 4.C.3 Circuit breaker per provider
- Related: ADR-0042 §5 (self-hosted consecutive breaker)
Context
packages/circuitbreaker already shipped as a consecutive-failure wrapper over sony/gobreaker/v2, wired only for the self-hosted OpenAI-compatible adapter. Hosted OpenAI and Anthropic had no breakers, so a single cloud outage could not be isolated, and circuit-open responses lacked Retry-After despite docs implying it. Milestone 4.C.3 requires per-provider isolation, rolling error-rate trip for cloud providers, and HTTP 503 + Retry-After on open.
Decision
- Extend
packages/circuitbreakerin place (no second package). AdditiveSettingsfields:Window,BucketPeriod,MinSamples,FailureRateThreshold. - Dual trip modes on one API:
Window == 0(default): consecutive failures viaMaxFailures— byte-compatible with ADR-0042 for self-hosted.Window > 0: rolling failure-rate via gobreakerInterval/BucketPeriodand ratioTotalFailures / validRequests, tripping only whenvalidRequests >= MinSamplesand rate>= FailureRateThreshold.
- Independent named breakers for
openai,anthropic, andopenaicompatible, each keyed byprovider.Name(). Self-hosted keeps consecutive defaults; OpenAI and Anthropic default to rolling (30s window, 3s buckets, min 10 samples, 50% failure rate). Requests withAPIKeyOverrideorBaseURLOverride(org BYO credentials) bypass the shared provider breaker so a BYO upstream cannot open—or be blocked by—the platform provider circuit. - Env vars: do not repurpose
IBEX_PROVIDER_CIRCUIT_BREAKER_FAILURES. New knobs:WINDOW_SECONDS,BUCKET_PERIOD_SECONDS,MIN_SAMPLES,FAILURE_RATE. SharedCOOLDOWN_SECONDSremains the open-state timeout / Retry-After for all providers. Rolling knobs default only when unset; explicit non-positive values are preserved and fail atcircuitbreaker.Newduring bootstrap. Retry-After: gobreaker does not expose remaining open expiry publicly; circuit-open returns*OpenError{RetryAfter: CoolDown}mapped intoProviderError.RetryAfterand propagated byprovider.mapReasonintoapierror.ErrorsoWriteHTTPemits the header.- Anthropic uses the same local
Breakerinterface +ErrOpenmapping shape asopenaicompatible(no proxy-level interceptor). - Context Assemble breaker wrap remains deferred (unchanged).
- Client-fault HTTP classification:
ProviderError(or anyHTTPStatus() interror) with status in 4xx except 429 does not count as a breaker failure — consecutive mode treats them as success (streak reset); rolling mode excludes them from window counts (same path as cancel/deadline). 429 and 5xx/timeouts/transport errors still trip. This avoids false-opens under bad client traffic before 4.C.4 fallback routing. - Known gobreaker quirk: when
BucketPeriod > 0, gobreaker roundsIntervalup to a multiple ofBucketPeriod. Operators should prefer Window divisible by BucketPeriod (production default 30s/3s).
Consequences
- Operators can tune cloud rolling knobs independently of self-hosted consecutive threshold.
- Misconfigured rolling settings (
Window > 0with invalid rate/samples, or rolling fields withoutWindow) fail atcircuitbreaker.New/ bootstrap. - Client-facing circuit-open detail is provider-agnostic:
"LLM provider circuit breaker is open". - A burst of
400 Bad Requestfrom a misconfigured client will not open the breaker; sustained429/500/timeouts will.
Was this page helpful?
Edit on GitHub
Last updated on