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

# Frequently asked questions

> Answers to common questions about ReasonBlocks setup, debugging traces, FSM behavior, middleware ordering, run tagging, and stuck dashboard rows.

<AccordionGroup>
  <Accordion title="What does 'FSM=FAST, e-traces skipped' mean in step_log?">
    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:

    ```python theme={null}
    entry.skipped_reason  # "FSM=FAST, e-traces skipped"
    ```

    Inspect it across a run:

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

    ```python theme={null}
    rb = ReasonBlocks(
        api_key="rb_live_...",
        fsm_thresholds={"fast_threshold": 0.10},
    )
    ```
  </Accordion>

  <Accordion title="How do I debug what got injected on each step?">
    `step_log` is the primary debugging surface. Every model call becomes one `StepLogEntry` with the full intervention details:

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

    | Field                | Contents                                                                                     |
    | -------------------- | -------------------------------------------------------------------------------------------- |
    | `injection_sources`  | Class names: `"E1Injection"`, `"E2Injection"`, `"E3Injection"`, `"MonitorSteeringInjection"` |
    | `injections`         | Short previews (first 150 chars) of each injection                                           |
    | `intervention_texts` | Full text as sent to the model                                                               |
    | `monitors_fired`     | Monitor names whose individual score reached the fire threshold                              |
    | `failure_type`       | Categorical failure label from the server (when monitors classified one)                     |
    | `fsm_state`          | FSM state at the time of the call                                                            |
    | `difficulty`         | Heuristic difficulty score for the step                                                      |
    | `model_id`           | Resolved model id when routing fired                                                         |
    | `tokens`             | Total tokens for the call                                                                    |
    | `latency_ms`         | Wall time from previous step to this one                                                     |
    | `skipped_reason`     | Set when E-traces were skipped (currently only `"FSM=FAST, e-traces skipped"`)               |
  </Accordion>

  <Accordion title="Why is my agent not getting any steering injections?">
    Walk through these in order:

    **1. `e_traces_enabled=False`**

    Disables the entire E1/E2/E3 retrieval pipeline. The default is `True`.

    **2. FSM is in FAST**

    All E-tracing skips when the FSM is in FAST. Inspect `entry.fsm_state` and `entry.skipped_reason` across `step_log`.

    **3. E1 monitor gate denies**

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

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

    Failures inside `before_model` and `wrap_model_call` are caught and logged at `WARNING`. Set the SDK logger to `DEBUG` to see them:

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

    **6. Empty pattern scope**

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

  <Accordion title="What's the right middleware ordering with TokenSavingMiddleware?">
    `TokenSavingMiddleware` must come **last**. It runs after `ReasonBlocksMiddleware` and `GeneralMonitorMiddleware` so it compresses whatever they injected before the LLM call goes out.

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

    <Warning>
      Putting `TokenSavingMiddleware` before `ReasonBlocksMiddleware` means it processes history before any injection is added — silent correctness failure, not an error.
    </Warning>
  </Accordion>

  <Accordion title="What happens when the ReasonBlocks API is down?">
    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`.
  </Accordion>

  <Accordion title="Can I run with no outbound calls at all?">
    Mostly. Set `e_traces_enabled=False` and `live_streaming_enabled=False`:

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

  <Accordion title="Can I use ReasonBlocks with a self-hosted rb-api?">
    Yes. Pass `base_url`:

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

  <Accordion title="How do I tag runs so I can filter them in the dashboard?">
    Pass a `metadata` dict to `rb.middleware()` (or `rb.openai_hooks()`):

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

  <Accordion title="What's the difference between org_id and project_id?">
    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.
  </Accordion>
</AccordionGroup>
