Skip to content

Switchyard Integration

The scorer behind shunt's stage router comes from NVIDIA's Switchyard project. What shunt vendors, what it leaves behind, the published benchmark results, and what the integration cost.

Updated View as Markdown

shunt’s stage router does not invent its own scoring. It depends on switchyard-libsy, the routing library from NVIDIA-NeMo/Switchyard — a project that describes itself as routing “each LLM call to the cheapest model that can still do the job.”

This page is the integration record: which part of Switchyard shunt uses, which parts it deliberately does not, what the published numbers actually measure, and what the dependency cost. How each algorithm decides a turn lives with that algorithm — start at the routing overview.

What shunt vendors

Switchyard ships a whole gateway: a proxy server, a provider-neutral protocol, request/response translation, an LLM-judge classifier, escalation, sub-agent routing. shunt already is a gateway, so it takes the scorer, the judge-backed algorithms behind opt-in tables, and the translation codec at that one boundary — and leaves the server.

Switchyard component In shunt
switchyard-libsy stage scorer Yes — pinned to a reviewed revision of upstream main
switchyard-protocol Pulled in as libsy’s own dependency; shunt builds no Request
switchyard-translation At the routing boundary only — neutral ⇄ Anthropic on the judge request and its reply. The Anthropic → Responses wire translation stays shunt’s own src/model/responses_request.rs
switchyard-server No — shunt is the server
LLM classifier / judge Opt-in per entry — [models.router.classifier] on a stage_router, consulted only on the turns the signals leave undecided, plus the judge-backed llm_classifier and composite types; see judge calls
Escalation routing Opt-in per entry — mode = "escalation". The weak turn is held until the judge rules, so the switch to the strong target happens before any header is sent; see held turns
Handoff notes Yes — [models.router.handoff_notes]
Advisor gate Opt-in per entry — type = "advisor", with the executor turn held until the review
Sub-agent routing Yes — [models.subagents]

The whole coupling is one function call. shunt fills libsy’s ToolSignals struct straight from the parsed Anthropic Messages body and calls pick_tier; every field on that struct is public, so no translation layer sits in between.

The dependency is a git pin at an immutable revision of upstream main, not the crates.io release, and carries a rev with no branch key — the same form as shunt’s two tungstenite pins. A bump is therefore a reviewed diff, not something an unrelated cargo update can do. 0.2.0 is the only published release, and main has diverged from it: the algorithms beyond stage scoring — the advisor gate, composite routing, sub-agent routing, tool semantics, handoff notes — exist only there. When checking the API, read the checkout cargo resolved, not the registry source and not GitHub’s default branch, which moves.

The learned prefill router

One more upstream piece is vendored, on its own terms: prefill_router, a learned classifier that scores the latest user turn and picks a target. It is available only from a build that opts into the prefill-router cargo feature, which is off by default.

The reason is what the crate is. Upstream’s crates/prefill-router embeds Python through PyO3, so building it links libpython and running it needs torch, transformers, numpy, and accelerate importable at run time, plus a router checkpoint on disk. Upstream gates it behind a cargo feature for the same reason. shunt’s release workflow never enables the feature, so prebuilt release binaries and the Homebrew formula do not have it — a from-source cargo build --release --features prefill-router is the only way to get it, and a binary without it fails to load a config naming the type, with an error that says so.

See type = "prefill_router" in the configuration reference for the keys, the prerequisites, and the route sources.

Benchmark results

These are upstream Switchyard’s numbers, not shunt’s. shunt has not run Terminal-Bench, and the section after this one is about how much of the result should be expected to carry over.

Terminal-Bench 2.1, against an Opus 4.8 baseline:

Configuration Accuracy Total cost vs. baseline
Opus 4.8 baseline 76.0% $98.06 —
Escalation routing 75.7% $85.00 99.6% of accuracy, 13.3% cheaper
Stage routing 72.7% $68.19 95.7% of accuracy, 30.5% cheaper
Capability routing 71.2% $79.32 93.7% of accuracy, 19.1% cheaper
Kimi K2.6 alone 55.8% $76.28
GLM 5.2 alone 52.4% $16.47
DeepSeek V4 Pro alone 48.7% $96.92
Ultra 3 alone 39.0% $29.66

Stage routing — the row shunt implements — is the cheapest of the three routed configurations and keeps 95.7% of the baseline’s solve rate. The bottom four rows are the control that makes the number interesting: every single fixed model, including ones that cost more than the router did, lands below 56%.

