use atomr_agents_agent::{Budget, SpendLedger};
use crate::state::HarnessState;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Termination {
Continue,
Done(&'static str),
}
pub trait TerminationStrategy: Send + Sync + 'static {
fn should_terminate(&self, state: &HarnessState) -> Termination;
}
impl TerminationStrategy for Box<dyn TerminationStrategy> {
fn should_terminate(&self, state: &HarnessState) -> Termination {
(**self).should_terminate(state)
}
}
pub struct IterationCapTermination {
pub cap: u64,
}
impl TerminationStrategy for IterationCapTermination {
fn should_terminate(&self, state: &HarnessState) -> Termination {
if state.iteration >= self.cap {
Termination::Done("iteration_cap")
} else {
Termination::Continue
}
}
}
pub struct BudgetTermination {
ledger: SpendLedger,
budget: Budget,
}
impl BudgetTermination {
pub fn new(ledger: SpendLedger, budget: Budget) -> Self {
Self { ledger, budget }
}
}
impl TerminationStrategy for BudgetTermination {
fn should_terminate(&self, _state: &HarnessState) -> Termination {
match self.ledger.check(&self.budget) {
Ok(()) => Termination::Continue,
Err(_) => Termination::Done("budget_exceeded"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use atomr_agents_agent::{BudgetScope, Cap, DecisionKey, Spend};
#[test]
fn budget_termination_stops_on_overspend() {
let ledger = SpendLedger::new();
let budget = Budget {
cap: Cap::Money(10_000),
scope: BudgetScope::Global,
};
let term = BudgetTermination::new(ledger.clone(), budget);
let state = HarnessState::new(atomr_agents_core::TokenBudget::new(1000));
assert_eq!(term.should_terminate(&state), Termination::Continue);
ledger.record(&Spend {
micro_usd: 12_000,
tokens: 10,
decision_key: Some(DecisionKey::new("d", "s", "1")),
});
assert_eq!(
term.should_terminate(&state),
Termination::Done("budget_exceeded")
);
}
}