clawgarden-agent 0.8.3

Agent runtime with persona/memory loader, judge, and pi RPC for ClawGarden
Documentation
//! Agent loop policy — State-based termination/abort rules

#[derive(Debug, Clone)]
pub struct LoopPolicy {
    /// Final safety fuse (not a normal termination condition)
    pub hard_max_steps: usize,
    /// Max allowed repeats of the same action
    pub same_action_repeat_limit: usize,
    /// Max consecutive errors allowed
    pub consecutive_error_limit: usize,
    /// Progress window size
    pub stall_window: usize,
    /// Progress sum below this value indicates stall
    pub stall_min_progress_sum: i32,
    /// Max allowed repeats of same action+observation cycle
    pub cycle_repeat_limit: usize,
    /// Enable checkpointing
    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());
    }
}