use rust_decimal::prelude::ToPrimitive;
use crate::budget::BudgetTracker;
use crate::registry::AgentRegistry;
pub struct ProductionPolicyContext<'a> {
registry: &'a AgentRegistry,
budget: &'a BudgetTracker,
agent_key: [u8; 16],
team_id: Option<String>,
proposed_child_risk_tier: Option<aa_core::RiskTier>,
now_secs: u64,
}
impl<'a> ProductionPolicyContext<'a> {
pub fn new(
registry: &'a AgentRegistry,
budget: &'a BudgetTracker,
agent_key: [u8; 16],
team_id: Option<String>,
now_secs: u64,
) -> Self {
Self {
registry,
budget,
agent_key,
team_id,
proposed_child_risk_tier: None,
now_secs,
}
}
}
impl<'a> PolicyContext for ProductionPolicyContext<'a> {
fn agent_depth(&self) -> Option<u32> {
self.registry.get(&self.agent_key).map(|r| r.depth)
}
fn team_active_agents(&self) -> Option<u64> {
let team_id = self.team_id.as_deref()?;
Some(self.registry.team_members(team_id).len() as u64)
}
fn team_budget_remaining(&self) -> Option<f64> {
let team_id = self.team_id.as_deref()?;
let state = self.budget.team_state(team_id)?;
let limit = self.budget.monthly_limit_usd()?;
let spent = state.monthly_spent_usd.unwrap_or(state.spent_usd);
let remaining = (limit - spent).max(rust_decimal::Decimal::ZERO);
remaining.to_f64()
}
fn child_tools(&self) -> Vec<String> {
self.registry
.children_of(&self.agent_key)
.into_iter()
.flat_map(|key| {
self.registry
.get(&key)
.map(|r| r.tool_names.clone())
.unwrap_or_default()
})
.collect()
}
fn agent_risk_tier(&self) -> Option<aa_core::RiskTier> {
let record = self.registry.get(&self.agent_key)?;
aa_core::RiskTier::from_proto_i32(record.risk_tier)
}
fn parent_risk_tier(&self) -> Option<aa_core::RiskTier> {
let record = self.registry.get(&self.agent_key)?;
let parent_key = record.parent_key?;
let parent = self.registry.get(&parent_key)?;
aa_core::RiskTier::from_proto_i32(parent.risk_tier)
}
fn child_risk_tier(&self) -> Option<aa_core::RiskTier> {
self.proposed_child_risk_tier
}
fn agent_age_secs(&self) -> Option<u64> {
let record = self.registry.get(&self.agent_key)?;
let registered_unix = record.registered_at.timestamp() as u64;
Some(self.now_secs.saturating_sub(registered_unix))
}
fn agent_parent_id(&self) -> Option<String> {
self.registry.get(&self.agent_key)?.parent_agent_id.clone()
}
fn agent_team_id(&self) -> Option<String> {
self.team_id.clone()
}
fn agent_children_count(&self) -> Option<u32> {
let record = self.registry.get(&self.agent_key)?;
Some(record.children.len() as u32)
}
}
#[cfg(test)]
#[derive(Default)]
pub struct FakePolicyContext {
pub depth: Option<u32>,
pub team_active: Option<u64>,
pub team_budget: Option<f64>,
pub child_tools: Vec<String>,
pub agent_risk_tier: Option<aa_core::RiskTier>,
pub parent_risk_tier: Option<aa_core::RiskTier>,
pub child_risk_tier: Option<aa_core::RiskTier>,
pub agent_age_secs: Option<u64>,
pub agent_parent_id: Option<String>,
pub agent_team_id: Option<String>,
pub agent_children_count: Option<u32>,
}
#[cfg(test)]
impl PolicyContext for FakePolicyContext {
fn agent_depth(&self) -> Option<u32> {
self.depth
}
fn team_active_agents(&self) -> Option<u64> {
self.team_active
}
fn team_budget_remaining(&self) -> Option<f64> {
self.team_budget
}
fn child_tools(&self) -> Vec<String> {
self.child_tools.clone()
}
fn agent_risk_tier(&self) -> Option<aa_core::RiskTier> {
self.agent_risk_tier
}
fn parent_risk_tier(&self) -> Option<aa_core::RiskTier> {
self.parent_risk_tier
}
fn child_risk_tier(&self) -> Option<aa_core::RiskTier> {
self.child_risk_tier
}
fn agent_age_secs(&self) -> Option<u64> {
self.agent_age_secs
}
fn agent_parent_id(&self) -> Option<String> {
self.agent_parent_id.clone()
}
fn agent_team_id(&self) -> Option<String> {
self.agent_team_id.clone()
}
fn agent_children_count(&self) -> Option<u32> {
self.agent_children_count
}
}
pub trait PolicyContext: Send + Sync {
fn agent_depth(&self) -> Option<u32>;
fn team_active_agents(&self) -> Option<u64>;
fn team_budget_remaining(&self) -> Option<f64>;
fn child_tools(&self) -> Vec<String>;
fn agent_risk_tier(&self) -> Option<aa_core::RiskTier>;
fn parent_risk_tier(&self) -> Option<aa_core::RiskTier>;
fn child_risk_tier(&self) -> Option<aa_core::RiskTier>;
fn agent_age_secs(&self) -> Option<u64>;
fn agent_parent_id(&self) -> Option<String>;
fn agent_team_id(&self) -> Option<String>;
fn agent_children_count(&self) -> Option<u32>;
}