Skip to content

fix(server): report stage-router stats for a composite route - #943

Open
v0ropaev wants to merge 1 commit into
NVIDIA-NeMo:mainfrom
v0ropaev:fix/composite-route-stage-router-stats
Open

v0ropaev wants to merge 1 commit into
NVIDIA-NeMo:mainfrom
v0ropaev:fix/composite-route-stage-router-stats

Conversation

@v0ropaev

@v0ropaev v0ropaev commented Oct 7, 2026 •

Copy link
Copy Markdown

AlgorithmStats::new creates the stage-router baseline only when some route's algorithm name is exactly "stage_router":

stage_router_baseline: algorithms
    .contains(STAGE_ROUTER)
    .then(|| StageRouterCumulative::collect(&families)),

A composite route is a stage route under another name. CompositeRouter::new ends with

let route = build_stage_route(config.stage)?
    .with_name(COMPOSITE)
    .with_processor(Arc::new(setter));

and CompositeRouter::name() returns "composite", while the StageClassifier inside it calls record_score_metrics and record_routing_decision on every turn, with no dependence on the route name. So the counters reach /metrics and the JSON block does not: with no baseline the snapshot field stays None and #[serde(skip_serializing_if = "Option::is_none")] drops it, and GET /v1/stats answers "algorithm_stats":{}.

What the operator loses is the whole decision-source breakdown the stage-router documentation promises (docs/routing_algorithms/stage_router_routing.md:154, "/v1/stats reports decision counts by source and selected target, plus aggregate" scoring) for one of the documented strategies. subagents and auto are unaffected: the former delegates name() to its parent and the latter builds a real StageRouter.

The change keys the baseline on either name, next to the two names already hardcoded in that file.

Worth saying out loud: this is the narrow fix, not the structural one. The structural one is for libsy to say which algorithms a route composes, something like an Algorithm::composed_names() defaulting to [self.name()] and overridden by CompositeRouter, so that the next wrapper around build_stage_route does not reintroduce the gap. That touches a trait in another crate and is yours to want or not; say so and I will send it that way instead.

Tested: a_composite_route_reports_its_stage_router_stats, next to the existing projection test, driving StatsAccumulator::new(registry, ["composite"]) and the same instruments the existing test uses. It fails on main at the unwrap_or_else with "stage-router stats missing for a composite route". cargo test --workspace 926 passed, 0 failed. cargo fmt --all --check and cargo clippy -p switchyard-server --all-targets -- -D warnings clean on toolchain 1.99.

Not run: a live composite deployment. The test records the instruments directly rather than routing a turn through a composite route, because the recording path goes through the process-global OTel meter provider and that would make the test order-dependent. The half that is read rather than run is "a composite route records these instruments", which follows from CompositeRouter::new building the route with build_stage_route and StageClassifier::score recording unconditionally.

Summary by CodeRabbit

  • Bug Fixes
    • Stats snapshots for composite routes now include stage-router routing metrics, including routing decisions.

AlgorithmStats::new creates the stage-router baseline only when a route's
algorithm name is literally "stage_router". A composite route is a stage
route under another name: CompositeRouter is

    build_stage_route(config.stage)?.with_name(COMPOSITE)

and its StageClassifier records switchyard.stage_router.routing_decisions
and the scoring histograms on every turn regardless of the route name. With
no baseline the snapshot field stays None and serde skips it, so an operator
running a composite route gets algorithm_stats: {} from /v1/stats while
/metrics carries the counters, and loses the decision-source breakdown the
stage-router docs promise.

Key the baseline on either name. The wider fix is for libsy to say which
algorithms a route composes, so the next wrapper does not reintroduce this;
this keeps the change inside the server, next to the two names already
hardcoded there.

Signed-off-by: Dmitry Voropaev <dy.voropaev@gmail.com>
@v0ropaev
v0ropaev requested a review from a team as a code owner October 7, 2026 16:16
@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA-NeMo/Switchyard/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: e42be69c-1eb9-455a-96fb-66fb97056912
📥 Commits

Reviewing files that changed from the base of the PR and between a3cdc51 and 4098e31.

📒 Files selected for processing (2)
  • crates/switchyard-server/src/stats/algorithms.rs
  • crates/switchyard-server/src/stats/algorithms/stage_router.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 9 remain after this review.


Walkthrough

Composite routes now include stage-router metrics in stats snapshots. A new test checks that a composite route reports three recorded routing decisions for llm-classifier.

Changes

Composite Route Stats

Layer / File(s) Summary
Collect stage-router metrics for composite routes
crates/switchyard-server/src/stats/algorithms.rs, crates/switchyard-server/src/stats/algorithms/stage_router.rs
The stats accumulator collects the stage-router baseline for composite as well as stage_router. A test verifies that a composite-route snapshot reports three llm-classifier routing decisions.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 4098e

Composite routes now have the metric baseline needed to report their stage-router decisions in /v1/stats. No actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reporting stage-router statistics for composite routes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit counts the routes in spring,
Three choices hop through stats to sing.
Composite joins the stage-router trail,
Its snapshot keeps each count in scale.
I tuck the numbers in my den,
Then check the total: three again!

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant