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

# Advanced configuration

> Lower-level knobs: FSM thresholds, model routing, live streaming, token-budget tracking, stage-timing instrumentation, and run metadata.

This page covers the parameters most integrations don't need on day one but matter once you're tuning a workload or instrumenting a benchmark.

## ReasonBlocks constructor

All parameters except `api_key` are keyword-only.

```python theme={null}
from reasonblocks import ReasonBlocks

rb = ReasonBlocks(
    api_key="rb_live_...",
    base_url=None,                  # default: hosted API
    token_budget=None,              # tracked-only; not enforced
    monitor_names=None,             # accepted but unused on the middleware path
    fsm_thresholds=None,            # default: see DifficultyFSM
    model_routing=None,             # default: no routing
    e_traces_enabled=True,
    live_streaming_enabled=True,
    task_profile="coding",
)
```

<Warning>
  The `ReasonBlocks(...)` constructor does not auto-read `REASONBLOCKS_API_KEY` — read it yourself and pass to `api_key`. The `base_url` default is read from `REASONBLOCKS_BASE_URL` at import time by `reasonblocks._settings`; setting it before importing `reasonblocks` will redirect the underlying HTTP clients without per-call configuration. `REASONBLOCKS_API_TIMEOUT` is read the same way.
</Warning>

***

## FSM threshold tuning

The difficulty FSM classifies each step into `INIT`, `FAST`, `NORMAL`, `SLOW`, or `SKIP` based on a difficulty score in `[0, 1]`. Pass `fsm_thresholds` to adjust any subset of the defaults:

| Parameter           | Default | Meaning                                                |
| ------------------- | ------- | ------------------------------------------------------ |
| `fast_threshold`    | `0.2`   | Score below which a step counts as "easy"              |
| `slow_threshold`    | `0.6`   | Score above which a step counts as "hard"              |
| `skip_threshold`    | `0.85`  | Score above which sustained hardness escalates to SKIP |
| `hysteresis_margin` | `0.1`   | Buffer that prevents thrashing at state boundaries     |
| `fast_window`       | `6`     | Consecutive easy steps to enter FAST                   |
| `slow_window`       | `5`     | Consecutive hard steps to enter SLOW                   |
| `skip_window`       | `35`    | Consecutive very-hard steps to enter SKIP              |

```python theme={null}
rb = ReasonBlocks(
    api_key="rb_live_...",
    fsm_thresholds={
        "fast_threshold": 0.15,
        "slow_threshold": 0.55,
        "fast_window": 4,
        "slow_window": 3,
    },
)
```

**Hysteresis.** Once in FAST, the FSM stays there until a single step scores above `fast_threshold + hysteresis_margin` (default `0.30`). Once in SLOW, it stays until a step scores below `slow_threshold - hysteresis_margin` (default `0.50`). This prevents one borderline step from bouncing the agent back to NORMAL immediately.

***

## Model routing

`model_routing` maps any of `"FAST"`, `"NORMAL"`, `"SLOW"`, `"SKIP"` to a model identifier. Routing applies inside `wrap_model_call` after FSM scoring, before pattern injections render.

```python theme={null}
rb = ReasonBlocks(
    api_key="rb_live_...",
    model_routing={
        "FAST":   "anthropic:claude-haiku-4-5-20251001",
        "NORMAL": "anthropic:claude-haiku-4-5-20251001",
        "SLOW":   "anthropic:claude-sonnet-4-6",
        "SKIP":   "anthropic:claude-sonnet-4-6",
    },
)
```

Unmapped states leave the agent's configured model untouched. See [Route models by FSM state](/guides/model-routing) for routing-specific details.

***

## Token budget (tracked, not enforced)

`token_budget` is **tracked-only**. The middleware records token usage from every model call onto the trace state, and `TraceStateManager.get_budget_used()` returns the fraction consumed. The FSM does **not** transition to SKIP when the budget is exhausted, and no inputs are rejected.

```python theme={null}
rb = ReasonBlocks(
    api_key="rb_live_...",
    token_budget=200_000,   # advisory; queryable via state_manager.get_budget_used()
)
```

