Skip to main content
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

StateTrigger condition (built-in FSM)Behavior
INITSet at construction timeFirst before_model call transitions to NORMAL
FASTSix 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
NORMALDefault operating stateE-trace pipeline runs, gated by the E1 retrieval gate. Monitor injection cooldown: 3 steps
SLOWFive consecutive scores above slow_threshold (0.6)Routes to the SLOW-tier model when configured. Monitor injection cooldown: 2 steps
SKIPReached 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
ENDDefined in the enumNot 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.
StateCooldown (steps between monitor injections)
FAST5
NORMAL3
SLOW2
SKIP2

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"