# How the breaker.ts Steer → Constrain → Stop Ladder Works in Munder Difflin

> Understand the breaker.ts steer constrain stop ladder in Munder Difflin. Learn how it manages agent states like healthy steering constrained and stopped based on trip conditions.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: internals
- Published: 2026-08-20

---

**The [`breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/breaker.ts) steer → constrain → stop ladder moves agents one level per heartbeat—from `healthy` to `steering`, then `constrained`, and finally `stopped`—based on evaluated trip conditions, while recovering one level when no trip occurs.**

The circuit breaker in `chaitanyagiri/munder-difflin` safeguards the system against runaway agent behavior by evaluating every agent during each periodic beat. In [`src/main/breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/breaker.ts), the **steer → constrain → stop ladder** enforces increasing restrictions through ordered state transitions defined by the `LEVELS` array and the `actionFor` helper, allowing recovery when conditions normalize.

## The Four-Level Escalation Hierarchy

At the core of [`breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/breaker.ts) is the ordered `LEVELS` array that pins the exact sequence an agent must follow.

```ts
const LEVELS: BreakerLevel[] = ['healthy', 'steering', 'constrained', 'stopped'];
const rank = (l: BreakerLevel): number => LEVELS.indexOf(l);

```

The ladder never skips ranks. An agent climbs from `healthy` to `steering` on the first trip, to `constrained` on a second consecutive trip, and to `stopped` on a third. A healthy beat always drops the agent down by exactly one step toward `healthy`.

### Mapping States to Enforcement Actions

Only active levels produce an enforcement verb. The `actionFor` helper at line 61 of [`src/main/breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/breaker.ts) translates rank into behavior.

```ts
const actionFor = (l: BreakerLevel): BreakerAction =>
  l === 'steering' ? 'steer' : l === 'constrained' ? 'constrain' : l === 'stopped' ? 'stop' : 'none';

```

When the state is `healthy`, `actionFor` returns `none`. For every other level it returns the matching verb, which the heartbeat loop uses to trigger corrective messages or termination.

## How `tick` Evaluates and Transitions Agents

The `tick` method drives the ladder. It accepts an array of `BreakerInput` objects—one per agent—and returns a `BreakerDecision` for each.

```ts
const decisions = breaker.tick(inputs, Date.now());

```

Inside `tick`, the method first checks the global `cfg` object pulled from [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts). If the circuit breaker is disabled, every agent is forced back to `healthy`. When enabled, `tick` identifies top-spender agents for cost-cap and token-cap comparisons before proceeding to individual evaluation.

### Trip Logic in `evaluate`

For each agent, `tick` calls `evaluate` (line 84), a pure function that inspects per-agent counters and usage samples. If any trip condition is met, `evaluate` signals that the agent should climb the ladder.

The `tick` method then compares the current level with the evaluation result. On a trip, the target level becomes the next higher rank, clamped to the configured ceiling. On a clean bill of health, the level decreases by one rank.

### Escalation, Recovery, and Output Flags

The ladder moves at most one level per heartbeat. This prevents flickering and guarantees safe escalation. The returned `BreakerDecision` includes a `changed` flag when any shift occurs, and an `action` field only when the level *increased* (`escalated`). The new `BreakerState` is emitted on the `control:breakerState` channel so dashboards can remain synchronized.

## Trip Conditions That Drive the Ladder

The `evaluate` function monitors four families of signals to decide whether an agent should escalate.

### Repeated Identical Tool Calls

When `repeatCount` exceeds `repeatedToolLimit`, the breaker treats the agent as stuck in a loop. This is one of the fastest ways to trigger an initial `steer` action.

### API-Error Storms

Consecutive API errors beyond `errorStormLimit` indicate a malfunctioning integration or malformed prompt. Each error increments an internal counter that `evaluate` inspects during the beat.

### Cost and Token Caps

The breaker calculates floor-wide totals and flags the top-spender agent when cumulative cost or token usage crosses configured ceilings. These checks correspond to the cost-cap logic (lines 30–38) and token-cap logic (lines 40–48) inside [`src/main/breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/breaker.ts).

### Token-Velocity Spikes

A rapid increase in output tokens per minute is detected by comparing the current `AgentUsageSample` from [`src/main/usage.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/usage.ts) with the previous one. The `tokenVelocityPerMin` threshold catches agents that suddenly begin generating excessive output.