To act on the budget, read it yourself and decide what to do:

```python theme={null}
mw = rb.middleware()

with mw:
    result = agent.invoke(...)

# After the run, inspect total tokens via step_log
total = sum(e.tokens for e in mw.step_log)
print(f"used {total} tokens")
```

***

## Live streaming

Live streaming emits `run_start` / `step` / `run_finish` events to rb-api as the run progresses. On by default.

```python theme={null}
rb = ReasonBlocks(api_key="rb_live_...", live_streaming_enabled=False)
```

When `live_streaming_enabled=False`:

* The `StreamingEmitter` is **never constructed**.
* `_maybe_emit_run_start`, `_maybe_emit_step`, and `_maybe_emit_run_finish` short-circuit to no-ops.
* No dashboard run row is created.
* `flush_session()` calls go nowhere — there's no emitter to flush. Don't expect `flush_session` to produce a run row after a network outage; if streaming was disabled, no events have been queued.
* Local behavior — FSM scoring, monitor steering, injection, model routing — is unaffected.

***

## E1 scoping

E1 retrieval is scoped to your organization automatically — rb-api derives the scope from the API key's principal (a per-customer `rb_live_*` key carries its org). There is no client-side `customer_id` parameter on the `ReasonBlocks` constructor or `ReasonBlocksConfig`; runs under the same key share an E1 pattern pool, and other organizations stay isolated.

***

## Stage-timing instrumentation

`ReasonBlocksMiddleware` can record per-stage latencies in milliseconds across six buckets. Useful for breaking down where SDK overhead lives during a benchmark.

```python theme={null}
mw = rb.middleware(run_id="bench-1")
mw.enable_stage_timings()           # must be called before agent.invoke

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

timings = mw.get_stage_timings()
# {
#   "monitor_scoring": [12.3, 9.8, ...],
#   "e1_retrieval":    [45.2, ...],
#   "e2_retrieval":    [22.1, ...],
#   "e3_retrieval":    [5.0],
#   "format_routing":  [0.3, ...],
#   "system_injection":[0.1, ...],
# }

overhead_ms = mw.get_call_overhead_ms()   # SDK overhead per step
llm_ms      = mw.get_call_llm_ms()        # LLM handler duration per step
```

The six buckets:

| Bucket             | What's measured                                                       |
| ------------------ | --------------------------------------------------------------------- |
| `monitor_scoring`  | `MonitorSteeringInjection.retrieve` latency (POST /monitors/evaluate) |
| `e1_retrieval`     | E1 vector search (skipped under FAST and when E1 gate denies)         |
| `e2_retrieval`     | E2 commons retrieval (skipped under FAST)                             |
| `e3_retrieval`     | E3 standing-rule scroll-all (step 0 only)                             |
| `format_routing`   | Pattern rendering for any pending injections                          |
| `system_injection` | Rewriting the system message with the injected text                   |

Each list contains one float per step the stage actually ran. Stages that didn't run (e.g. E1 under FAST) produce no entry.

***

## Custom run metadata

`metadata` on `rb.middleware()` (or on `rb.openai_hooks()`) merges into the run record's JSON metadata column. Anything not consumed by named fields rides along.

```python theme={null}
mw = rb.middleware(
    run_id="pr-review-42",
    agent_name="reviewer",
    task="review PR #42",
    model="anthropic:claude-sonnet-4-6",
    org_id="my-org",
    project_id="infra-review",
    metadata={
        "pr_number": 42,
        "repo": "acme/backend",
        "experiment": "sonnet-vs-haiku",
        "ab_group": "B",
    },
)
```

Named fields (`agent_name`, `task`, `framework`, `model`, `codebase_id`, `org_id`, `project_id`, `task_profile`) are popped out before `metadata` is serialized, so they don't double-count.

***

## Self-hosted deployments

```python theme={null}
rb = ReasonBlocks(
    api_key="rb_live_...",
    base_url="https://reasonblocks-api.internal.acme.com",
)
```

`base_url` is forwarded to every internal API client (E-trace retrieval, monitor evaluation, live telemetry). Trailing slashes are stripped.
