Skip to content

feat(libsy): stage router (signals only) - #135

Merged
messiaen merged 9 commits into
mainfrom
grclark/libsy-stage-router
Jul 28, 2026
Merged

messiaen merged 9 commits into
mainfrom
grclark/libsy-stage-router

Conversation

@messiaen

@messiaen messiaen commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

What

  • Implements stage_router tier_picker piece as a Classifier in libsy.
  • No llm fallback yet, once we have llm_classifier processor / classifier, we can join them with fall_through
  • bridge for switchyard-components

How tested

  • uv run ruff check . clean
  • uv run mypy switchyard clean
  • uv run pytest tests/ green
  • Manual smoke (describe what was run)

Checklist

  • One class per file; filename = snake_case of the primary class.
  • New public symbols exported from switchyard/__init__.py.__all__ if intended for downstream use.
  • Unit tests added for new components / bug fixes.
  • README / --help updated if customer-facing surface changed.
  • Commits signed off (Signed-off-by: Your Name <email>) per the DCO.

Notes for reviewers

Anything reviewers should pay extra attention to — risky paths, follow-up tickets, intentional trade-offs.

Summary by CodeRabbit

  • New Features
    • Added automatic stage routing that selects efficient or capable processing based on request signals.
    • Added escalation for critical conditions and de-escalation when work is settled.
    • Added confidence-based fallback classification when routing signals are inconclusive.
    • Added processing to capture recent tool activity for routing decisions.
  • API Updates
    • Exposed stage-routing scores, dimensions, outcomes, and tier-selection utilities through the library interface.

@messiaen messiaen self-assigned this Jul 24, 2026
@messiaen messiaen changed the title Grclark/libsy stage router feat(libsy): stage router (signals only) Jul 24, 2026
@sabhatinas
sabhatinas force-pushed the sabhatinas/stage-router-scorer-corroborative-tanh branch from 7cb8a7b to 96fc5e3 Compare July 24, 2026 18:22
Base automatically changed from sabhatinas/stage-router-scorer-corroborative-tanh to main July 24, 2026 18:27
@messiaen
messiaen force-pushed the grclark/libsy-stage-router branch from c8d9195 to ac31ad5 Compare July 24, 2026 18:43
@sabhatinas

Copy link
Copy Markdown
Contributor

Will you also be updating docs?

@messiaen

Copy link
Copy Markdown
Contributor Author

Will you also be updating docs?

My plan was to update the user docs when we have the full router working.

@messiaen
messiaen marked this pull request as ready for review July 24, 2026 20:47
@messiaen
messiaen requested a review from a team as a code owner July 24, 2026 20:47
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Stage Router

Layer / File(s) Summary
Tool signal state plumbing
crates/libsy/src/algorithms/util.rs, crates/libsy/src/algorithms/util/tool_signals.rs, crates/libsy/src/core/state.rs
Registers the new modules, stores optional ToolSignals in State, and processes request events into signal state.
Signal scoring model
crates/libsy/src/algorithms/util/stage_router.rs
Defines routing types, projects signals into coding dimensions, and calculates signed scores with confidence.
Tier decisions and classifier integration
crates/libsy/src/algorithms/util/stage_router.rs
Applies override, threshold, and fall-open routing rules, integrates StageClassifier, and adds unit tests.
Public API forwarding
crates/libsy/src/lib.rs, crates/switchyard-components/src/stage_router.rs
Publishes the libsy stage-router API and forwards the components API to it.

Estimated code review effort: 4 (Complex) | ~60 minutes

Poem