The separate confidence-threshold calibration that produced the 0.5 default was derived from SWE-Bench Pro Python-75, not from Terminal-Bench.

Provenance and caveats

  • The figures are the v0.2.0 Terminal-Bench 2.1 results published in upstream’s own Benchmark Provenance table, which in turn attributes them to Route AI Agent Workloads Across Models with NVIDIA NeMo Switchyard (NVIDIA, 11 August 2026). The blog post does not print the table itself, so check a number against the README rather than against it.
  • Those runs used NVIDIA-internal inference endpoints, so upstream states absolute solve rates may shift on another serving stack.
  • Upstream checks in only the escalation profile (benchmark/routing-profiles/tb21-escalation-opus-glm-deepseek.toml). The exact tier pair behind the 72.7% stage-routing row is not published, so do not read it as a claim about any specific model pair you might configure in shunt.
  • No latency or per-request routing-overhead figures are published with the table; upstream directs that question to its own soak-test docs.

What carries over to shunt, and what does not

The scorer is the same code. Everything around it is shunt’s, so treat the upstream table as evidence that the approach pays rather than as shunt’s own result.

Upstream benchmark shunt
Scoring core switchyard-libsy Same crate, same constants
Signal extraction Upstream’s, provider-neutral shunt’s own, from Anthropic Messages blocks
tests_passed / repeated_failure Available to the scorer Always false — never populated
compacted Available to the scorer Set from x-claude-code-context-compacted and latched onto the session pin
Judge on ambiguous turns Optionally available Off by default, falling open to the picker default; opt in per entry with [models.router.classifier]
Tier stability across turns Scored per turn Session-pinned with asymmetric hysteresis
Workload Terminal-Bench 2.1 tasks Claude Code sessions

Two of those cut in opposite directions. shunt leaves tests_passed and repeated_failure unpopulated, which costs it libsy’s repeated-failure escalation — strictly less signal than the benchmark had. Both need evidence this extractor does not have: the text of a tool result. The wire carries that text — a tool_result block has a content field — but the extractor deliberately does not read it: deciding these signals from free-form result text means content-pattern heuristics, and a structural proxy would fire for ls as readily as for a test run. compacted is not in that group: it comes from a request header rather than from the transcript, so libsy’s compaction override does fire here — see Stage Router. Against that, shunt’s hysteresis exists precisely because a per-turn flip is expensive in a Claude Code session in ways a benchmark harness never pays (see Why going down is harder than going up).

Where the algorithms are documented

Switchyard is provider-neutral and does not have to price a tier flip. shunt is a Claude Code gateway and does, and that difference produces most of what shunt adds on top of the vendored code. Each part lives with the algorithm it belongs to:

  • The stage scorer — signals, dimensions, score, and decision, plus shunt’s Claude Code tool vocabulary, asymmetric hysteresis, and session pinning — is in Stage Router.
  • What every router shares — targets as ordinary model ids, the id the client sees, judge calls, and held turns — is in the routing overview.
  • The judge-backed types each have a page: LLM Classifier, Escalation, Advisor Gate, and Composite.
  • Delegated work, and how shunt reads “delegated” differently from upstream’s claude_lineage header, is in Subagent Routing.

What the integration cost

Measured, not estimated, against 0.2.0 when the dependency was first adopted:

Release binary +115,376 bytes (+0.31%)
Cargo.lock packages +33 (412 → 445)
Of which unused ~26, the jsonschema tree serving the classifier path shunt never calls
Actually linked The jsonschema tree is not — zero jsonschema strings and zero num-bigint symbols in the built binary
Porting alternative 137 lines

The binary result is what decided it: the linker drops the classifier tree entirely, so the cost of depending on the crate is roughly 0.3%. The 137-line port stays on file as the exit if the pin goes stale.

Moving from that release to the main pin added no packages at all — the lock stayed at 445, because libsy’s new direct dependencies there (http, regex, uuid) were already in shunt’s tree. The linked-or-not result holds on the new pin too: the release binary still carries zero jsonschema strings and zero num-bigint symbols.

Taking switchyard-translation for the judge boundary added three lock entries: the crate itself plus async-stream and async-stream-impl. switchyard-protocol was already in the tree as libsy’s own dependency.

Attribution

switchyard-libsy is Apache-2.0, © NVIDIA Corporation. shunt uses its stage scorer unmodified. The design record for shunt’s side — including each capability declined and why — is ADR-0004, and the decision to pin upstream main rather than the release is ADR-0005.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close