#[derive(Debug, Clone)]
pub struct LoopPolicy {
pub hard_max_steps: usize,
pub same_action_repeat_limit: usize,
pub consecutive_error_limit: usize,
pub stall_window: usize,
pub stall_min_progress_sum: i32,
pub cycle_repeat_limit: usize,
pub checkpoint_enabled: bool,
}
impl LoopPolicy {
pub fn defaults() -> Self {
Self {
hard_max_steps: 64,
same_action_repeat_limit: 3,
consecutive_error_limit: 3,
stall_window: 6,
stall_min_progress_sum: 0,
cycle_repeat_limit: 3,
checkpoint_enabled: true,
}
}
pub fn validate_runtime(&self) -> anyhow::Result<()> {
if !(8..=4096).contains(&self.hard_max_steps) {
anyhow::bail!("loop_hard_max_steps out of range: {}", self.hard_max_steps);
}
if !(2..=64).contains(&self.same_action_repeat_limit) {
anyhow::bail!(
"loop_same_action_repeat_limit out of range: {}",
self.same_action_repeat_limit
);
}
if !(1..=64).contains(&self.consecutive_error_limit) {
anyhow::bail!(
"loop_consecutive_error_limit out of range: {}",
self.consecutive_error_limit
);
}
if !(2..=256).contains(&self.stall_window) {
anyhow::bail!("loop_stall_window out of range: {}", self.stall_window);
}
if self.stall_window > self.hard_max_steps {
anyhow::bail!(
"loop_stall_window ({}) cannot exceed loop_hard_max_steps ({})",
self.stall_window,
self.hard_max_steps
);
}
let min = -(self.stall_window as i32);
let max = self.stall_window as i32;
if self.stall_min_progress_sum < min || self.stall_min_progress_sum > max {
anyhow::bail!(
"loop_stall_min_progress_sum ({}) out of range [{}..{}]",
self.stall_min_progress_sum,
min,
max
);
}
if !(2..=64).contains(&self.cycle_repeat_limit) {
anyhow::bail!(
"loop_cycle_repeat_limit out of range: {}",
self.cycle_repeat_limit
);
}
Ok(())
}
pub fn validate(&self) {
debug_assert!(self.validate_runtime().is_ok());
}
}