> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reasonblocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshoot common ReasonBlocks issues

> Diagnose and fix the issues users hit most: import errors, missing injections, FAST-mode never triggering, over-injection, TokenSaving not compressing, and stuck dashboard rows.

<AccordionGroup>
  <Accordion title="AttributeError: module 'reasonblocks' has no attribute X">
    **Symptom**

    ```python theme={null}
    import reasonblocks
    reasonblocks.TokenSavingMiddleware  # AttributeError
    ```

    **What this means**

    `reasonblocks/__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:

    ```python theme={null}
    from reasonblocks.injections.e1 import E1Injection
    ```
  </Accordion>

  <Accordion title="ImportError: cannot import name 'AgentMiddleware' from 'langchain'">
    **Cause**

    ReasonBlocks requires LangChain 1.0+. `AgentMiddleware`, `ModelRequest`, and `ModelResponse` were introduced in 1.0.

    **Fix**

    ```bash theme={null}
    pip install "langchain>=1.0"
    ```
  </Accordion>

  <Accordion title="ImportError: No module named 'networkx'">
    **Cause**

    `networkx` is only needed for `ImportGraph` (`reasonblocks.import_graph`). It's an optional dependency.

    **Fix**

    ```bash theme={null}
    pip install networkx
    ```

    If you don't use `ImportGraph`, remove the import — the rest of the SDK doesn't touch networkx.
  </Accordion>

  <Accordion title="No steering injections appearing in any run">
    **Symptom**

    Every entry in `mw.step_log` has empty `injection_sources` and `intervention_texts`. The agent runs, but receives no guidance.

    **Walk through these in order**

    **1. Missing or invalid `api_key`**

    Empty, malformed, or revoked keys cause silent API failure. Enable debug logging:

    ```python theme={null}
    import logging
    logging.getLogger("reasonblocks").setLevel(logging.DEBUG)
    ```

    **2. Wrong `base_url`**

    Verify the endpoint:

    ```bash theme={null}
    curl -I https://your-rb-api-host/health
    ```

    **3. `e_traces_enabled=False`**

    Disables E1/E2/E3 entirely. Default is `True`.

    **4. FSM stays in FAST**

    All E-tracing skips when the FSM is FAST. Inspect:

    ```python theme={null}
    for entry in mw.step_log:
        print(entry.step, entry.fsm_state, entry.difficulty, entry.skipped_reason)
    ```

    **5. E1 monitor gate denies**

    E1 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 caps**

    E1 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.
  </Accordion>

  <Accordion title="Agent never enters FAST mode">
    **Symptom**

    The FSM stays in `NORMAL` even on steps that look easy.

    **Cause**

    The 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`.

    **Fix**

    Raise `fast_threshold` or shrink `fast_window`:

    ```python theme={null}
    rb = ReasonBlocks(
        api_key="rb_live_...",
        fsm_thresholds={
            "fast_threshold": 0.30,
            "fast_window": 4,
        },
    )
    ```

    Inspect the raw distribution of difficulty scores to calibrate:

    ```python theme={null}
    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}")
    ```
  </Accordion>

  <Accordion title="Too many steering injections — agent looks over-guided">
    **Cause**

    Monitor 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**

    ```python theme={null}
    config = ReasonBlocksConfig(
        enable_general_monitor=True,
        gm_cooldown=15,         # raise from 8
        gm_max_tool_calls=50,   # raise the reference budget
    )
    ```

    **Fix for monitor steering**

    Indirectly, raise the FSM thresholds so the agent reaches SLOW less aggressively (SLOW has the shortest cooldown):

    ```python theme={null}
    rb = ReasonBlocks(
        api_key="rb_live_...",
        fsm_thresholds={
            "slow_threshold": 0.70,
            "slow_window": 7,
        },
    )
    ```
  </Accordion>

  <Accordion title="TokenSavingMiddleware isn't compressing tool outputs">
    **Symptom**

    `mw.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.

    **Fix**

    Use `build_middleware()` or `rb.middleware()` to get the right order automatically. When constructing middleware manually:

    ```python theme={null}
    middleware = [
        rb.middleware(),
        TokenSavingMiddleware(compress_threshold_chars=1800),   # last
    ]
    ```
  </Accordion>

  <Accordion title="Dashboard shows runs as 'in progress' indefinitely">
    **Symptom**

    Runs appear in the dashboard with a spinner or "in progress" status and never transition to "complete".

    **Cause**

    A 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.

    **Fix**

    Use the context manager:

    ```python theme={null}
    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:

    ```python theme={null}
    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).

    <Tip>
      The context manager is the recommended pattern. It handles success, exceptions, and explicit failure marking with no extra code.
    </Tip>
  </Accordion>
</AccordionGroup>
