use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use once_cell::sync::Lazy;
use crate::events::{HarnessEvalSnapshot, LlmPayloadInvalidMetric, PolicyMetricsData};
pub(in crate::agent) struct PolicyRuntimeMetrics {
pub(in crate::agent) tool_exposure_samples: AtomicU64,
pub(in crate::agent) tool_exposure_before_sum: AtomicU64,
pub(in crate::agent) tool_exposure_after_sum: AtomicU64,
pub(in crate::agent) tool_schema_contract_rejections_total: AtomicU64,
pub(in crate::agent) ambiguity_detected_total: AtomicU64,
pub(in crate::agent) uncertainty_clarify_total: AtomicU64,
pub(in crate::agent) context_refresh_total: AtomicU64,
pub(in crate::agent) escalation_total: AtomicU64,
pub(in crate::agent) fallback_expansion_total: AtomicU64,
pub(in crate::agent) response_direct_return_total: AtomicU64,
pub(in crate::agent) response_fallthrough_total: AtomicU64,
pub(in crate::agent) orchestration_route_clarification_required_total: AtomicU64,
pub(in crate::agent) orchestration_route_tools_required_total: AtomicU64,
pub(in crate::agent) orchestration_route_short_correction_direct_reply_total: AtomicU64,
pub(in crate::agent) orchestration_route_acknowledgment_direct_reply_total: AtomicU64,
pub(in crate::agent) orchestration_route_default_continue_total: AtomicU64,
pub(in crate::agent) context_bleed_prevented_total: AtomicU64,
pub(in crate::agent) context_mismatch_preflight_drop_total: AtomicU64,
pub(in crate::agent) followup_mode_overrides_total: AtomicU64,
pub(in crate::agent) cross_scope_blocked_total: AtomicU64,
pub(in crate::agent) route_drift_alert_total: AtomicU64,
pub(in crate::agent) route_drift_failsafe_activation_total: AtomicU64,
pub(in crate::agent) route_failsafe_active_turn_total: AtomicU64,
pub(in crate::agent) tokens_failed_tasks_total: AtomicU64,
pub(in crate::agent) est_input_token_samples: AtomicU64,
pub(in crate::agent) est_input_tokens_total: AtomicU64,
pub(in crate::agent) est_msg_tokens_total: AtomicU64,
pub(in crate::agent) est_tool_tokens_total: AtomicU64,
pub(in crate::agent) est_tool_tokens_high_share_total: AtomicU64,
pub(in crate::agent) est_tool_tokens_high_abs_total: AtomicU64,
pub(in crate::agent) no_progress_iterations_total: AtomicU64,
pub(in crate::agent) deferred_no_tool_forced_required_total: AtomicU64,
pub(in crate::agent) deferred_no_tool_deferral_detected_total: AtomicU64,
pub(in crate::agent) deferred_no_tool_model_switch_total: AtomicU64,
pub(in crate::agent) deferred_no_tool_error_marker_total: AtomicU64,
pub(in crate::agent) llm_payload_invalid_total: AtomicU64,
pub(in crate::agent) harness_eval_tasks_total: AtomicU64,
pub(in crate::agent) harness_eval_overall_milli_sum: AtomicU64,
}
impl PolicyRuntimeMetrics {
const fn new() -> Self {
Self {
tool_exposure_samples: AtomicU64::new(0),
tool_exposure_before_sum: AtomicU64::new(0),
tool_exposure_after_sum: AtomicU64::new(0),
tool_schema_contract_rejections_total: AtomicU64::new(0),
ambiguity_detected_total: AtomicU64::new(0),
uncertainty_clarify_total: AtomicU64::new(0),
context_refresh_total: AtomicU64::new(0),
escalation_total: AtomicU64::new(0),
fallback_expansion_total: AtomicU64::new(0),
response_direct_return_total: AtomicU64::new(0),
response_fallthrough_total: AtomicU64::new(0),
orchestration_route_clarification_required_total: AtomicU64::new(0),
orchestration_route_tools_required_total: AtomicU64::new(0),
orchestration_route_short_correction_direct_reply_total: AtomicU64::new(0),
orchestration_route_acknowledgment_direct_reply_total: AtomicU64::new(0),
orchestration_route_default_continue_total: AtomicU64::new(0),
context_bleed_prevented_total: AtomicU64::new(0),
context_mismatch_preflight_drop_total: AtomicU64::new(0),
followup_mode_overrides_total: AtomicU64::new(0),
cross_scope_blocked_total: AtomicU64::new(0),
route_drift_alert_total: AtomicU64::new(0),
route_drift_failsafe_activation_total: AtomicU64::new(0),
route_failsafe_active_turn_total: AtomicU64::new(0),
tokens_failed_tasks_total: AtomicU64::new(0),
est_input_token_samples: AtomicU64::new(0),
est_input_tokens_total: AtomicU64::new(0),
est_msg_tokens_total: AtomicU64::new(0),
est_tool_tokens_total: AtomicU64::new(0),
est_tool_tokens_high_share_total: AtomicU64::new(0),
est_tool_tokens_high_abs_total: AtomicU64::new(0),
no_progress_iterations_total: AtomicU64::new(0),
deferred_no_tool_forced_required_total: AtomicU64::new(0),
deferred_no_tool_deferral_detected_total: AtomicU64::new(0),
deferred_no_tool_model_switch_total: AtomicU64::new(0),
deferred_no_tool_error_marker_total: AtomicU64::new(0),
llm_payload_invalid_total: AtomicU64::new(0),
harness_eval_tasks_total: AtomicU64::new(0),
harness_eval_overall_milli_sum: AtomicU64::new(0),
}
}
}
pub(in crate::agent) static POLICY_METRICS: Lazy<PolicyRuntimeMetrics> =
Lazy::new(PolicyRuntimeMetrics::new);
const MAX_LLM_PAYLOAD_INVALID_METRIC_KEYS: usize = 512;
const LLM_PAYLOAD_INVALID_OVERFLOW_PROVIDER: &str = "__other__";
const LLM_PAYLOAD_INVALID_OVERFLOW_MODEL: &str = "__other__";
const LLM_PAYLOAD_INVALID_OVERFLOW_REASON: &str = "__other__";
type PayloadInvalidMetricKey = (String, String, String);
type PayloadInvalidMetricMap = HashMap<PayloadInvalidMetricKey, u64>;
static LLM_PAYLOAD_INVALID_BREAKDOWN: Lazy<std::sync::Mutex<PayloadInvalidMetricMap>> =
Lazy::new(|| std::sync::Mutex::new(HashMap::new()));
pub(in crate::agent) fn provider_kind_metric_label(
kind: crate::config::ProviderKind,
) -> &'static str {
match kind {
crate::config::ProviderKind::OpenaiCompatible => "openai_compatible",
crate::config::ProviderKind::XaiNative => "xai_native",
crate::config::ProviderKind::GoogleGenai => "google_genai",
crate::config::ProviderKind::Anthropic => "anthropic",
}
}
pub(in crate::agent) fn record_llm_payload_invalid_metric(
provider: &str,
model: &str,
reason: &str,
) {
POLICY_METRICS
.llm_payload_invalid_total
.fetch_add(1, Ordering::Relaxed);
let Ok(mut breakdown) = LLM_PAYLOAD_INVALID_BREAKDOWN.lock() else {
return;
};
let key = (provider.to_string(), model.to_string(), reason.to_string());
if let Some(count) = breakdown.get_mut(&key) {
*count = count.saturating_add(1);
return;
}
if breakdown.len() >= MAX_LLM_PAYLOAD_INVALID_METRIC_KEYS {
let overflow_key = (
LLM_PAYLOAD_INVALID_OVERFLOW_PROVIDER.to_string(),
LLM_PAYLOAD_INVALID_OVERFLOW_MODEL.to_string(),
LLM_PAYLOAD_INVALID_OVERFLOW_REASON.to_string(),
);
let count = breakdown.entry(overflow_key).or_insert(0);
*count = count.saturating_add(1);
return;
}
breakdown.insert(key, 1);
}
fn llm_payload_invalid_breakdown_snapshot() -> Vec<LlmPayloadInvalidMetric> {
let Ok(breakdown) = LLM_PAYLOAD_INVALID_BREAKDOWN.lock() else {
return Vec::new();
};
let mut rows: Vec<LlmPayloadInvalidMetric> = breakdown
.iter()
.map(
|((provider, model, reason), count)| LlmPayloadInvalidMetric {
provider: provider.clone(),
model: model.clone(),
reason: reason.clone(),
count: *count,
},
)
.collect();
rows.sort_by(|a, b| {
b.count
.cmp(&a.count)
.then_with(|| a.provider.cmp(&b.provider))
.then_with(|| a.model.cmp(&b.model))
.then_with(|| a.reason.cmp(&b.reason))
});
rows
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
struct RouteDriftSample {
reason: RouteDriftReason,
action: RouteDriftAction,
reply_len: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
enum RouteDriftReason {
ClarificationRequired,
ToolsRequired,
ShortCorrectionDirectReply,
AcknowledgmentDirectReply,
DefaultContinue,
Unknown,
}
#[allow(dead_code)]
impl RouteDriftReason {
fn from_str(reason: &str) -> Self {
match reason {
"clarification_required" => Self::ClarificationRequired,
"tools_required" => Self::ToolsRequired,
"short_correction_direct_reply" => Self::ShortCorrectionDirectReply,
"acknowledgment_direct_reply" => Self::AcknowledgmentDirectReply,
"default_continue" => Self::DefaultContinue,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
enum RouteDriftAction {
Return,
Continue,
Unknown,
}
#[allow(dead_code)]
impl RouteDriftAction {
fn from_str(action: &str) -> Self {
match action {
"return" => Self::Return,
"continue" => Self::Continue,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Default)]
#[allow(dead_code)]
struct RouteDriftSessionState {
samples: VecDeque<RouteDriftSample>,
last_seen_epoch_secs: u64,
last_alert_epoch_secs: u64,
consecutive_anomaly_windows: u32,
failsafe_until_epoch_secs: u64,
}
#[derive(Debug, Default)]
#[allow(dead_code)]
struct RouteDriftMonitor {
sessions: HashMap<String, RouteDriftSessionState>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub(in crate::agent) struct RouteDriftSignal {
pub summary: String,
pub failsafe_activated: bool,
}
#[allow(dead_code)]
const ROUTE_DRIFT_WINDOW_SIZE: usize = 24;
#[allow(dead_code)]
const ROUTE_DRIFT_MIN_WINDOW: usize = 12;
#[allow(dead_code)]
const ROUTE_DRIFT_ALERT_COOLDOWN_SECS: u64 = 300;
#[allow(dead_code)]
const ROUTE_DRIFT_FAILSAFE_DURATION_SECS: u64 = 900;
#[allow(dead_code)]
const ROUTE_DRIFT_FAILSAFE_STREAK: u32 = 2;
const ROUTE_DRIFT_MAX_TRACKED_SESSIONS: usize = 256;
const ROUTE_DRIFT_STALE_SESSION_SECS: u64 = 7200;
#[allow(dead_code)]
static ROUTE_DRIFT_MONITOR: Lazy<std::sync::Mutex<RouteDriftMonitor>> =
Lazy::new(|| std::sync::Mutex::new(RouteDriftMonitor::default()));
#[allow(dead_code)]
fn now_epoch_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.unwrap_or(0)
}
#[allow(dead_code)]
fn prune_route_drift_sessions(monitor: &mut RouteDriftMonitor, now: u64) {
monitor.sessions.retain(|_, state| {
now.saturating_sub(state.last_seen_epoch_secs) <= ROUTE_DRIFT_STALE_SESSION_SECS
|| state.failsafe_until_epoch_secs > now
});
if monitor.sessions.len() <= ROUTE_DRIFT_MAX_TRACKED_SESSIONS {
return;
}
let mut oldest: Vec<(String, u64)> = monitor
.sessions
.iter()
.map(|(session_id, state)| (session_id.clone(), state.last_seen_epoch_secs))
.collect();
oldest.sort_by_key(|(_, ts)| *ts);
let remove_count = monitor
.sessions
.len()
.saturating_sub(ROUTE_DRIFT_MAX_TRACKED_SESSIONS);
for (session_id, _) in oldest.into_iter().take(remove_count) {
monitor.sessions.remove(&session_id);
}
}
#[allow(dead_code)]
pub(in crate::agent) fn observe_route_reason_for_drift(
session_id: &str,
route_reason: &str,
route_action: &str,
route_reply_len: Option<usize>,
) -> Option<RouteDriftSignal> {
let now = now_epoch_secs();
let Ok(mut monitor) = ROUTE_DRIFT_MONITOR.lock() else {
return None;
};
let state = monitor
.sessions
.entry(session_id.to_string())
.or_insert_with(RouteDriftSessionState::default);
state.last_seen_epoch_secs = now;
state.samples.push_back(RouteDriftSample {
reason: RouteDriftReason::from_str(route_reason),
action: RouteDriftAction::from_str(route_action),
reply_len: route_reply_len,
});
while state.samples.len() > ROUTE_DRIFT_WINDOW_SIZE {
state.samples.pop_front();
}
let sample_count = state.samples.len();
if sample_count < ROUTE_DRIFT_MIN_WINDOW {
prune_route_drift_sessions(&mut monitor, now);
return None;
}
let tools_required = state
.samples
.iter()
.filter(|sample| sample.reason == RouteDriftReason::ToolsRequired)
.count();
let default_continue = state
.samples
.iter()
.filter(|sample| sample.reason == RouteDriftReason::DefaultContinue)
.count();
let clarification_required = state
.samples
.iter()
.filter(|sample| sample.reason == RouteDriftReason::ClarificationRequired)
.count();
let empty_direct_replies = state
.samples
.iter()
.filter(|sample| sample.action == RouteDriftAction::Return && sample.reply_len == Some(0))
.count();
let total = sample_count as f64;
let tools_rate = tools_required as f64 / total;
let default_rate = default_continue as f64 / total;
let clarification_rate = clarification_required as f64 / total;
let mut anomaly_reasons: Vec<String> = Vec::new();
if empty_direct_replies > 0 {
anomaly_reasons.push(format!("empty_direct_replies={}", empty_direct_replies));
}
if tools_rate <= 0.05 && default_rate >= 0.85 {
anomaly_reasons.push(format!(
"tools_required_rate={:.0}% default_continue_rate={:.0}%",
tools_rate * 100.0,
default_rate * 100.0
));
}
if clarification_rate >= 0.75 {
anomaly_reasons.push(format!(
"clarification_required_rate={:.0}%",
clarification_rate * 100.0
));
}
let mut signal: Option<RouteDriftSignal> = None;
if anomaly_reasons.is_empty() {
state.consecutive_anomaly_windows = 0;
} else {
state.consecutive_anomaly_windows = state.consecutive_anomaly_windows.saturating_add(1);
let cooldown_elapsed =
now.saturating_sub(state.last_alert_epoch_secs) >= ROUTE_DRIFT_ALERT_COOLDOWN_SECS;
let mut failsafe_activated = false;
if state.consecutive_anomaly_windows >= ROUTE_DRIFT_FAILSAFE_STREAK
&& state.failsafe_until_epoch_secs <= now
{
state.failsafe_until_epoch_secs = now + ROUTE_DRIFT_FAILSAFE_DURATION_SECS;
POLICY_METRICS
.route_drift_failsafe_activation_total
.fetch_add(1, Ordering::Relaxed);
failsafe_activated = true;
}
if cooldown_elapsed || failsafe_activated {
state.last_alert_epoch_secs = now;
POLICY_METRICS
.route_drift_alert_total
.fetch_add(1, Ordering::Relaxed);
signal = Some(RouteDriftSignal {
summary: format!(
"route drift anomaly: {} (window={} turns)",
anomaly_reasons.join(", "),
sample_count
),
failsafe_activated,
});
}
}
prune_route_drift_sessions(&mut monitor, now);
signal
}
pub(in crate::agent) fn route_failsafe_active_for_session(session_id: &str) -> bool {
let now = now_epoch_secs();
let Ok(mut monitor) = ROUTE_DRIFT_MONITOR.lock() else {
return false;
};
let active = monitor
.sessions
.get(session_id)
.is_some_and(|state| state.failsafe_until_epoch_secs > now);
if active {
POLICY_METRICS
.route_failsafe_active_turn_total
.fetch_add(1, Ordering::Relaxed);
}
prune_route_drift_sessions(&mut monitor, now);
active
}
#[cfg(test)]
pub(crate) fn set_route_failsafe_for_session_for_test(session_id: &str, active: bool) {
let now = now_epoch_secs();
let Ok(mut monitor) = ROUTE_DRIFT_MONITOR.lock() else {
return;
};
if active {
let state = monitor
.sessions
.entry(session_id.to_string())
.or_insert_with(RouteDriftSessionState::default);
state.last_seen_epoch_secs = now;
state.failsafe_until_epoch_secs = now + ROUTE_DRIFT_FAILSAFE_DURATION_SECS;
} else {
monitor.sessions.remove(session_id);
}
}
struct PolicyRuntimeTunables {
initialized: AtomicBool,
uncertainty_threshold_bp: AtomicU64,
}
impl PolicyRuntimeTunables {
const fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
uncertainty_threshold_bp: AtomicU64::new(5500),
}
}
}
static POLICY_TUNABLES: Lazy<PolicyRuntimeTunables> = Lazy::new(PolicyRuntimeTunables::new);
pub fn policy_metrics_snapshot() -> PolicyMetricsData {
PolicyMetricsData {
tool_exposure_samples: POLICY_METRICS.tool_exposure_samples.load(Ordering::Relaxed),
tool_exposure_before_sum: POLICY_METRICS
.tool_exposure_before_sum
.load(Ordering::Relaxed),
tool_exposure_after_sum: POLICY_METRICS
.tool_exposure_after_sum
.load(Ordering::Relaxed),
tool_schema_contract_rejections_total: POLICY_METRICS
.tool_schema_contract_rejections_total
.load(Ordering::Relaxed),
ambiguity_detected_total: POLICY_METRICS
.ambiguity_detected_total
.load(Ordering::Relaxed),
uncertainty_clarify_total: POLICY_METRICS
.uncertainty_clarify_total
.load(Ordering::Relaxed),
context_refresh_total: POLICY_METRICS.context_refresh_total.load(Ordering::Relaxed),
escalation_total: POLICY_METRICS.escalation_total.load(Ordering::Relaxed),
fallback_expansion_total: POLICY_METRICS
.fallback_expansion_total
.load(Ordering::Relaxed),
response_direct_return_total: POLICY_METRICS
.response_direct_return_total
.load(Ordering::Relaxed),
response_fallthrough_total: POLICY_METRICS
.response_fallthrough_total
.load(Ordering::Relaxed),
orchestration_route_clarification_required_total: POLICY_METRICS
.orchestration_route_clarification_required_total
.load(Ordering::Relaxed),
orchestration_route_tools_required_total: POLICY_METRICS
.orchestration_route_tools_required_total
.load(Ordering::Relaxed),
orchestration_route_short_correction_direct_reply_total: POLICY_METRICS
.orchestration_route_short_correction_direct_reply_total
.load(Ordering::Relaxed),
orchestration_route_acknowledgment_direct_reply_total: POLICY_METRICS
.orchestration_route_acknowledgment_direct_reply_total
.load(Ordering::Relaxed),
orchestration_route_default_continue_total: POLICY_METRICS
.orchestration_route_default_continue_total
.load(Ordering::Relaxed),
context_bleed_prevented_total: POLICY_METRICS
.context_bleed_prevented_total
.load(Ordering::Relaxed),
context_mismatch_preflight_drop_total: POLICY_METRICS
.context_mismatch_preflight_drop_total
.load(Ordering::Relaxed),
followup_mode_overrides_total: POLICY_METRICS
.followup_mode_overrides_total
.load(Ordering::Relaxed),
cross_scope_blocked_total: POLICY_METRICS
.cross_scope_blocked_total
.load(Ordering::Relaxed),
route_drift_alert_total: POLICY_METRICS
.route_drift_alert_total
.load(Ordering::Relaxed),
route_drift_failsafe_activation_total: POLICY_METRICS
.route_drift_failsafe_activation_total
.load(Ordering::Relaxed),
route_failsafe_active_turn_total: POLICY_METRICS
.route_failsafe_active_turn_total
.load(Ordering::Relaxed),
tokens_failed_tasks_total: POLICY_METRICS
.tokens_failed_tasks_total
.load(Ordering::Relaxed),
est_input_token_samples: POLICY_METRICS
.est_input_token_samples
.load(Ordering::Relaxed),
est_input_tokens_total: POLICY_METRICS
.est_input_tokens_total
.load(Ordering::Relaxed),
est_msg_tokens_total: POLICY_METRICS.est_msg_tokens_total.load(Ordering::Relaxed),
est_tool_tokens_total: POLICY_METRICS.est_tool_tokens_total.load(Ordering::Relaxed),
est_tool_tokens_high_share_total: POLICY_METRICS
.est_tool_tokens_high_share_total
.load(Ordering::Relaxed),
est_tool_tokens_high_abs_total: POLICY_METRICS
.est_tool_tokens_high_abs_total
.load(Ordering::Relaxed),
no_progress_iterations_total: POLICY_METRICS
.no_progress_iterations_total
.load(Ordering::Relaxed),
deferred_no_tool_forced_required_total: POLICY_METRICS
.deferred_no_tool_forced_required_total
.load(Ordering::Relaxed),
deferred_no_tool_deferral_detected_total: POLICY_METRICS
.deferred_no_tool_deferral_detected_total
.load(Ordering::Relaxed),
deferred_no_tool_model_switch_total: POLICY_METRICS
.deferred_no_tool_model_switch_total
.load(Ordering::Relaxed),
deferred_no_tool_error_marker_total: POLICY_METRICS
.deferred_no_tool_error_marker_total
.load(Ordering::Relaxed),
llm_payload_invalid_total: POLICY_METRICS
.llm_payload_invalid_total
.load(Ordering::Relaxed),
llm_payload_invalid_breakdown: llm_payload_invalid_breakdown_snapshot(),
harness_eval_tasks_total: POLICY_METRICS
.harness_eval_tasks_total
.load(Ordering::Relaxed),
harness_eval_overall_avg: {
let tasks = POLICY_METRICS
.harness_eval_tasks_total
.load(Ordering::Relaxed);
if tasks == 0 {
0.0
} else {
POLICY_METRICS
.harness_eval_overall_milli_sum
.load(Ordering::Relaxed) as f64
/ tasks as f64
/ 1000.0
}
},
}
}
pub(super) fn record_harness_eval_task(snapshot: &HarnessEvalSnapshot) {
POLICY_METRICS
.harness_eval_tasks_total
.fetch_add(1, Ordering::Relaxed);
let milli = (f64::from(snapshot.scores.overall) * 1000.0).round() as u64;
POLICY_METRICS
.harness_eval_overall_milli_sum
.fetch_add(milli, Ordering::Relaxed);
}
pub(super) fn record_failed_task_tokens(tokens_used: u64) {
POLICY_METRICS
.tokens_failed_tasks_total
.fetch_add(tokens_used, Ordering::Relaxed);
}
pub fn init_policy_tunables_once(base_uncertainty_threshold: f32) {
if POLICY_TUNABLES
.initialized
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
let bp = (base_uncertainty_threshold.clamp(0.0, 1.0) * 10_000.0) as u64;
POLICY_TUNABLES
.uncertainty_threshold_bp
.store(bp, Ordering::SeqCst);
}
}
pub(in crate::agent) fn current_uncertainty_threshold(default_threshold: f32) -> f32 {
if POLICY_TUNABLES.initialized.load(Ordering::SeqCst) {
let bp = POLICY_TUNABLES
.uncertainty_threshold_bp
.load(Ordering::SeqCst);
(bp as f32 / 10_000.0).clamp(0.0, 1.0)
} else {
default_threshold
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PolicyAutotuneSnapshot {
pub uncertainty_threshold: f32,
}
pub fn policy_autotune_snapshot(default_threshold: f32) -> PolicyAutotuneSnapshot {
PolicyAutotuneSnapshot {
uncertainty_threshold: current_uncertainty_threshold(default_threshold),
}
}
pub fn apply_bounded_autotune_from_failure_ratio(
failure_ratio: f64,
enforce: bool,
) -> Option<(f32, f32)> {
if !enforce {
return None;
}
let old_bp = POLICY_TUNABLES
.uncertainty_threshold_bp
.load(Ordering::SeqCst);
let old = old_bp as f32 / 10_000.0;
let mut next = old;
if failure_ratio >= 0.25 {
next = (next - 0.02).max(0.45);
} else if failure_ratio <= 0.05 {
next = (next + 0.01).min(0.75);
}
if (next - old).abs() < f32::EPSILON {
return None;
}
let next_bp = (next * 10_000.0) as u64;
POLICY_TUNABLES
.uncertainty_threshold_bp
.store(next_bp, Ordering::SeqCst);
Some((old, next))
}