# Circuit Breaker

- Canonical URL: https://docs.fairvisor.com/docs/policy/circuit-breaker/
- Section: docs
- Last updated: n/a
> Budget-rate circuit breaker that trips when spend velocity exceeds a threshold.


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](/docs/algorithms/cost-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

```json
{
  "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`:

1. The `cb_state` key is set to `"open:{current_time}"`
2. The request is rejected with `reason: circuit_breaker_open`
3. 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

```json
{
  "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.

