#[derive(Debug, Clone)]
pub enum AgentState {
Planning,
Executing { step: usize },
ErrorRecovery { error: String },
Completed,
Failed { reason: String },
}
pub struct AgentLoop {
state: AgentState,
max_iterations: usize,
current_step: usize,
iteration: usize,
}
impl AgentLoop {
pub fn new(max_iterations: usize) -> Self {
Self {
state: AgentState::Planning,
max_iterations,
current_step: 0,
iteration: 0,
}
}
pub fn next_state(&mut self) -> Option<AgentState> {
if self.iteration >= self.max_iterations {
return Some(AgentState::Failed {
reason: "Max iterations exceeded".to_string(),
});
}
self.iteration += 1;
Some(self.state.clone())
}
pub fn approaching_limit_warning(&self) -> Option<String> {
if self.max_iterations == 0 {
return None;
}
let remaining = self.max_iterations.saturating_sub(self.iteration);
let pct_used = (self.iteration * 100) / self.max_iterations;
if pct_used >= 90 {
Some(format!(
"[SYSTEM] Only {} iteration(s) remaining out of {}. Wrap up your current work and provide a final answer now.",
remaining, self.max_iterations
))
} else if pct_used >= 80 {
Some(format!(
"[SYSTEM] Approaching iteration limit: {} of {} iterations used ({} remaining). Start wrapping up.",
self.iteration, self.max_iterations, remaining
))
} else {
None
}
}
pub fn set_state(&mut self, state: AgentState) {
self.state = state;
}
pub fn increment_step(&mut self) {
self.current_step += 1;
self.state = AgentState::Executing {
step: self.current_step,
};
}
pub fn current_step(&self) -> usize {
self.current_step
}
pub fn current_iteration(&self) -> usize {
self.iteration
}
pub fn current_state_label(&self) -> &'static str {
match self.state {
AgentState::Planning => "planning",
AgentState::Executing { .. } => "executing",
AgentState::ErrorRecovery { .. } => "error_recovery",
AgentState::Completed => "completed",
AgentState::Failed { .. } => "failed",
}
}
pub fn restore_progress(&mut self, step: usize, iteration: usize) {
self.current_step = step;
self.iteration = iteration;
self.state = AgentState::Executing { step };
}
pub fn reset_for_task(&mut self) {
self.state = AgentState::Planning;
self.current_step = 0;
self.iteration = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_loop_new() {
let loop_ctrl = AgentLoop::new(100);
assert_eq!(loop_ctrl.max_iterations, 100);
assert_eq!(loop_ctrl.current_step, 0);
assert_eq!(loop_ctrl.iteration, 0);
}
#[test]
fn test_agent_loop_initial_state_is_planning() {
let mut loop_ctrl = AgentLoop::new(100);
let state = loop_ctrl.next_state();
assert!(matches!(state, Some(AgentState::Planning)));
}
#[test]
fn test_agent_loop_set_state() {
let mut loop_ctrl = AgentLoop::new(100);
loop_ctrl.set_state(AgentState::Executing { step: 0 });
let state = loop_ctrl.next_state();
assert!(matches!(state, Some(AgentState::Executing { step: 0 })));
}
#[test]
fn test_agent_loop_increment_step() {
let mut loop_ctrl = AgentLoop::new(100);
assert_eq!(loop_ctrl.current_step(), 0);
loop_ctrl.increment_step();
assert_eq!(loop_ctrl.current_step(), 1);
loop_ctrl.increment_step();
assert_eq!(loop_ctrl.current_step(), 2);
}
#[test]
fn test_agent_loop_max_iterations_exceeded() {
let mut loop_ctrl = AgentLoop::new(3);
assert!(loop_ctrl.next_state().is_some());
assert!(loop_ctrl.next_state().is_some());
assert!(loop_ctrl.next_state().is_some());
let state = loop_ctrl.next_state();
assert!(
matches!(state, Some(AgentState::Failed { reason }) if reason == "Max iterations exceeded")
);
}
#[test]
fn test_agent_state_error_recovery() {
let mut loop_ctrl = AgentLoop::new(100);
loop_ctrl.set_state(AgentState::ErrorRecovery {
error: "Test error".to_string(),
});
let state = loop_ctrl.next_state();
match state {
Some(AgentState::ErrorRecovery { error }) => {
assert_eq!(error, "Test error");
}
_ => panic!("Expected ErrorRecovery state"),
}
}
#[test]
fn test_agent_state_failed() {
let mut loop_ctrl = AgentLoop::new(100);
loop_ctrl.set_state(AgentState::Failed {
reason: "Something went wrong".to_string(),
});
let state = loop_ctrl.next_state();
match state {
Some(AgentState::Failed { reason }) => {
assert_eq!(reason, "Something went wrong");
}
_ => panic!("Expected Failed state"),
}
}
#[test]
fn test_executing_state_tracks_step() {
let state = AgentState::Executing { step: 5 };
match state {
AgentState::Executing { step } => assert_eq!(step, 5),
_ => panic!("Expected Executing state"),
}
}
#[test]
fn test_increment_step_updates_state() {
let mut loop_ctrl = AgentLoop::new(100);
loop_ctrl.increment_step();
match &loop_ctrl.state {
AgentState::Executing { step } => assert_eq!(*step, 1),
_ => panic!("Expected Executing state after increment"),
}
}
#[test]
fn test_reset_for_task() {
let mut loop_ctrl = AgentLoop::new(10);
loop_ctrl.next_state();
loop_ctrl.next_state();
loop_ctrl.next_state();
loop_ctrl.increment_step();
loop_ctrl.increment_step();
assert_eq!(loop_ctrl.iteration, 3);
assert_eq!(loop_ctrl.current_step(), 2);
loop_ctrl.reset_for_task();
assert_eq!(loop_ctrl.iteration, 0);
assert_eq!(loop_ctrl.current_step(), 0);
assert!(matches!(loop_ctrl.state, AgentState::Planning));
for _ in 0..10 {
let state = loop_ctrl.next_state();
assert!(!matches!(state, Some(AgentState::Failed { .. })));
}
let state = loop_ctrl.next_state();
assert!(matches!(state, Some(AgentState::Failed { .. })));
}
#[test]
fn test_restore_progress() {
let mut loop_ctrl = AgentLoop::new(10);
loop_ctrl.restore_progress(3, 7);
assert_eq!(loop_ctrl.current_step(), 3);
assert_eq!(loop_ctrl.current_iteration(), 7);
assert!(matches!(loop_ctrl.state, AgentState::Executing { step: 3 }));
}
#[test]
fn test_approaching_limit_warning_none_early() {
let mut loop_ctrl = AgentLoop::new(100);
for _ in 0..50 {
loop_ctrl.next_state();
}
assert!(loop_ctrl.approaching_limit_warning().is_none());
}
#[test]
fn test_approaching_limit_warning_at_80_pct() {
let mut loop_ctrl = AgentLoop::new(100);
for _ in 0..80 {
loop_ctrl.next_state();
}
let warning = loop_ctrl.approaching_limit_warning();
assert!(warning.is_some());
assert!(warning.unwrap().contains("wrapping up"));
}
#[test]
fn test_approaching_limit_warning_at_90_pct() {
let mut loop_ctrl = AgentLoop::new(100);
for _ in 0..90 {
loop_ctrl.next_state();
}
let warning = loop_ctrl.approaching_limit_warning();
assert!(warning.is_some());
assert!(warning.unwrap().contains("final answer"));
}
#[test]
fn test_approaching_limit_warning_zero_max() {
let loop_ctrl = AgentLoop::new(0);
assert!(loop_ctrl.approaching_limit_warning().is_none());
}
}