#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelTrustTier {
Guided,
Autonomous,
}
impl ModelTrustTier {
pub fn as_str(self) -> &'static str {
match self {
ModelTrustTier::Guided => "guided",
ModelTrustTier::Autonomous => "autonomous",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TrustTierSetting {
#[default]
Auto,
Guided,
Autonomous,
}
impl TrustTierSetting {
pub fn parse(value: &str) -> Self {
match value.trim().to_ascii_lowercase().as_str() {
"guided" => Self::Guided,
"autonomous" => Self::Autonomous,
_ => Self::Auto,
}
}
}
const AUTONOMOUS_FAMILY_MARKERS: &[&str] = &[
"claude-",
"gpt-4",
"gpt-5",
"gemini-2.5",
"gemini-3",
"grok-3",
"grok-4",
];
pub fn classify_model_trust(model: &str, setting: TrustTierSetting) -> ModelTrustTier {
match setting {
TrustTierSetting::Guided => return ModelTrustTier::Guided,
TrustTierSetting::Autonomous => return ModelTrustTier::Autonomous,
TrustTierSetting::Auto => {}
}
let id = model.to_ascii_lowercase();
if AUTONOMOUS_FAMILY_MARKERS.iter().any(|f| id.contains(f)) {
return ModelTrustTier::Autonomous;
}
let o_series = id.split(['/', ':', '.']).any(|seg| {
["o1", "o3", "o4"].iter().any(|family| {
seg.strip_prefix(family)
.is_some_and(|rest| rest.is_empty() || rest.starts_with('-'))
})
});
if o_series {
return ModelTrustTier::Autonomous;
}
ModelTrustTier::Guided
}
impl crate::agent::Agent {
pub(crate) fn trust_tier_for_model(&self, model: &str) -> ModelTrustTier {
classify_model_trust(
model,
TrustTierSetting::parse(&self.policy_config.trust_tier),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn auto(model: &str) -> ModelTrustTier {
classify_model_trust(model, TrustTierSetting::Auto)
}
#[test]
fn frontier_models_classify_autonomous() {
for model in [
"claude-fable-5",
"claude-opus-4-8",
"claude-sonnet-4-6",
"anthropic/claude-haiku-4-5-20251001",
"gpt-5",
"openai/gpt-4.1",
"gpt-4o",
"o3",
"o4-mini",
"openrouter/openai/o3",
"gemini-2.5-pro",
"gemini-3-pro-preview",
"grok-4",
"xai/grok-3-mini",
] {
assert_eq!(
auto(model),
ModelTrustTier::Autonomous,
"expected {model} to classify Autonomous"
);
}
}
#[test]
fn small_or_local_models_classify_guided() {
for model in [
"gemma-3-27b-it",
"google/gemma-4-12b",
"llama-3.1-8b-instruct",
"qwen2.5-7b-instruct",
"mistral-small-latest",
"phi-4",
"kimi-k2.5",
"moonshotai/kimi-k2",
"my-local-gguf-model",
"",
] {
assert_eq!(
auto(model),
ModelTrustTier::Guided,
"expected {model} to classify Guided"
);
}
}
#[test]
fn provider_prefixes_and_casing_are_ignored() {
assert_eq!(
auto("US.Anthropic.Claude-Opus-4-8-v1:0"),
ModelTrustTier::Autonomous
);
assert_eq!(auto("OpenAI/GPT-5-Codex"), ModelTrustTier::Autonomous);
}
#[test]
fn o_series_requires_segment_boundary() {
assert_eq!(auto("yi-large-o3000"), ModelTrustTier::Guided);
assert_eq!(auto("llava-onevision-o4b"), ModelTrustTier::Guided);
}
#[test]
fn config_override_wins_over_heuristic() {
assert_eq!(
classify_model_trust("claude-opus-4-8", TrustTierSetting::Guided),
ModelTrustTier::Guided
);
assert_eq!(
classify_model_trust("gemma-3-27b-it", TrustTierSetting::Autonomous),
ModelTrustTier::Autonomous
);
}
#[test]
fn setting_parses_config_strings() {
assert_eq!(TrustTierSetting::parse("auto"), TrustTierSetting::Auto);
assert_eq!(TrustTierSetting::parse("Guided"), TrustTierSetting::Guided);
assert_eq!(
TrustTierSetting::parse(" AUTONOMOUS "),
TrustTierSetting::Autonomous
);
assert_eq!(
TrustTierSetting::parse("garbage"),
TrustTierSetting::Auto,
"unknown values fall back to Auto"
);
}
}