/** * CircuitBreaker — pure state-machine functions for the Nygard breaker * pattern. * * Refactored from a class-with-instance-state to PURE FUNCTIONS that * take a state record and return a new one. Reasons: * * 1. **No hidden runtime state.** Breaker state lives in scope where * it's visible to commitLog, narrative, and rules — the footprintjs * "everything in scope" principle. The closure used to be the * source of truth and scope held only a projection; now scope IS * the source of truth. * * 2. **Round-trippable across gate invocations.** Because state is a * plain record, gate's outputMapper writes it back to agent scope; * agent scope persists across the ReAct loop's many LLM-call gate * invocations; gate's inputMapper reads it back in for the next * call. Per-process persistence comes from the agent scope, not * from a closure that hides between runs. * * 3. **Distributable later.** A future v2.12 `BreakerStateStore` * adapter (Redis/DynamoDB) just needs to serialize/deserialize the * state record. No class instances to reconstruct. * * 4. **Testable in isolation.** Pure functions; no instance setup. * * Pattern: Nygard *Release It!* — three states (CLOSED → OPEN → * HALF-OPEN) with cooldown and probe-success thresholds. */ import type { CircuitBreakerConfig } from './types.js'; export type CircuitState = 'closed' | 'open' | 'half-open'; /** Plain serializable record holding one breaker's full state. */ export interface BreakerState { state: CircuitState; consecutiveFailures: number; consecutiveSuccesses: number; openedAt: number; lastErrorMessage?: string; } /** * Thrown by `assertAdmit()` when the breaker is OPEN and the cooldown * window has not elapsed. The reliability gate stage catches this, * classifies via `classifyError` → `'circuit-open'`, and lets the * post-decide rules route on it. */ export declare class CircuitOpenError extends Error { readonly code: "ERR_CIRCUIT_OPEN"; readonly cause: unknown; readonly retryAfter: number; constructor(providerName: string, lastErrorMessage: string | undefined, retryAfter: number); } /** Initial state for a freshly-CLOSED breaker. */ export declare function initialBreakerState(): BreakerState; /** * Decide whether to admit a call. Returns the (possibly-updated) state * AND whether to admit. If OPEN and cooldown elapsed, transitions to * HALF-OPEN and admits. Pure: caller must use the returned state. * * Usage in the gate stage: * ```ts * const { admitted, nextState } = admitCall(scope.breakerStates[name], config); * scope.breakerStates[name] = nextState; * if (!admitted) throw new CircuitOpenError(name, nextState.lastErrorMessage, ...); * ``` */ export declare function admitCall(state: BreakerState, config: CircuitBreakerConfig | undefined): { admitted: boolean; nextState: BreakerState; }; /** Record a successful call. Returns the (possibly-updated) state. */ export declare function recordSuccess(state: BreakerState, config: CircuitBreakerConfig | undefined): BreakerState; /** Record a failed call. Returns the (possibly-updated) state. */ export declare function recordFailure(state: BreakerState, err: unknown, config: CircuitBreakerConfig | undefined): BreakerState; /** Compute the next probe time given a state + config. */ export declare function nextProbeTime(state: BreakerState, config: CircuitBreakerConfig | undefined): number;