Circuit Breaker
The budget circuit breaker monitors the rate of cost accumulation across a sliding window. When the spend rate exceeds the configured threshold, it trips open and blocks all traffic for the policy until it auto-resets or is manually reset.
This is distinct from the cost-based budget algorithm: the budget limiter enforces a total period spend cap, while the circuit breaker triggers on velocity — a sudden spike in spending even if the total budget hasn’t been reached.
Configuration
{
"spec": {
"circuit_breaker": {
"enabled": true,
"spend_rate_threshold_per_minute": 500,
"action": "reject",
"auto_reset_after_minutes": 5,
"alert": true
},
"rules": [ ... ]
}
}
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
enabled |
boolean | yes | — | Must be true to activate. |
spend_rate_threshold_per_minute |
number | yes if enabled | — | Positive. Trips when the rolling per-minute spend rate reaches this value. |
action |
string | no | "reject" |
Currently only "reject" is supported. |
auto_reset_after_minutes |
number | no | 0 |
If > 0, the breaker automatically closes after this many minutes. 0 = never auto-resets. |
alert |
boolean | no | false |
If true, emits a structured circuit_breaker_tripped alert event when tripping. |
How it works
The circuit breaker uses a weighted two-window rolling rate over 60-second windows.
State
Two keys are maintained per composite limit key:
cb_state:{limit_key} → "open:{opened_at_epoch}" (when tripped)
cb_rate:{limit_key}:{window} → accumulated_cost (TTL = 120s)
The {window} is floor(now / 60) * 60 — the start of the current 60-second bucket.
Rate calculation
On each request (before the cost for that request is applied):
weight = elapsed_in_current_window / 60
spend_rate = previous_window_total × (1 − weight) + current_window_total
The weighted sum gives a smooth estimate that avoids sharp step changes at window boundaries.
Trip condition
If spend_rate >= spend_rate_threshold_per_minute:
- The
cb_statekey is set to"open:{current_time}" - The request is rejected with
reason: circuit_breaker_open - If
alert: true, an event is queued for delivery to the SaaS control plane
Auto-reset
If auto_reset_after_minutes > 0, the circuit breaker transitions to closed when:
current_time - opened_at >= auto_reset_after_minutes × 60
On the next request after the reset interval, the cb_state key is deleted and the breaker behaves as closed.
Manual reset
To reset a tripped circuit breaker immediately, increment bundle_version and push a new bundle. The new bundle initialises fresh state.
Response when tripped
HTTP 429 Too Many Requests
X-Fairvisor-Reason: circuit_breaker_open
Retry-After: 1
Policy/rule attribution is available in debug session headers (X-Fairvisor-Debug-*).
Cost source for the circuit breaker
The circuit breaker uses the first rule’s first limit_key descriptor as the partition key, and the cost from the first rule’s algorithm config. This means the circuit breaker and the first rule share the same key dimension.
Example: protect against runaway LLM agents
{
"circuit_breaker": {
"enabled": true,
"spend_rate_threshold_per_minute": 10000,
"auto_reset_after_minutes": 10,
"alert": true
}
}
If an agentic system suddenly starts burning 10,000 tokens per minute (e.g., stuck in a loop that escaped loop detection), the breaker trips and blocks all traffic on the policy for 10 minutes. The alert: true flag sends an event to the SaaS dashboard for operator notification.
Interaction with other limiters
Circuit breaker evaluation happens after loop detection and before per-rule limiter evaluation. If the breaker is open, no limiters are invoked.