### No-Progress Detection

If an agent produces tokens without file-system progress or a distinct tool call within a five-minute window, a debounce counter (`NO_PROGRESS_BEATS`) eventually triggers a trip. This catches agents that appear busy but accomplish nothing.

## Hard Stop and the Ladder Ceiling

The `CircuitBreakerConfig` in [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts) exposes a `hardStop` flag that determines the terminal ceiling. When enabled, the ladder can reach `stopped` and the agent is killed or archived. When disabled, escalation caps at `constrained`, keeping the agent alive under heavy restriction. This logic is applied inside `tick` at the escalation boundary.

## Implementing the Ladder in Practice

### Heartbeat Loop Integration

The following pattern shows how to run the breaker inside a periodic beat and act only on escalations.

```ts
import { CircuitBreaker } from './breaker';

// Provide a function returning the current configuration (could come from a
// config file or a UI control).
const getConfig = () => ({
  enabled: true,
  hardStop: false,
  repeatedToolLimit: 8,
  errorStormLimit: 5,
  tokenVelocityPerMin: 60_000,
  // optional caps…
});

const breaker = new CircuitBreaker(getConfig);

// Inside the periodic “beat”:
const inputs: BreakerInput[] = agents.map(a => ({
  agentId: a.id,
  sample: a.usageSample,      // cumulative usage or null
  progressing: a.fileMtimeProgress // true if recent file change
}));

const decisions = breaker.tick(inputs, Date.now());

decisions.forEach(d => {
  // Emit state for monitoring UI
  publish('control:breakerState', d.state);
  // Perform enforcement only when an escalation action is present
  if (d.action !== 'none') enforceAction(d.agentId, d.action);
});

```

### Recording Events from the Hook Server

External callers such as [`src/main/hookServer.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/hookServer.ts) feed the breaker through dedicated record methods so that `evaluate` has fresh data on each `tick`.

```ts
// When a tool finishes:
breaker.recordToolUse(agentId, toolName, toolInput);

// When an api_error occurs:
breaker.recordError(agentId);

// When a compaction starts / ends (to avoid false‑positive velocity trips):
breaker.recordCompactStart(agentId);
breaker.recordCompactEnd(agentId);

```

These calls update the per-agent counters inspected by the trip logic in [`src/main/breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/breaker.ts).

## Summary

- The **steer → constrain → stop ladder** in [`src/main/breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/breaker.ts) uses four ordered levels: `healthy`, `steering`, `constrained`, and `stopped`.
- The `LEVELS` array and `actionFor` helper map each state to `none`, `steer`, `constrain`, or `stop`.
- The `tick` method evaluates every agent once per heartbeat, allowing at most one level of escalation or recovery per beat.
- Trip conditions include repeated tool calls, error storms, cost caps, token velocity, and no-progress detection.
- The `hardStop` configuration flag in [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts) determines whether the ladder ceiling is `constrained` or `stopped`.

## Frequently Asked Questions

### How does the breaker.ts steer constrain stop ladder move an agent up?

The ladder advances one level per trip condition detected during `tick`. If `evaluate` finds a tripping signal while the agent is `healthy`, it moves to `steering`; a second trip moves it to `constrained`; a third trip moves it to `stopped` when `hardStop` is enabled. The escalation logic inside `tick` enforces this single-step progression according to the `LEVELS` ranking.

### What prevents the circuit breaker from jumping straight to stopped?

The escalation logic inside `tick` limits movement to a single rank per heartbeat. Because the `LEVELS` array enforces a strict sequence, an agent must pass through `steering` and `constrained` before reaching `stopped`, preventing instantaneous termination from a `healthy` state.

### Can an agent recover after being steered or constrained?

Yes. When `evaluate` reports no trip condition during a beat, `tick` de-escalates the agent by exactly one level toward `healthy`. This one-step recovery design prevents state flickering and allows agents to resume normal operation after sustained good behavior.

### Where does the breaker receive the usage data it evaluates?

Per-agent counters are populated by external callers such as [`src/main/hookServer.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/hookServer.ts), which invokes `recordToolUse` and `recordError`. The heartbeat loop in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) passes fresh `BreakerInput` objects—containing `AgentUsageSample` data from [`src/main/usage.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/usage.ts)—into `breaker.tick` for evaluation.