FSMState is the enum at the heart of ReasonBlocks’ difficulty tracking. After scoring each reasoning step, the SDK advances a finite state machine through these states to determine model routing, monitor-injection cooldowns, and whether to skip the E-trace retrieval pipeline. You read the current state from TraceState.current_state and from per-step StepLogEntry.fsm_state fields exposed on the middleware.
from reasonblocks.types import FSMState
States
| State | Trigger condition (built-in FSM) | Behavior |
|---|
INIT | Set at construction time | First before_model call transitions to NORMAL |
FAST | Six consecutive scores below fast_threshold (0.2) | Skips the full E-trace retrieval pipeline; routes to the FAST-tier model when model_routing includes a FAST entry; monitors still evaluate. Monitor injection cooldown: 5 steps |
NORMAL | Default operating state | E-trace pipeline runs, gated by the E1 retrieval gate. Monitor injection cooldown: 3 steps |
SLOW | Five consecutive scores above slow_threshold (0.6) | Routes to the SLOW-tier model when configured. Monitor injection cooldown: 2 steps |
SKIP | Reached only from SLOW after 35 consecutive scores above skip_threshold (0.85) | Routing and cooldown identical to SLOW; signals an exceptionally stalled run for downstream tooling |
END | Defined in the enum | Not entered by the built-in DifficultyFSM. Reserved for callers that ship a custom transition function |
The default thresholds and windows live on DifficultyFSM; override them via ReasonBlocks(fsm_thresholds={...}).
State reference
FSMState.INIT
The trace starts in INIT. The first scored step transitions to NORMAL. You will only see INIT on the very first before_model call (before any thought has been produced).
FSMState.FAST
Entered from NORMAL when the most recent six difficulty scores are all below fast_threshold (default 0.2). In FAST, the middleware skips the full E-trace retrieval pipeline (E1, E2, E3); monitors still run so loop and trajectory-length detectors keep working. If model_routing contains a FAST entry, the next call uses that model.
FSMState.NORMAL
The default state. The E-trace pipeline runs, with E1 retrieval further gated by recent monitor activity (see the middleware notes). The model from your ReasonBlocks config is used unless model_routing overrides it for NORMAL.
FSMState.SLOW
Entered from NORMAL when the most recent five difficulty scores all exceed slow_threshold (default 0.6). E-traces continue to be retrieved; if model_routing contains a SLOW entry the next call swaps to that model.
Both FAST and SLOW exit on a single contrary score with hysteresis: from FAST, a score above fast_threshold + hysteresis_margin (0.3) returns to NORMAL; from SLOW, a score below slow_threshold - hysteresis_margin (0.5) returns to NORMAL.
FSMState.SKIP
Entered from SLOW when 35 consecutive scores all exceed skip_threshold (default 0.85). For routing and monitor-injection cooldown purposes, SKIP is identical to SLOW; it is a separate label so dashboards and downstream code can flag exceptionally stalled runs. There is no special “skip injection” — the same monitor steering and E-trace machinery applies.
FSMState.END
END is defined in the enum but the built-in DifficultyFSM.transition never returns it. The state exists for callers that supply a custom transition function that wants to mark a trace as terminal. Reading END from a stock SDK installation indicates manual intervention.
Monitor injection cooldowns
Monitor steering injection respects per-state cooldowns to avoid over-steering. The middleware caps monitor injections at 5 per run total.
| State | Cooldown (steps between monitor injections) |
|---|
FAST | 5 |
NORMAL | 3 |
SLOW | 2 |
SKIP | 2 |
Reading state after a run
from reasonblocks import ReasonBlocks
rb = ReasonBlocks(api_key="rb_live_...")
with rb.middleware(run_id="run-1", agent_name="bugfixer", task="...") as mw:
result = agent.invoke({"messages": [("user", "Fix the failing tests.")]})
for entry in mw.step_log:
print(entry.step, entry.fsm_state, entry.difficulty)
StepLogEntry.fsm_state is the string value of the enum (e.g. "FAST", "NORMAL"); use FSMState(entry.fsm_state) to round-trip back to the enum.
Values
FSMState is a standard enum.Enum. Each member’s .value is its uppercase string name, which is also what appears in the dashboard.
FSMState.INIT.value # "INIT"
FSMState.FAST.value # "FAST"
FSMState.NORMAL.value # "NORMAL"
FSMState.SLOW.value # "SLOW"
FSMState.SKIP.value # "SKIP"
FSMState.END.value # "END"