Skip to main content

switchyard_libsy/algorithms/vgr/
config.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::time::Duration;
5
6use switchyard_protocol::ModelId;
7
8use super::safety::{BreakerConfig, KillSwitch};
9use crate::{LibsyError, Result};
10
11/// Required attestation for live local commits.
12pub const ACTIVE_APPROVAL: &str = "prospective-validation-and-canary-approved";
13
14/// Authority granted to VGR decisions.
15#[derive(Clone, Debug, Default, Eq, PartialEq)]
16pub enum ServingMode {
17    /// Always serves cloud without producing an attempt.
18    #[default]
19    Off,
20    /// Serves decisions, for isolated measurement.
21    Evaluate,
22    /// Decides but always serves cloud.
23    Shadow,
24    /// Serves decisions after operator approval.
25    Active {
26        /// Operator approval attestation.
27        approval: String,
28    },
29}
30
31/// Targets used by a VGR route.
32#[derive(Clone, Debug)]
33pub struct Targets {
34    /// Tier that produces the candidate attempt.
35    pub local: ModelId,
36    /// Tier used when the candidate is not licensed.
37    pub cloud: ModelId,
38    /// Local verifier, defaulting to `local`.
39    pub judge: Option<ModelId>,
40    /// Capable-tier verifier; unset removes the cloud confirmation rungs.
41    pub cloud_judge: Option<ModelId>,
42}
43
44/// VGR runtime configuration.
45#[derive(Clone, Debug)]
46pub struct VgrConfig {
47    /// Completion and verifier targets.
48    pub targets: Targets,
49    /// Authority granted to routing decisions.
50    pub mode: ServingMode,
51    /// Budget for one turn's attempt and verification.
52    pub deadline: Duration,
53    /// Optional live stop controlled by the operator.
54    pub kill_switch: Option<KillSwitch>,
55    /// Local endpoint breaker tuning.
56    pub breaker: BreakerConfig,
57    /// Whether a cheap typing call selects a verification regime.
58    pub task_typing: bool,
59    /// Whether the local tier accepts image content.
60    pub local_supports_images: bool,
61    /// Lets a long agentic run with earlier tool errors commit on a capable-tier
62    /// confirmation once it ends in this many consecutive clean tool results.
63    pub confirmed_recovery_min_clean_tail: Option<u32>,
64}
65
66impl VgrConfig {
67    /// Creates an off-by-default route between local and capable tiers.
68    pub fn new(local: ModelId, cloud: ModelId) -> Self {
69        Self {
70            targets: Targets {
71                local,
72                cloud,
73                judge: None,
74                cloud_judge: None,
75            },
76            mode: ServingMode::Off,
77            deadline: Duration::from_secs(30),
78            kill_switch: None,
79            breaker: BreakerConfig::default(),
80            task_typing: true,
81            local_supports_images: false,
82            confirmed_recovery_min_clean_tail: None,
83        }
84    }
85
86    pub(super) fn validate(&self) -> Result<()> {
87        if self.targets.local == self.targets.cloud {
88            return Err(LibsyError::AlgorithmError {
89                message: "vgr requires distinct local and cloud model IDs".into(),
90            });
91        }
92        if self.deadline.is_zero() || self.breaker.threshold == 0 {
93            return Err(LibsyError::AlgorithmError {
94                message: "vgr deadline and breaker threshold must be non-zero".into(),
95            });
96        }
97        if let ServingMode::Active { approval } = &self.mode
98            && approval != ACTIVE_APPROVAL
99        {
100            return Err(LibsyError::AlgorithmError {
101                message: format!(
102                    "vgr active mode requires approval attestation {ACTIVE_APPROVAL:?}"
103                ),
104            });
105        }
106        Ok(())
107    }
108
109    pub(super) fn judge(&self) -> &ModelId {
110        self.targets.judge.as_ref().unwrap_or(&self.targets.local)
111    }
112
113    pub(super) fn confirmed_min_clean_tail(&self) -> Option<i32> {
114        self.confirmed_recovery_min_clean_tail
115            .map(|tail| i32::try_from(tail.max(1)).unwrap_or(i32::MAX))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn direct_callers_must_use_distinct_local_and_cloud_ids() {
125        assert!(super::super::Vgr::new(VgrConfig::new("same".into(), "same".into())).is_err());
126        assert!(super::super::Vgr::new(VgrConfig::new("local".into(), "cloud".into())).is_ok());
127    }
128}