Skip to main content
When the FSM is in FAST state, the middleware skips E1, E2, and E3 retrieval entirely. This is by design — FAST means the agent is sailing through easy steps and the pattern-store query overhead isn’t worth paying. Monitor scoring still runs so loop detection stays active.The middleware records this on the step entry as:
entry.skipped_reason  # "FSM=FAST, e-traces skipped"
Inspect it across a run:
for entry in mw.step_log:
    if entry.skipped_reason:
        print(f"step {entry.step}: {entry.skipped_reason}")
To stay out of FAST longer, raise fast_threshold or grow fast_window:
rb = ReasonBlocks(
    api_key="rb_live_...",
    fsm_thresholds={"fast_threshold": 0.10},
)
step_log is the primary debugging surface. Every model call becomes one StepLogEntry with the full intervention details:
mw = rb.middleware(run_id="debug-1")

with mw:
    result = agent.invoke({"messages": [("user", "...")]})

for entry in mw.step_log:
    print(f"step {entry.step} | state={entry.fsm_state} difficulty={entry.difficulty}")
    for src, text in zip(entry.injection_sources, entry.intervention_texts):
        print(f"  [{src}] {text[:160]}")
    if entry.monitors_fired:
        print(f"  monitors fired: {entry.monitors_fired}")
    if entry.skipped_reason:
        print(f"  skipped: {entry.skipped_reason}")
FieldContents
injection_sourcesClass names: "E1Injection", "E2Injection", "E3Injection", "MonitorSteeringInjection"
injectionsShort previews (first 150 chars) of each injection
intervention_textsFull text as sent to the model
monitors_firedMonitor names whose individual score reached the fire threshold
failure_typeCategorical failure label from the server (when monitors classified one)
fsm_stateFSM state at the time of the call
difficultyHeuristic difficulty score for the step
model_idResolved model id when routing fired
tokensTotal tokens for the call
latency_msWall time from previous step to this one
skipped_reasonSet when E-traces were skipped (currently only "FSM=FAST, e-traces skipped")
Walk through these in order:1. e_traces_enabled=FalseDisables the entire E1/E2/E3 retrieval pipeline. The default is True.2. FSM is in FASTAll E-tracing skips when the FSM is in FAST. Inspect entry.fsm_state and entry.skipped_reason across step_log.3. E1 monitor gate deniesE1 retrieval is gated by monitor health — it only runs when at least one monitor fired in the current call, the composite score is above 0.15, or a monitor fired in either of the previous two calls. By design, E1 stays quiet when the run looks healthy. Check entry.monitors_fired over the trailing window.4. Per-injection capsE1 caps at one retrieval per run (max_calls=1). E2 caps at one. E3 fires only on step 0. Monitor steering caps at 5 injection events per run with cooldowns: 2 steps in SLOW/SKIP, 3 in NORMAL, 5 in FAST.5. Silent API failureFailures inside before_model and wrap_model_call are caught and logged at WARNING. Set the SDK logger to DEBUG to see them:
import logging
logging.getLogger("reasonblocks").setLevel(logging.DEBUG)
6. Empty pattern scopeA new account or organization may have no E1 patterns yet. E2 and E3 will still fire. Check entry.injection_sources to see which tiers contributed.
TokenSavingMiddleware must come last. It runs after ReasonBlocksMiddleware and GeneralMonitorMiddleware so it compresses whatever they injected before the LLM call goes out.
middleware = [
    ReasonBlocksMiddleware(...),
    GeneralMonitorMiddleware(...),   # optional
    TokenSavingMiddleware(...),      # last
]
When you use rb.middleware() or build_middleware(), the ordering is handled for you. Manual ordering only matters when constructing middleware classes directly.
Putting TokenSavingMiddleware before ReasonBlocksMiddleware means it processes history before any injection is added — silent correctness failure, not an error.
All exceptions inside before_model and wrap_model_call are caught, logged at WARNING, and swallowed. The agent continues — it just receives no injection on that step. Live telemetry events are fire-and-forget; if the endpoint is unreachable, events are dropped after a timeout and the agent loop is never blocked waiting for them.Watch for log lines like:
WARNING reasonblocks.middleware: before_model failed, continuing unmodified
WARNING reasonblocks.middleware: wrap_model_call failed, calling handler directly
A degraded run typically shows up as step_log entries with empty injection_sources while fsm_state is NORMAL or SLOW.
Mostly. Set e_traces_enabled=False and live_streaming_enabled=False:
rb = ReasonBlocks(
    api_key="rb_live_...",
    e_traces_enabled=False,
    live_streaming_enabled=False,
)
With this configuration, the FSM, scoring, and local pieces of the middleware run without any network calls. Note that monitor steering still requires a server call (POST /monitors/evaluate) when constructed; if you also need monitors-off, configure ReasonBlocksConfig with enable_monitor_steering=False and use build_middleware.
Yes. Pass base_url:
rb = ReasonBlocks(
    api_key="rb_live_...",
    base_url="https://rb-api.internal.acme.com",
)
base_url is forwarded to every internal client (E-trace retrieval, monitor evaluation, live telemetry).
Pass a metadata dict to rb.middleware() (or rb.openai_hooks()):
mw = rb.middleware(
    run_id="pr-42-attempt-3",
    agent_name="reviewer",
    task="review PR #42",
    metadata={
        "pr_number": 42,
        "repo": "acme/backend",
        "experiment": "sonnet-vs-haiku",
        "ab_group": "B",
        "triggered_by": "github_actions",
    },
)
The metadata dict is stored as JSON on the run record and is queryable from the dashboard’s run filter. Use it for any tag that doesn’t fit a named field. Values can be strings, numbers, or booleans.
Both are multi-tenant scoping fields on the run record.
  • org_id identifies the organization. When you use a per-customer rb_live_* key bound to a specific org, the API overrides this with the key’s authoritative org regardless of what you pass.
  • project_id is a finer-grained grouping inside the org — separate infra-review from frontend-review agents within the same customer.
Both default to "default". For SaaS deployments, set org_id to your customer identifier and project_id to the workload name.