TwinRouterBench exposes the construction stages behind the static routing supervision as a versioned Python package and CLI. The implementation follows the v2 paper protocol for both multi-step and single-turn collection paths.
The default path is completely offline. It uses five deterministic synthetic fixtures to exercise the same state transitions as a live run; no API key, network connection, Docker daemon, or original benchmark download is required.
The public entry point is a complete, credential-free JSON pipeline config:
twinrouterbench data run \
--config configs/data_generation/custom_qa_pipeline.json \
--output-dir runs/custom-qafrom twinrouterbench.data_generation import run_pipeline
manifest = run_pipeline("pipeline.json", output_dir="runs/my-benchmark")The shared engine never switches on benchmark names. A suite config declares:
{
"schema": "twinrouterbench.data_pipeline.v1",
"backend": "mock",
"benchmarks": {
"my_benchmark": {
"display_name": "My Benchmark",
"scenario": "single_turn_qa",
"source": {
"uri": "local://tasks.jsonl",
"license": "CC-BY-4.0",
"version": "v1"
},
"loader": {
"type": "single_turn",
"options": {
"field_map": {
"instance_id": "id",
"prompt": "question",
"reference": "answer",
"target_tier": "target_tier"
}
}
},
"evaluation": {
"trial": "exact_match",
"final": "exact_match"
}
}
},
"run": {"benchmarks": "all", "output_dir": "runs/my-benchmark"}
}Built-in loaders are normalized and single_turn. Built-in evaluators are
execution, backend_judge, exact_match, and contains. The generic
OpenAI-compatible executor is exposed as
twinrouterbench.data_generation.openai_backend:create_backend; its API key is
read only from an environment variable.
Complex agent benchmarks cannot be made declarative at the sandbox boundary.
They implement the narrow TaskLoader, ExecutionEvaluator, or
ExecutionBackend protocols through module:factory; search, caching, review,
provenance, and publication remain unchanged. Thus adding a normal QA benchmark
requires only data plus config, while adding SWE-bench-like environments
requires one thin harness plugin rather than a new pipeline.
Validate configs before spending API budget:
twinrouterbench data config validate --config pipeline.json
twinrouterbench data suite validate --config benchmark-suite.jsonFor each normalized task, the pipeline performs:
- Run or replay a successful strong-model seed trajectory. Failed seeds are rejected.
- Produce one conservative downgrade-search hint per routed step. A hint is a pruning device and never becomes a label without execution.
- Apply Algorithm A1 sequential locking. Previously accepted steps keep their
locked assignments, the current step uses a candidate tier/model, and all
future steps remain at
high. - Execute the complete mixed-model trajectory. A candidate is accepted only when the task passes and the trajectory has the same number of steps as the seed.
- Probe up to three models inside each candidate tier. The tier passes when at least one pool model passes.
- Rebuild later router-visible prefixes from the successful mixed trajectory. Cached trials are keyed by instance, step, assignments, and generation parameters; downstream entries are invalidated when an upstream lock changes.
- Apply the hardened Faithfulness/Appropriateness/Completeness judge to mtRAG and QMSum. Evidence conflicts, incomplete answers, and uncertainty fail.
- Export approximately 10% of BFCL, SWE-bench, and PinchBench routed steps for manual review.
- Apply
tightorfurther_downgradeableverdicts.uncertainblocks publication. - Validate and merge finalized runs into a separate release-candidate directory.
Generate all five fixture datasets:
twinrouterbench data generate \
--benchmark all \
--backend mock \
--config configs/data_generation/mock_all.json \
--output-dir runs/data-generation/mock-allGenerate one dataset:
twinrouterbench data generate \
--benchmark swebench \
--backend mock \
--output-dir runs/data-generation/mock-swebenchLimit a smoke run to the first normalized case from each selected benchmark:
twinrouterbench data generate \
--benchmark all \
--backend mock \
--max-cases 1 \
--output-dir runs/data-generation/mock-one-eachThe same APIs are importable:
from twinrouterbench.data_generation import GenerationConfig, GenerationPipeline
from twinrouterbench.data_generation.backends import MockBackend
from twinrouterbench.data_generation.pipeline import load_model_pool
_, pool = load_model_pool()
pipeline = GenerationPipeline(
output_dir="runs/data-generation/example",
backend=MockBackend(pool),
config=GenerationConfig(run_id="example"),
)
pipeline.generate("bfcl")Export a writable review template:
twinrouterbench data review export \
--run-dir runs/data-generation/mock-all \
--output reviews/mock-all.jsonlEach queued row accepts one verdict:
tight: keep the generated tier;further_downgradeable: lower the tier by exactly one level;uncertain: leave the run blocked for another review pass.
Apply completed reviews:
twinrouterbench data review apply \
--run-dir runs/data-generation/mock-all \
--reviews reviews/mock-all.jsonllabels.final.jsonl is emitted only after every queued row is resolved.
Generation never writes directly to data/static/. Publish one or more ready
runs into an isolated candidate directory:
twinrouterbench data publish \
--runs runs/data-generation/mock-all \
--output-dir runs/release-candidate/static-v2
twinrouterbench data validate \
--path runs/release-candidate/static-v2Publication checks required fields, tier/id consistency, duplicate IDs, contiguous steps, source/license metadata, unresolved reviews, and internal model/reviewer field leakage.
--backend mock uses deterministic fixtures for SWE-bench, BFCL, mtRAG,
QMSum, and PinchBench. It is the CI path.
Every run writes backend_events.jsonl. Re-run the complete algorithm without
calling a model or harness:
twinrouterbench data generate \
--benchmark all \
--backend replay \
--replay-log runs/data-generation/mock-all/backend_events.jsonl \
--output-dir runs/data-generation/replayedFor the supplied fixtures, mock and replay produce byte-identical labels and trial logs.
Live infrastructure is supplied as a module:factory plugin implementing
ExecutionBackend:
twinrouterbench data generate \
--benchmark swebench \
--backend live \
--live-backend my_harness.backend:create_backend \
--source swebench=local:///data/normalized/swebench/tasks.jsonl \
--output-dir runs/data-generation/swe-liveThe plugin owns provider and benchmark execution. Credentials must be read from the environment or a secret manager; they are never part of the generation config or run manifest.
The repository includes a provider smoke plugin that makes a real CommonStack chat-completion request for every seed, hint, mixed-execution, and judge backend operation while retaining the deterministic fixture oracle:
export COMMONSTACK_API_KEY=... # never commit this value
twinrouterbench data generate \
--benchmark all \
--backend live \
--live-backend twinrouterbench.data_generation.commonstack_smoke:create_backend \
--config configs/data_generation/commonstack_smoke.json \
--output-dir runs/data-generation/commonstack-smokeThis verifies credentials, provider transport, plugin loading, all five adapters, downgrade search, judging, and artifact generation. Pass/fail remains fixture-oracle based, so these smoke outputs are not official benchmark scores and must not be published as supervision data. Each provider probe includes a bounded preview of the normalized task messages so message serialization and real task content are exercised without recording provider responses.
Accepted source URI forms are:
local:///absolute/path/tasks.jsonl
hf://organization/dataset@revision/path
github://owner/repository@commit/path
fixture://swebench
Remote URIs record provenance only. The pipeline does not download them
implicitly. Materialize remote data explicitly and pass a local:// path for a
real run. GitHub/Hugging Face revisions should be pinned to immutable commits
for release construction.
Normalized inputs are JSON or JSONL documents containing TaskSpec fields.
The five adapter fixtures under twinrouterbench/data_generation/fixtures/
serve as minimal schema examples.
Each run is isolated and contains:
config.lock.json
backend_events.jsonl
seed_trajectories.jsonl
downgrade_hints.jsonl
execution_trials.jsonl
rejections.jsonl
labels.pre_review.jsonl
review_queue.jsonl
reviews.applied.jsonl # after review
labels.final.jsonl # only when ready
manifest.json
The public question_bank.jsonl is produced only by data publish. Internal
fields such as selected model IDs, reviewer identity, and search traces are not
copied into public rows.
The full suite is offline:
python -m pytest -qIt covers all five built-ins, arbitrary config-only benchmarks, generic OpenAI-compatible execution, loader/evaluator plugins, single-dataset generation, cascade fallback, sequential locking, mixed-prefix reconstruction, cache invalidation, fixed-step acceptance, hardened judging, review correction, publication gates, source URI validation, replay equivalence, CLI behavior, and golden output hashes.