I’m a rabbit routing through the code,
With signals tucked beneath my load.
Strong or weak, the tiers now know,
Tests can make the pathway flow.
Hop, hop—exports bloom in sight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: adding the libsy stage router with signals-only behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/libsy/src/algorithms/util/stage_router.rs`:
- Line 310: Make StageClassifier public and add its documentation in
crates/libsy/src/algorithms/util/stage_router.rs:310-310, then re-export it from
stage_router in crates/libsy/src/lib.rs:92-96 and forward that export through
the components bridge in crates/switchyard-components/src/stage_router.rs:12-15
so consumers can construct and register it.
- Around line 329-359: Update the PickOutcome::Resolved and
PickOutcome::ConsultClassifier branches to use the outcome-provided confidence
for resolved selections instead of deriving it from signed score. Keep fall-open
results genuinely ambiguous: do not remap the signed score onto default_tier or
emit high confidence for the default weak target, while preserving
default_target state handling.

In `@crates/libsy/src/algorithms/util/tool_signals.rs`:
- Around line 259-262: Add /// documentation for ToolSignalProcessor and its
recent_window field in crates/libsy/src/algorithms/util/tool_signals.rs:259-262.
Add a /// comment in crates/libsy/src/core/state.rs:24 describing the lifecycle
and absence semantics of tool_signals, and document the public stage_router
module in crates/libsy/src/lib.rs:92; use Rust documentation comments for all
three public APIs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d6162c00-6a3b-48de-ae14-2c8c1702ca6b

📥 Commits

Reviewing files that changed from the base of the PR and between 3269dce and ac31ad5.

📒 Files selected for processing (7)
  • crates/libsy/src/algorithms/util.rs
  • crates/libsy/src/algorithms/util/stage_router.rs
  • crates/libsy/src/algorithms/util/tool_signals.rs
  • crates/libsy/src/core/state.rs
  • crates/libsy/src/lib.rs
  • crates/libsy/src/signal.rs
  • crates/switchyard-components/src/stage_router.rs
💤 Files with no reviewable changes (1)
  • crates/libsy/src/signal.rs

Comment thread crates/libsy/src/algorithms/util/stage_router.rs Outdated
Comment thread crates/libsy/src/algorithms/util/stage_router.rs Outdated
Comment thread crates/libsy/src/algorithms/util/tool_signals.rs
Comment thread crates/libsy/src/algorithms/util/stage_router.rs
Comment thread crates/switchyard-components/src/stage_router.rs
Comment thread crates/libsy/src/algorithms/util/tool_signals.rs
@messiaen
messiaen force-pushed the grclark/libsy-stage-router branch from ac31ad5 to 0775ad6 Compare July 24, 2026 20:58
Comment thread crates/libsy/src/algorithms/util/stage_router.rs Outdated
Comment thread crates/libsy/src/algorithms/util/stage_router.rs Outdated
@sabhatinas

Copy link
Copy Markdown
Contributor

Pushed a commit to this branch (3ba1804):

  • Addressed the DEFAULT_WEIGHTS comment — score_signal now uses the direct, compile-checked expression; dropped the string-keyed weights table and CodingAgentDimensions::value().
  • Made the picker configurable — StageClassifier was hardcoding PickerMode::EfficientFirst, 0.5; it now takes mode + confidence_threshold via StageClassifier::new(...) and is exported from switchyard_libsy::stage_router. Added a test proving the mode is honored (capable_first falls open to strong, efficient_first to weak).

All 9 libsy stage_router tests pass; cargo fmt + clippy clean. Parity with the v2 scorer/picker unchanged.

@sabhatinas

Copy link
Copy Markdown
Contributor

9f671cc: resolved the no-signal TODO using the new config — a turn with no tool activity yet now falls open to the picker's configured default tier (efficient_first → weak, capable_first → strong) and records default_target, instead of abstaining with an empty set. Matches the reference stage_router's no_signal → default_tier behavior. Updated the test accordingly (classifier_defaults_without_tool_signals); 9 libsy tests pass, clippy clean.

Left the other TODO (add the non-target to this score set) for you — that's a scoring-shape design call.

@sabhatinas

Copy link
Copy Markdown
Contributor

Design note for the follow-up parity work (tracked in SWITCH-1110), flagging it here since it may shape the Classification shape you land:

StageClassifier currently drops the DecisionSource. score() maps the outcome to strong/weak, but the reason (Override / Dimensions / TestsPassed / FallOpen) doesn't travel downstream. The handoff-notes feature needs it: the escalation note is direction-specific (weak→strong) and gated by only_on_wrong_signal_escalation — it should fire only when the escalation was signal-driven (Dimensions/Override), never on a fall_open default. Without the source surfaced (e.g. stashed in state.extra alongside default_target), a downstream handoff-note processor can't tell a real escalation from a defaulted one.

Not asking you to build handoff notes here — just consider surfacing the DecisionSource so the signals-only picker doesn't have to change shape later.

@sabhatinas

Copy link
Copy Markdown
Contributor

Addressed in 8c627b3 on this branch — StageClassifier now surfaces the DecisionSource in state.extra["decision_source"] (override / dimensions / tests_passed for resolved turns, fall_open for fall-open/no-signal). So the source does travel downstream now, and a handoff-note processor can gate only_on_wrong_signal_escalation on decision_source ∈ {dimensions, override}. Tests assert it. Retracting my earlier "drops the DecisionSource" note — it's handled.

@sabhatinas

Copy link
Copy Markdown
Contributor

Addressed the CodeRabbit findings in 184c178:

  • Expose StageClassifier through the bridge — switchyard-components::stage_router now re-exports StageClassifier (it was already pub + exported from switchyard_libsy::stage_router).
  • Confidence mapping — score() now emits score.abs() (the magnitude) for resolved outcomes instead of the signed/remapped value, so a resolved-weak -0.6 reports 0.6. (Direction still travels via the target string + decision_source.)
  • Public-item docs — added /// docs for ToolSignalProcessor + recent_window, State.tool_signals (lifecycle / absence semantics), and the stage_router module.

libsy + components build clean; 9 stage_router tests pass; clippy clean; no new rustdoc warnings.

Not touching Nachiket's structural asks here (module layout, deleting the old switchyard-components files, moving tool_signals parsing to switchyard-protocol/IR) — flagging those for @messiaen to scope on this PR vs a follow-up.

messiaen and others added 8 commits July 28, 2026 10:36
Signed-off-by: Greg Clark <grclark@nvidia.com>

feat(libsy): bridge back to components for stage_router

Signed-off-by: Greg Clark <grclark@nvidia.com>
…ct field math

Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
…handoff gating

Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
…nce, doc public items

Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
…t::Decision

Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
Signed-off-by: Greg Clark <grclark@nvidia.com>
@messiaen
messiaen force-pushed the grclark/libsy-stage-router branch from 5e11262 to 87fbde1 Compare July 28, 2026 14:40
@messiaen
messiaen merged commit e3e7c47 into main Jul 28, 2026
15 checks passed
@messiaen
messiaen deleted the grclark/libsy-stage-router branch July 28, 2026 14:49
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.

3 participants