IBEX Harness
DocsBenchmarksBlogChangelogRoadmap
GitHub
IBEX Harness

Documentation

Architecture Decision RecordsADR-0002: Repository foundation bootstrapADR-0003: Branch protection and merge policyADR-0004: Protobuf and code generation policyADR-0005: Postgres migration strategyADR-0006: Auth protobuf contract (`ibex.auth.v1`)ADR-0007: Auth token validation implementationADR-0008: Security scanning and CI quality gatesADR-0009: Permission bitmap layoutADR-0010: Cryptography policyADR-0011: Proxy auth gRPC client and middlewareADR-0012: Proxy request normalization (OpenAI chat)ADR-0013: Proxy input validation and stable error envelopeADR-0014: Core domain migration sequencingADR-0015: Proxy rate limit skeleton (Phase 1)ADR-0016: Proxy agent identity verification (Phase 1)ADR-0017: Request ID and trace context strategy (Phase 1)ADR-0018: Graceful shutdown contract (Phase 1)ADR-0019: OpenTelemetry provider configuration (Phase 1)ADR-0020: Shared package boundaries — `packages/config` and `packages/apierror`ADR-0021: Prometheus Metric Catalog (Phase 1)ADR-0022: Health check contract (Phase 1)ADR-0023: Docs site architecture (Phase 1.5)ADR-0024: Benchmark data publishing modelADR-0025: LLM provider abstractionADR-0026: OpenAI client designADR-0027: Streaming dual-write strategyADR-0028: Auth cache designADR-0029: Token revocation propagation via Redis pub/subADR-0030: Directive versioning strategyADR-0031: System prompt injection strategyADR-0032: Session data model and retentionADR-0033: ClickHouse llm_traces schema and retentionADR-0034: Proxy overhead performance measurement methodologyADR-0035: Chat Idempotency-Key Redis dedupeADR-0038: Context assembly service design and gRPC contractADR-0039: Proxy Postgres ownership for session and directive storesADR-0040: Anthropic provider adapterADR-0041: Model capability registryADR-0042: Self-hosted OpenAI-compatible LLM adapterADR-0043: Tokenizer registry architectureADR-0044: Non-streaming response pipelineADR-0045: Streaming response transformationADR-0046: Embedder interface and profile registryADR-0047: Memory temporal validity foundationADR-0048: Memory multi-label categoriesADR-0049: Memory relationship graph readinessADR-0050: MCP server skeleton (transport, auth, audit)ADR-0051: Local LGTM observability stack (Phase 2.5 exit pull-forward)ADR-0052: Memory schema v2 expand (HNSW, quality columns)ADR-0053: Vector store abstraction and composite scoring v2ADR-0054: In-process Presidio PII detection in memoryADR-0055: Memory write-path exact and near-duplicate dedupADR-0056: Temporal-interval-aware conflict detectionADR-0057: Memory write-path persistence and orchestrationADR-0058: Semantic search FTS fallback triggerADR-0059: Hot cache read pathADR-0060: Memory service tenant isolation architectureADR-0061: Retrieval-quality benchmark methodologyADR-0062: Worker task observability and dead-letter handlingADR-0063: Extraction output schema v2ADR-0064: Extraction provider registry and batch executionADR-0065: Extraction batch idempotency and turn pointer orderingADR-0066: Extraction quality evaluation harnessADR-0067: Context capability catalog generate-and-diff for token budgetsADR-0068: Composite scoring-time relevance gateADR-0069: Context packer bounded DP knapsackADR-0070: Context formatting locked ordering and session nonceADR-0071: Context assembly gRPC degradation ladder and retrieval deadlineADR-0072: Extraction enqueue via worker HTTP (not Celery Redis wire format)ADR-0073: Organization Lifecycle and Cascading SuspensionADR-0074 Provider Credential StorageADR-0075 Per-Org Model Routing PolicyADR-0076: Circuit Breaker per ProviderADR-0077: Provider Fallback RoutingADR-0078: Operator runtime topology & environment contract (4.P.0)ADR-0079: Operator session RS256 dual-verify and TOTP step-upIBEX Console service rename and boundaryADR-0081: Fail-closed Proxy runtime controlsD2 metadata-only trace read contractD2 publication, identity, query, and lifecycle contract
ADRs›ADR-0076: Circuit Breaker per Provider
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

  1. Extend packages/circuitbreaker in place (no second package). Additive Settings fields: Window, BucketPeriod, MinSamples, FailureRateThreshold.
  2. Dual trip modes on one API:
    • Window == 0 (default): consecutive failures via MaxFailures — byte-compatible with ADR-0042 for self-hosted.
    • Window > 0: rolling failure-rate via gobreaker Interval/BucketPeriod and ratio TotalFailures / validRequests, tripping only when validRequests >= MinSamples and rate >= FailureRateThreshold.
  3. Independent named breakers for openai, anthropic, and openaicompatible, each keyed by provider.Name(). Self-hosted keeps consecutive defaults; OpenAI and Anthropic default to rolling (30s window, 3s buckets, min 10 samples, 50% failure rate). Requests with APIKeyOverride or BaseURLOverride (org BYO credentials) bypass the shared provider breaker so a BYO upstream cannot open—or be blocked by—the platform provider circuit.
  4. Env vars: do not repurpose IBEX_PROVIDER_CIRCUIT_BREAKER_FAILURES. New knobs: WINDOW_SECONDS, BUCKET_PERIOD_SECONDS, MIN_SAMPLES, FAILURE_RATE. Shared COOLDOWN_SECONDS remains the open-state timeout / Retry-After for all providers. Rolling knobs default only when unset; explicit non-positive values are preserved and fail at circuitbreaker.New during bootstrap.
  5. Retry-After: gobreaker does not expose remaining open expiry publicly; circuit-open returns *OpenError{RetryAfter: CoolDown} mapped into ProviderError.RetryAfter and propagated by provider.mapReason into apierror.Error so WriteHTTP emits the header.
  6. Anthropic uses the same local Breaker interface + ErrOpen mapping shape as openaicompatible (no proxy-level interceptor).
  7. Context Assemble breaker wrap remains deferred (unchanged).
  8. Client-fault HTTP classification: ProviderError (or any HTTPStatus() int error) 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.
  9. Known gobreaker quirk: when BucketPeriod > 0, gobreaker rounds Interval up to a multiple of BucketPeriod. 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 > 0 with invalid rate/samples, or rolling fields without Window) fail at circuitbreaker.New / bootstrap.
  • Client-facing circuit-open detail is provider-agnostic: "LLM provider circuit breaker is open".
  • A burst of 400 Bad Request from a misconfigured client will not open the breaker; sustained 429/500/timeouts will.

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0075 Per-Org Model Routing PolicyNextADR-0077: Provider Fallback Routing

On this page

  • Context
  • Decision
  • Consequences
0%