Skip to main content
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:
ParameterDefaultMeaning
fast_threshold0.2Score below which a step counts as “easy”
slow_threshold0.6Score above which a step counts as “hard”
skip_threshold0.85Score above which sustained hardness escalates to SKIP
hysteresis_margin0.1Buffer that prevents thrashing at state boundaries
fast_window6Consecutive easy steps to enter FAST
slow_window5Consecutive hard steps to enter SLOW
skip_window35Consecutive 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:
BucketWhat’s measured
monitor_scoringMonitorSteeringInjection.retrieve latency (POST /monitors/evaluate)
e1_retrievalE1 vector search (skipped under FAST and when E1 gate denies)
e2_retrievalE2 commons retrieval (skipped under FAST)
e3_retrievalE3 standing-rule scroll-all (step 0 only)
format_routingPattern rendering for any pending injections
system_injectionRewriting 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.
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.