Skip to main content
Symptom
import reasonblocks
reasonblocks.TokenSavingMiddleware  # AttributeError
What this meansreasonblocks/__init__.py resolves public names lazily through a PEP 562 __getattr__. The _LAZY mapping in __init__.py covers every documented name, including TokenSavingMiddleware, GeneralMonitorMiddleware, ReasonBlocks, ReasonBlocksAPI, MonitorClient, compress_tool_output, and assign_arm. Accessing any of them on the module object should succeed.If you hit this error for a documented name, that’s a bug in the lazy mapping (the name is in the docs but missing from _LAZY). File an issue with the exact name you tried to access and the SDK version.For internal names (anything not exported in __all__), import directly from the submodule:
from reasonblocks.injections.e1 import E1Injection
CauseReasonBlocks requires LangChain 1.0+. AgentMiddleware, ModelRequest, and ModelResponse were introduced in 1.0.Fix
pip install "langchain>=1.0"
Causenetworkx is only needed for ImportGraph (reasonblocks.import_graph). It’s an optional dependency.Fix
pip install networkx
If you don’t use ImportGraph, remove the import — the rest of the SDK doesn’t touch networkx.
SymptomEvery entry in mw.step_log has empty injection_sources and intervention_texts. The agent runs, but receives no guidance.Walk through these in order1. Missing or invalid api_keyEmpty, malformed, or revoked keys cause silent API failure. Enable debug logging:
import logging
logging.getLogger("reasonblocks").setLevel(logging.DEBUG)
2. Wrong base_urlVerify the endpoint:
curl -I https://your-rb-api-host/health
3. e_traces_enabled=FalseDisables E1/E2/E3 entirely. Default is True.4. FSM stays in FASTAll E-tracing skips when the FSM is FAST. Inspect:
for entry in mw.step_log:
    print(entry.step, entry.fsm_state, entry.difficulty, entry.skipped_reason)
5. E1 monitor gate deniesE1 only retrieves when monitors signal trouble (a monitor fired in the current step or one of the previous two, or composite > 0.15). Healthy runs leave E1 quiet by design. E2 and E3 should still fire — check injection_sources for those tiers.6. Per-tier capsE1 caps at one retrieval per run. E2 caps at one. E3 fires only on step 0. Monitor steering caps at 5 per run with cooldowns: SLOW/SKIP=2, NORMAL=3, FAST=5.
SymptomThe FSM stays in NORMAL even on steps that look easy.CauseThe default fast_threshold is 0.2 and fast_window is 6. The agent must score below 0.2 on six consecutive steps to enter FAST. The shipped heuristic scorer weights hedging language, length, error references, and entity density — agents with verbose intermediate reasoning may never drop below 0.2.FixRaise fast_threshold or shrink fast_window:
rb = ReasonBlocks(
    api_key="rb_live_...",
    fsm_thresholds={
        "fast_threshold": 0.30,
        "fast_window": 4,
    },
)
Inspect the raw distribution of difficulty scores to calibrate:
scores = [e.difficulty for e in mw.step_log if e.difficulty is not None]
print(f"min={min(scores):.3f} avg={sum(scores)/len(scores):.3f} max={max(scores):.3f}")
CauseMonitor steering is capped at _monitor_max_per_run = 5 per run with FSM-state cooldowns (SLOW/SKIP=2, NORMAL=3, FAST=5). E1 and E2 each cap at one retrieval per run; E3 fires only on step 0. So the cap from ReasonBlocksMiddleware itself is bounded.If you’re seeing many more injections than that, the source is most likely GeneralMonitorMiddleware (when enabled separately) — its rule cooldown defaults to 8.Fix for GeneralMonitor
config = ReasonBlocksConfig(
    enable_general_monitor=True,
    gm_cooldown=15,         # raise from 8
    gm_max_tool_calls=50,   # raise the reference budget
)
Fix for monitor steeringIndirectly, raise the FSM thresholds so the agent reaches SLOW less aggressively (SLOW has the shortest cooldown):
rb = ReasonBlocks(
    api_key="rb_live_...",
    fsm_thresholds={
        "slow_threshold": 0.70,
        "slow_window": 7,
    },
)
Symptommw.stats.compressions stays at 0 after a run that produced long tool outputs.Causes (check in order)
  1. Tool outputs are shorter than compress_threshold_chars (default 1800).
  2. Fewer than keep_recent_tool_messages + 1 tool messages exist (defaults to 2 — with only 2 tool messages, both are exempt).
  3. enable_compression=False was set explicitly.
  4. Tool messages have no id — without one, the LangGraph add_messages reducer can’t dedup, so the middleware skips compression rather than appending duplicates. This is rare and usually a custom-tool issue.
  5. Middleware ordering: TokenSavingMiddleware should be last in the list. When stacking with ReasonBlocksMiddleware (or GeneralMonitorMiddleware), placing it earlier means it processes history before injections land.
FixUse build_middleware() or rb.middleware() to get the right order automatically. When constructing middleware manually:
middleware = [
    rb.middleware(),
    TokenSavingMiddleware(compress_threshold_chars=1800),   # last
]
SymptomRuns appear in the dashboard with a spinner or “in progress” status and never transition to “complete”.CauseA run row only flips to “complete” when ReasonBlocks emits a run_finish event. That happens in two places:
  1. after_agent hook — fires when LangChain finishes agent.invoke() cleanly.
  2. __exit__ on the context manager — fires when the with mw: block exits (success or exception).
If neither fires (you swallowed an exception outside the context manager and never called flush_session), the run row stays open.FixUse the context manager:
mw = rb.middleware(run_id="my-run")

with mw:
    result = agent.invoke({"messages": [...]})
If the context manager isn’t an option, call flush_session yourself:
mw = rb.middleware(run_id="my-run")
try:
    result = agent.invoke({"messages": [...]})
    outcome = "success"
except Exception as exc:
    outcome = f"failure: {type(exc).__name__}"
    raise
finally:
    mw.flush_session(outcome_status=outcome)
    mw.close()
To override the default "success" outcome when the agent returned normally but the run was logically a failure, call mw.mark_failure(reason=...) before after_agent fires (or before the with block exits).
The context manager is the recommended pattern. It handles success, exceptions, and explicit failure marking with no extra code.