Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/libsy/src/algorithms/vgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use switchyard_protocol::Request;

use self::text::ToolRecord;

mod decide;
mod readout;
mod render;
mod rungs;
mod text;

#[cfg(test)]
Expand Down
133 changes: 133 additions & 0 deletions crates/libsy/src/algorithms/vgr/decide.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! The frozen commit rules: pure, and indeterminate evidence never commits.
//!
//! A signal is `None` when its verifier was never consulted and `Some(Unknown)`
//! when it was consulted and gave no usable verdict.

use super::{Branch, Capabilities};

const READOUT: f64 = 0.9;
const DELIBERATION: f64 = 0.5;
/// Most tool results a run may contain and still recover without the capable tier.
const SHORT_RUN_MAX_TOOL_RESULTS: i32 = 100;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum Route {
Local,
Cloud,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum Tri {
Yes,
No,
Unknown,
}

/// Evidence gathered for one decision.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(super) struct Signals {
/// Local logprob score that the attempt is correct.
pub(super) readout: Option<f64>,
/// Local deliberating verdict, mapped to 1.0 or 0.0.
pub(super) deliberation: Option<f64>,
/// Capable-tier evidence verdict.
pub(super) cloud_judge: Option<Tri>,
/// Capable-tier answer verdict after `cloud_judge` affirms.
pub(super) evidence_confirm: Option<Tri>,
/// Capable-tier verdicts on the answer branch.
pub(super) answer_verifier: Option<Tri>,
pub(super) evidence_verifier: Option<Tri>,
}

/// How an agentic run's tool record lets it spend verification rungs.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum AgenticRun {
/// Earlier tool errors veto the commit; no rung can change it.
Vetoed,
Clean,
/// A bounded run that failed and ended clean: local rungs only.
ShortRecovered,
/// A long run with a clean tail: local rungs, then the capable-tier judge.
ConfirmedRecovery,
}

pub(super) fn agentic_run(
caps: &Capabilities,
confirmed_min_clean_tail: Option<i32>,
) -> AgenticRun {
let Some(tools) = caps.tools else {
return AgenticRun::Vetoed;
};
if tools.errors == 0 {
AgenticRun::Clean
} else if tools.results <= SHORT_RUN_MAX_TOOL_RESULTS && tools.tail_clean {
AgenticRun::ShortRecovered
} else if confirmed_min_clean_tail.is_some_and(|min| tools.clean_tail >= min) {
AgenticRun::ConfirmedRecovery
} else {
AgenticRun::Vetoed
}
}

/// The readout bar that licenses a commit on each branch.
fn dial(branch: Branch) -> Option<f64> {
match branch {
Branch::Answer | Branch::DefaultVerified => Some(0.7),
Branch::Chat => Some(0.3),
Branch::Agentic => Some(0.2),
Branch::Coding | Branch::Unknown => None,
}
}

fn clears(score: Option<f64>, bar: f64) -> bool {
score.is_some_and(|score| score >= bar)
}

fn affirms(verdict: Option<Tri>) -> bool {
verdict == Some(Tri::Yes)
}

/// The local rungs affirm: the readout at the branch dial, or deliberation.
pub(super) fn locally_verified(branch: Branch, signals: &Signals) -> bool {
let bar = dial(branch).map_or(READOUT, |dial| dial.min(READOUT));
clears(signals.readout, bar) || clears(signals.deliberation, DELIBERATION)
}

/// Commits the local attempt or escalates.
///
/// Coding has no sandboxed checker here, so it never commits.
pub(super) fn decide(
caps: &Capabilities,
signals: &Signals,
confirmed_min_clean_tail: Option<i32>,
) -> Route {
let branch = caps.branch;
let clean = caps.tools.is_none_or(|tools| tools.errors == 0);
let commit = match branch {
Branch::Coding | Branch::Unknown => false,
Branch::Answer => {
affirms(signals.answer_verifier)
|| affirms(signals.evidence_verifier)
|| clears(signals.readout, 0.7)
}
Branch::Chat => {
clean
&& (clears(signals.deliberation, DELIBERATION)
|| clears(signals.readout, 0.3)
|| (affirms(signals.cloud_judge) && affirms(signals.evidence_confirm)))
}
Branch::Agentic => {
locally_verified(branch, signals)
&& match agentic_run(caps, confirmed_min_clean_tail) {
AgenticRun::Clean | AgenticRun::ShortRecovered => true,
AgenticRun::ConfirmedRecovery => affirms(signals.cloud_judge),
AgenticRun::Vetoed => false,
}
}
Branch::DefaultVerified => clean && locally_verified(branch, signals),
};
if commit { Route::Local } else { Route::Cloud }
}
84 changes: 84 additions & 0 deletions crates/libsy/src/algorithms/vgr/readout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! The cheap readout: the probability a verifier put on "yes" at its first token.
//!
//! Normalized responses carry no logprobs, so this reads the preserved Chat
//! body. Anything that cannot be scored is `None`, which never commits.

use serde_json::{Value, json};
use switchyard_protocol::{AggLlmResponse, FormatId, Request, WireFormat};

pub(super) const MAX_OUTPUT_TOKENS: u64 = 4;

/// Asks for a direct verdict with its alternatives; reasoning stays request-local.
pub(super) fn request_logprobs(request: &mut Request) {
request.llm_request.reasoning.effort = Some("none".to_string());
let extensions = &mut request.llm_request.extensions.fields;
extensions.insert("logprobs".to_string(), json!(true));
extensions.insert("top_logprobs".to_string(), json!(8));
}

pub(super) fn p_yes(response: &AggLlmResponse) -> Option<f64> {
let alternatives = response
.preservation
.responses
.get(&FormatId::from(WireFormat::OpenAiChat))?
.pointer("/choices/0/logprobs/content/0/top_logprobs")?
.as_array()?;
let (mut yes, mut no) = (None, None);
for entry in alternatives {
let Some(probability) = entry.get("logprob").and_then(Value::as_f64) else {
continue;
};
let token = entry.get("token")?.as_str()?;
let slot = match token
.trim()
.trim_matches(['"', '\'', '.', ','])
.to_lowercase()
.as_str()
{
"yes" | "y" | "true" => &mut yes,
"no" | "n" | "false" => &mut no,
_ => continue,
};
*slot.get_or_insert(0.0) += probability.exp();
}
match (yes, no) {
(Some(yes), Some(no)) if yes + no > 0.0 => Some(yes / (yes + no)),
(Some(_), None) => Some(1.0),
(None, Some(_)) => Some(0.0),
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::*;

fn scored(alternatives: Value) -> Option<f64> {
let mut response = AggLlmResponse::default();
response.preservation.responses.insert(
FormatId::from(WireFormat::OpenAiChat),
json!({"choices": [{"logprobs": {"content": [{"top_logprobs": alternatives}]}}]}),
);
p_yes(&response)
}

#[test]
fn verdict_mass_is_scored_against_the_pair() {
let half = 0.5_f64.ln();
let score = scored(json!([
{"token": " Yes", "logprob": half},
{"token": "no", "logprob": half},
{"token": ".", "logprob": -9.0}
]));
assert!(score.is_some_and(|score| (score - 0.5).abs() < 1e-9));
assert_eq!(
scored(json!([{"token": "yes", "logprob": -3.0}])),
Some(1.0)
);
assert_eq!(scored(json!([{"token": "maybe", "logprob": 0.0}])), None);
assert_eq!(p_yes(&AggLlmResponse::default()), None);
}
}
Loading
Loading