PIXELBANKv9.1.0
Menu

Circuit Breaker State Machine for a Failing Tool

Problem Statement

When a downstream tool is genuinely down, retrying is worse than useless: every attempt costs a step, a timeout and a slot in the context window, and the agent's plan drifts further from reality with every empty observation. A circuit breaker stops the bleeding - after enough consecutive failures it opens and rejects calls without making them, then cautiously probes for recovery.

Background

Three states:

  • closed - calls go through. A failure increments a consecutive-failure counter; a success resets it to zero. When the counter reaches failure_threshold, the breaker opens and records opened_at = t of that event.
  • open - any event at time t < opened_at + cooldown is rejected: the call is not executed at all, and its t is recorded. The first event at t >= opened_at + cooldown transitions the breaker to half-open and is then processed by the half-open rules.
  • half-open - a trial window. Successes accumulate; on reaching half_open_successes the breaker closes and all counters reset. A single failure re-opens it immediately with opened_at = t of that failure.

The counters reset on every state change, so a fresh open always requires failure_threshold fresh consecutive failures.

Your Task

Implement:

def run_circuit_breaker(events, failure_threshold, cooldown, half_open_successes):
  • events: list of {"t": int, "result": "success"|"failure"}, already in ascending time order.

Return:

  • final_state: "closed", "open" or "half_open".
  • rejected: list of t values that were short-circuited, in event order.
  • transitions: list of strings "<from>-><to>@<t>", in the order they happened.

Input/Output Format

Returns a dict with those three keys. The breaker starts closed.

Sample

events = [{"t": 1, "result": "failure"}, {"t": 2, "result": "failure"},
          {"t": 3, "result": "failure"}, {"t": 4, "result": "success"}]
print(run_circuit_breaker(events, 2, 10, 1))
# {'final_state': 'open', 'rejected': [3, 4], 'transitions': ['closed->open@2']}

Two consecutive failures open the breaker at t=2; the cooldown runs to t=12, so both later events are rejected without ever being tried.

Example:

Input:
events = [{'t':1,'result':'failure'},{'t':2,'result':'failure'},{'t':3,'result':'failure'},{'t':4,'result':'success'}]
print(run_circuit_breaker(events, 2, 10, 1))
Output:
{'final_state': 'open', 'rejected': [3, 4], 'transitions': ['closed->open@2']}
Reasoning:

The failures at t=1 and t=2 are two consecutive failures, hitting the threshold of 2, so the breaker opens at t=2 with a cooldown to t=12. The events at t=3 and t=4 both fall inside the cooldown, so they are rejected without being executed - the success at t=4 never gets a chance to reset anything.

Constraints:

  • 1 <= len(events) <= 1000; t values are non-decreasing ints
  • failure_threshold >= 1, cooldown >= 0, half_open_successes >= 1
  • A rejected event is never executed and never affects any counter
  • The probe event at t >= opened_at + cooldown is processed under half-open rules, not skipped
  • Every state change resets the failure and success counters
  • transitions uses the exact format "<from>-><to>@<t>"
solution.py

Test Results

0/0
Run code to see test results.
Circuit Breaker State Machine for a Failing Tool - Medium | PixelBank