Skip to main content
Version: Next

Circuit breakers

The breaker is the hook that bounds an unattended agent: loops, repeated failures, budgets and wall clock.

A tripped breaker ends the run, and nothing works around it. A breaker fires when a session has shown it is not making progress, and continuing would cost money and produce confident, wrong work.

Where it applies​

The breaker applies to supervised runs only. A trip commits so the run's work survives the run, which is the wrong thing to do to a person at a keyboard.

The breaker arms when ORION_WORKSPACE is set, which the supervisor exports into every agent run and nothing else does. Outside one it says so and allows the call:

orion: not a supervised run (ORION_WORKSPACE unset); breaker inactive

ORION_BREAKER_FORCE=1 arms it anyway, for testing from a shell.

This scoping is the breaker's alone. gate (dangerous shell commands) and shield (an agent editing its own guardrails, or weakening the test that defines a fix) guard against anyone holding the tool, and stay armed everywhere, including your own Claude Code sessions in an adopted repository.

The trips​

TripFires whenSettingSelf-service recovery
breaker/unverified-editsedits pile up with no passing test or buildmax_edits_without_verify (25)Yes. Run the tests or the build. A passing verify clears the trip and the run continues.
breaker/loopthe identical call, with the same arguments, repeatsmax_repeat_identical (4)None
breaker/command-failuresone command keeps failingmax_same_command_failures (3)None
breaker/consecutive-failurescalls fail several times in a rowmax_consecutive_failures (3)None
breaker/no-progressthe agent only polls, with nothing else happeningmax_consecutive_polls (12)None
breaker/tool-budgetthe session reaches its tool-call budgetmax_tool_calls (400)None
breaker/blast-radiusone session edits too many distinct filesmax_files_touched (60)None
breaker/session-timethe session runs past its wall clockmax_session_minutes (90)None

All the settings are under limits in orion.json. At 80% of the tool budget the breaker warns the agent to start converging, so it can wind down before it is cut off.

The block message names the recovery this specific trip has. If it offers none, none exists.

What stays open after a trip​

Two things stay open on every trip:

  1. Writing plans/BLOCKED.md, the breaker's own protocol.
  2. The cleanup allowance.

The cleanup allowance​

Without it, an agent that trips mid-edit, with Edit and Bash both refused, could not revert the risky change it just made, and the next reader would find a modified file and no explanation. So a tripped session may still run six commands, and only these:

git status git diff git checkout -- <path> git restore <path>
git add <path> git commit

git commit --amend is refused, because it replaces the tip commit, and what the run already committed is the only durable record a tripped run leaves.

General Bash is not allowed for cleanup, since a cleanup edit and another attempt at the task differ only in intent. The listed commands cannot do anything else: git checkout -- x can only revert, and git commit cannot change a file's contents. Spending the allowance never clears the trip, and it does not refill; when it is gone, everything is refused. Compound commands such as git status; anything are refused too.

Waiting on a long command​

The identical-repeat breaker does not count a read of a file you are waiting on. A call counts as a wait when it is:

  • asking a background task for its output;
  • reading a file a background command of yours was told to write, even while it is still empty;
  • a read that returns something different from the last identical one.

Everything else counts, including re-reading a file nothing is writing.

An agent that does nothing but poll trips breaker/no-progress. In a headless run a backgrounded command is never announced back, so prefer the foreground. One Bash call that waits several minutes is one tool call, and waiting is free.

The wip: snapshot​

When a trip with no way out fires, the worktree's modified and new files are committed on the spot as a wip: snapshot, before the agent gets another turn. plans/BLOCKED.md is excluded, since it is the account of the trip.

The commit message says the work is unverified, and it is preserved for a person to read. Nothing in the cleanup allowance is spent on it, and the block message says what happened to it, including when the commit failed.

breaker/unverified-edits does not snapshot. It is the one trip with a way out, and a wip: commit in the middle of a run that then succeeds would be noise in a pull request.

The stop note​

plans/BLOCKED.md is appended by the breaker, at the moment it trips, so the note exists even if the agent never gets another turn.

What an agent should do when it trips​

  1. Stop. Do not retry the call. Do not find another route to the same action.
  2. Add to plans/BLOCKED.md: what was being attempted, what is done, what remains, and the exact next step.
  3. Revert what should not survive, then commit what compiles. A branch with real commits can be resumed. A plan file describing uncommitted work cannot, and an uncommitted change also blocks the next rebase of the branch.
  4. Summarise and stop.

What Orion does when the agent could not​

A run killed on its turn ceiling, or one whose last act was the call that tripped, has no turn left to spend the allowance. When a run ends with a breaker tripped and the worktree still holding uncommitted changes, Orion commits them as the same wip: snapshot, and says so in the run output, on the ticket, and in the project's channel. Only if that commit fails does it revert, and it says that too, because a dirty worktree makes the next rebase of the branch refuse.

What a person should do​

orion reset --session <id> # clears the trip, after you have reviewed it

Then requeue the ticket, or let the watch retry it; both are in the recovery runbook.

If a limit is wrong for a repository, change the limit in orion.json (limits.max_repeat_identical and the rest) and leave the trip alone. See Tuning the guardrails.

Why loop trips have no way out​

For an unverified-edits trip, running the check is both the remedy and the proof. A loop trip means the agent is repeating itself, and letting it run a build tells it nothing new. The cleanup allowance makes no attempt at the task: it lets the agent hand over the worktree, and the run still ends.

Breakers above the session​

Two more breakers work at the level of the queue rather than one session:

  • The planner evicts a ticket that trips the breaker limits.max_breaker_trips times, or whose worktree fails to settle limits.max_stranded times. Evicted twice, it is held for a person.
  • A watch that achieves nothing for limits.no_progress_minutes stops itself and says what it was waiting on.

Both are on the Recovery runbook.