Skip to content

feat(libsy): advisor review gate as a native algorithm and advisor route type - #371

Open
eric-liu-nvidia wants to merge 4 commits into
mainfrom
claude/advisor-review-gate-rust
Open

feat(libsy): advisor review gate as a native algorithm and advisor route type#371
eric-liu-nvidia wants to merge 4 commits into
mainfrom
claude/advisor-review-gate-rust

Conversation

@eric-liu-nvidia

@eric-liu-nvidia eric-liu-nvidia commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Adds the advisor review gate to the Rust server as a new route type: type = "advisor".

Benchmark results (Terminal-Bench 2.1)

Configuration Accuracy
Nemotron 3 Ultra (baseline) 43.8%
Nemotron 3 Ultra + Opus 4.8 as advisor review gate 54.7% ± 0.7 pp

With $64.76 ± $4.95 additional Opus 4.8 cost, the advisor review gate lifts Nemotron 3 Ultra accuracy by almost 25%.

How it works

There are two models: an executor (the model doing the work) and an advisor (a stronger model that checks the work).

  1. The client talks to the executor as usual. While the executor is still working — its replies contain tool calls — everything passes straight through. The advisor is not involved.
  2. When the executor produces a reply with no tool calls, that means it thinks it is done (or it is proposing a plan). The proxy holds that reply back instead of returning it.
  3. The proxy sends the conversation and the held-back reply to the advisor and asks one question: is this work complete and correct? The advisor answers APPROVE or REDO.
  4. APPROVE → the held-back reply is released to the client unchanged.
    REDO → the client never sees that reply. The proxy puts the advisor's feedback into the conversation ("not done yet — here is what is missing") and calls the executor again, so it keeps working.
  5. The review happens once per session (max_reviews, default 1). After that, every call is a plain passthrough with zero overhead.

If the advisor is down or replies with something unparseable, the gate fails open: the executor's reply goes to the client as if approved. Advisor problems never block the executor.

Why this shape: giving advice up front made the executor trust the plan and skip its own test-and-iterate loop. Checking only the final "I'm done" claim leaves the executor's normal behavior untouched and catches exactly one failure mode — stopping too early.

Example

[routes.gated]
id = "switchyard-advisor"
type = "advisor"
executor_target = "executor"
advisor_target = "advisor"   # judge-only; set max_retries = 0 on its llm_client

Note for reviewers: this adds regex as a production dependency of libsy (for the configurable review trigger pattern).

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://NVIDIA-NeMo.github.io/Switchyard/pr-preview/pr-371/

Built to branch gh-pages at 2026-08-11 21:14 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Advisor gate

