Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

ADR-006: Circuit Breaker on Safety Kernel Client

  • Status: Accepted
  • Date: 2026-01-15

Context

The Safety Kernel is a synchronous dependency on the scheduler hot path (see ADR-001). If the Safety Kernel becomes unavailable or slow, the scheduler cannot dispatch any jobs. Without a fallback mechanism, a Safety Kernel outage causes total platform unavailability.

Decision

The scheduler's Safety Kernel client uses a circuit breaker pattern with configurable thresholds.

States

CLOSED (normal) → OPEN (tripped) → HALF-OPEN (probe)
↑ │
└────────────────────────────────────┘
  • Closed: All requests forwarded to Safety Kernel normally.
  • Open: After N consecutive failures, the circuit trips. Requests are short-circuited with a fallback decision (configurable: deny-all or allow-all).
  • Half-Open: After a cooldown period, one probe request is sent. If it succeeds, the circuit closes. If it fails, the circuit reopens.

Configuration

ParameterDefaultDescription
Failure threshold3Consecutive failures before tripping (safetyCircuitFailBudget)
Cooldown period30sTime in open state before probing (safetyCircuitOpenFor)
Half-open probes3Probe budget while half-open (safetyCircuitHalfOpenMax)
Successes to close2Consecutive probe successes that re-close the circuit (safetyCircuitCloseAfter)
Timeout per call2sgRPC deadline for safety-kernel calls (safetyTimeout)

Behavior When the Circuit Is Open

The circuit breaker itself does not decide allow/deny. When the circuit is open, SafetyClient.Check returns a SafetyUnavailable record (reason "safety kernel circuit open"); the scheduler then resolves what to do with the job using its fail mode:

  • fail-closed (default): the job is not allowed through — the scheduler requeues it via RetryAfter until the kernel recovers. No unchecked action executes. Set with WithInputFailMode("closed").
  • fail-open (opt-in): the job is dispatched with a SafetyAllow decision, tagged safety_bypassed=true, and a dedicated safety-bypass audit event is emitted. Set with WithInputFailMode("open").

Fail mode is resolved per request: a per-tenant FailModeResolver overrides the global flag when configured (isInputFailOpenForTenant), otherwise the global default (fail-closed) applies. The same model governs the async output-safety check (isAsyncFailOpenForTenant).

Observability

  • IncSafetyUnavailable(topic) — incremented whenever the kernel is unavailable (circuit open or call error).
  • IncInputFailOpen(topic) — incremented when a job is allowed through under fail-open.
  • Redis counter cordum:scheduler:input_fail_open_total — durable fail-open tally.
  • A safety-bypass audit event is emitted on every fail-open dispatch so SIEM can detect bypass-on-unavailable without parsing logs.

Key source files:

  • core/controlplane/scheduler/circuit_breaker.goRedisCircuitBreaker (distributed breaker + local fallback)
  • core/controlplane/scheduler/safety_client.go — breaker thresholds, safetyTimeout, open-circuit handling
  • core/controlplane/scheduler/engine.goSafetyUnavailable handling, fail-open/fail-closed resolution

Consequences

Positive:

  • Platform degrades gracefully instead of hanging on Safety Kernel outage
  • Configurable fail mode (per-tenant or global) lets operators choose safety vs availability tradeoff
  • Automatic recovery when Safety Kernel comes back (no manual intervention)
  • Observability signals (fail-open metrics + audit events) enable alerting on bypass-on-unavailable

Tradeoffs:

  • fail-closed (default) means jobs are requeued and processing stalls during an outage
  • fail-open means unchecked actions can execute (risk) — gated behind explicit opt-in + audit trail
  • Circuit breaker adds complexity to the evaluation path
  • Probe requests during half-open may see stale results