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.
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",
)
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.
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 |
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.
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 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.
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:
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.
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.
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.
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.
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
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.