Layer / File(s) Summary
Public API and route contract
Cargo.toml, crates/libsy/Cargo.toml, crates/libsy/src/algorithms.rs, crates/libsy/src/algorithms/advisor_gate.rs, crates/libsy/src/lib.rs
Adds the AdvisorGate configuration types, public exports, module wiring, and workspace dependencies.
AdvisorGate execution flow
crates/libsy/src/algorithms/advisor_gate.rs, crates/libsy/src/algorithms/util/*
Buffers executor turns, invokes the advisor on configured triggers, handles APPROVE and REDO, supports streaming replay, enforces scoped budgets, and records telemetry.
Server route construction
crates/switchyard-server/src/config.rs
Adds Advisor routes, target resolution, trigger validation, capability handling, defaults, and configuration tests.
Advisor gate statistics
crates/switchyard-server/src/stats/algorithms.rs, crates/switchyard-server/src/stats/algorithms/advisor_gate.rs
Projects advisor-gate Prometheus counters into reset-relative serialized snapshots.
End-to-end advisor coverage
crates/switchyard-server/tests/server.rs
Tests approval, redo, budgets, streaming, routing logs, token counting, failures, and statistics resets.

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

Poem

A rabbit reviews each turn,
“APPROVE” makes bright replies return.
“REDO” sends feedback down the track,
Streams replay, then hop right back.
Budgets, stats, and tests all bloom.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 86.96% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding the AdvisorGate algorithm and introducing the advisor route type.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/switchyard-server/src/stats/algorithms.rs`:
- Line 31: Add concise Rust doc comments for the public advisor_gate field in
crates/switchyard-server/src/stats/algorithms.rs (lines 31-31), describing when
it appears in the serialized response. Also document every public field in
crates/switchyard-server/src/stats/algorithms/advisor_gate.rs (lines 42-53),
specifying each field’s count and grouping semantics.

In `@crates/switchyard-server/src/stats/algorithms/advisor_gate.rs`:
- Line 192: Update the stats binding in the test around StatsAccumulator::new to
be mutable, so the later StatsAccumulator::reset(&mut self) call compiles.
🪄 Autofix

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: 585f7428-96a2-4510-9e3b-71963e67e64e

📥 Commits

Reviewing files that changed from the base of the PR and between 58f355a and 315d4b0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (11)
  • Cargo.toml
  • crates/libsy/Cargo.toml
  • crates/libsy/src/algorithms.rs
  • crates/libsy/src/algorithms/advisor_gate.rs
  • crates/libsy/src/algorithms/util/llm_judge.rs
  • crates/libsy/src/algorithms/util/prompts.rs
  • crates/libsy/src/lib.rs
  • crates/switchyard-server/src/config.rs
  • crates/switchyard-server/src/stats/algorithms.rs
  • crates/switchyard-server/src/stats/algorithms/advisor_gate.rs
  • crates/switchyard-server/tests/server.rs

#[derive(Clone, Debug, Default, PartialEq, Serialize)]
pub(crate) struct AlgorithmStatsSnapshot {
#[serde(skip_serializing_if = "Option::is_none")]
pub advisor_gate: Option<AdvisorGateStatsSnapshot>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the new public statistics fields.

  • crates/switchyard-server/src/stats/algorithms.rs#L31-L31: document when advisor_gate is present in the serialized response.
  • crates/switchyard-server/src/stats/algorithms/advisor_gate.rs#L42-L53: document the count and grouping semantics of each public field.

As per coding guidelines, add concise /// documentation for public Rust items.

📍 Affects 2 files
  • crates/switchyard-server/src/stats/algorithms.rs#L31-L31 (this comment)
  • crates/switchyard-server/src/stats/algorithms/advisor_gate.rs#L42-L53
🤖 Prompt for 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.

In `@crates/switchyard-server/src/stats/algorithms.rs` at line 31, Add concise
Rust doc comments for the public advisor_gate field in
crates/switchyard-server/src/stats/algorithms.rs (lines 31-31), describing when
it appears in the serialized response. Also document every public field in
crates/switchyard-server/src/stats/algorithms/advisor_gate.rs (lines 42-53),
specifying each field’s count and grouping semantics.

Source: Coding guidelines

.unwrap_or_else(|error| panic!("failed to build metrics exporter: {error}"));
let provider = SdkMeterProvider::builder().with_reader(exporter).build();
let meter = provider.meter("switchyard");
let stats = StatsAccumulator::new(registry, ["advisor_gate"]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Make stats mutable.

Line 236 calls StatsAccumulator::reset(&mut self), but this binding is immutable. The test does not compile.

Proposed fix
-        let stats = StatsAccumulator::new(registry, ["advisor_gate"]);
+        let mut stats = StatsAccumulator::new(registry, ["advisor_gate"]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let stats = StatsAccumulator::new(registry, ["advisor_gate"]);
let mut stats = StatsAccumulator::new(registry, ["advisor_gate"]);
🤖 Prompt for 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.

In `@crates/switchyard-server/src/stats/algorithms/advisor_gate.rs` at line 192,
Update the stats binding in the test around StatsAccumulator::new to be mutable,
so the later StatsAccumulator::reset(&mut self) call compiles.

@eric-liu-nvidia
eric-liu-nvidia force-pushed the claude/advisor-review-gate-rust branch from 315d4b0 to 136bc21 Compare August 11, 2026 21:13
Signed-off-by: zengyuanl <zengyuanl@nvidia.com>
Signed-off-by: zengyuanl <zengyuanl@nvidia.com>
Signed-off-by: zengyuanl <zengyuanl@nvidia.com>
Signed-off-by: zengyuanl <zengyuanl@nvidia.com>
@ayushag-nv

Copy link
Copy Markdown
Contributor

@eric-liu-nvidia can you address coderabbit first

@nachiketb-nvidia nachiketb-nvidia 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.

Let's break this up into 2 or 3 MRs

  • one for core logic
  • one more for potential telemetry
  • one more for server side config changes

also, if we have any prompts, let's make sure they're also knobs

}

#[tokio::test]
async fn advisor_route_approve_flow_and_stats() -> TestResult {

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.

Can you wrap all of your tests in a #[cfg(test)] mod tests {? That will ensure they don't get compiled unless you are running tests. Grep for those strings in most other files for an example.

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.

4 participants