use aios_protocol::mode::{GatingProfile, OperatingMode};
use serde::{Deserialize, Serialize};
use crate::economic::{EconomicMode, EconomicState, ModelTier};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EconomicGates {
pub economic_mode: EconomicMode,
pub max_tokens_next_turn: Option<u32>,
pub preferred_model: Option<ModelTier>,
pub allow_expensive_tools: bool,
pub allow_replication: bool,
}
impl Default for EconomicGates {
fn default() -> Self {
Self {
economic_mode: EconomicMode::Sovereign,
max_tokens_next_turn: None,
preferred_model: None,
allow_expensive_tools: true,
allow_replication: true,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AutonomicGatingProfile {
pub operational: GatingProfile,
pub economic: EconomicGates,
pub rationale: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationalState {
pub mode: OperatingMode,
pub error_streak: u32,
pub total_errors: u32,
pub total_successes: u32,
pub last_tick_ms: u64,
}
impl Default for OperationalState {
fn default() -> Self {
Self {
mode: OperatingMode::Execute,
error_streak: 0,
total_errors: 0,
total_successes: 0,
last_tick_ms: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CognitiveState {
pub total_tokens_used: u64,
pub tokens_remaining: u64,
pub context_pressure: f32,
pub turns_completed: u32,
}
impl Default for CognitiveState {
fn default() -> Self {
Self {
total_tokens_used: 0,
tokens_remaining: 120_000,
context_pressure: 0.0,
turns_completed: 0,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StrategyState {
pub drift_alerts: u32,
pub decisions_logged: u32,
pub critiques_completed: u32,
pub last_strategy_event_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvalState {
pub inline_eval_count: u32,
pub async_eval_count: u32,
pub aggregate_quality_score: f64,
pub quality_trend: f64,
pub last_eval_ms: u64,
}
impl Default for EvalState {
fn default() -> Self {
Self {
inline_eval_count: 0,
async_eval_count: 0,
aggregate_quality_score: 1.0, quality_trend: 0.0,
last_eval_ms: 0,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HomeostaticState {
pub agent_id: String,
pub operational: OperationalState,
pub cognitive: CognitiveState,
pub economic: EconomicState,
pub strategy: StrategyState,
pub eval: EvalState,
pub last_event_seq: u64,
pub last_event_ms: u64,
}
impl HomeostaticState {
pub fn for_agent(agent_id: impl Into<String>) -> Self {
Self {
agent_id: agent_id.into(),
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn autonomic_gating_profile_default() {
let profile = AutonomicGatingProfile::default();
assert!(profile.operational.allow_side_effects);
assert!(profile.economic.allow_expensive_tools);
assert_eq!(profile.economic.economic_mode, EconomicMode::Sovereign);
assert!(profile.rationale.is_empty());
}
#[test]
fn autonomic_gating_profile_serde_roundtrip() {
let profile = AutonomicGatingProfile {
operational: GatingProfile::default(),
economic: EconomicGates {
economic_mode: EconomicMode::Conserving,
max_tokens_next_turn: Some(4096),
preferred_model: Some(ModelTier::Budget),
allow_expensive_tools: false,
allow_replication: false,
},
rationale: vec!["balance low".into(), "reducing spend".into()],
};
let json = serde_json::to_string(&profile).unwrap();
let back: AutonomicGatingProfile = serde_json::from_str(&json).unwrap();
assert_eq!(back.economic.economic_mode, EconomicMode::Conserving);
assert_eq!(back.economic.max_tokens_next_turn, Some(4096));
assert!(!back.economic.allow_expensive_tools);
assert_eq!(back.rationale.len(), 2);
}
#[test]
fn homeostatic_state_for_agent() {
let state = HomeostaticState::for_agent("agent-1");
assert_eq!(state.agent_id, "agent-1");
assert_eq!(state.operational.mode, OperatingMode::Execute);
assert_eq!(state.economic.mode, EconomicMode::Sovereign);
}
#[test]
fn strategy_state_default_is_zeroed() {
let strategy = StrategyState::default();
assert_eq!(strategy.drift_alerts, 0);
assert_eq!(strategy.decisions_logged, 0);
assert_eq!(strategy.critiques_completed, 0);
assert_eq!(strategy.last_strategy_event_ms, 0);
}
#[test]
fn homeostatic_state_includes_strategy() {
let state = HomeostaticState::for_agent("agent-1");
assert_eq!(state.strategy.drift_alerts, 0);
assert_eq!(state.strategy.decisions_logged, 0);
assert_eq!(state.strategy.critiques_completed, 0);
assert_eq!(state.strategy.last_strategy_event_ms, 0);
}
#[test]
fn strategy_state_serde_roundtrip() {
let strategy = StrategyState {
drift_alerts: 5,
decisions_logged: 12,
critiques_completed: 3,
last_strategy_event_ms: 1_700_000_000_000,
};
let json = serde_json::to_string(&strategy).unwrap();
let back: StrategyState = serde_json::from_str(&json).unwrap();
assert_eq!(back.drift_alerts, 5);
assert_eq!(back.decisions_logged, 12);
assert_eq!(back.critiques_completed, 3);
assert_eq!(back.last_strategy_event_ms, 1_700_000_000_000);
}
#[test]
fn eval_state_default_optimistic() {
let eval = EvalState::default();
assert_eq!(eval.inline_eval_count, 0);
assert_eq!(eval.async_eval_count, 0);
assert!((eval.aggregate_quality_score - 1.0).abs() < f64::EPSILON);
assert!((eval.quality_trend).abs() < f64::EPSILON);
assert_eq!(eval.last_eval_ms, 0);
}
#[test]
fn eval_state_serde_roundtrip() {
let eval = EvalState {
inline_eval_count: 15,
async_eval_count: 3,
aggregate_quality_score: 0.78,
quality_trend: -0.02,
last_eval_ms: 1_700_000_000_000,
};
let json = serde_json::to_string(&eval).unwrap();
let back: EvalState = serde_json::from_str(&json).unwrap();
assert_eq!(back.inline_eval_count, 15);
assert_eq!(back.async_eval_count, 3);
assert!((back.aggregate_quality_score - 0.78).abs() < f64::EPSILON);
assert!((back.quality_trend - (-0.02)).abs() < f64::EPSILON);
}
#[test]
fn homeostatic_state_includes_eval() {
let state = HomeostaticState::for_agent("test");
assert_eq!(state.eval.inline_eval_count, 0);
assert!((state.eval.aggregate_quality_score - 1.0).abs() < f64::EPSILON);
}
}