Skip to main content

loopsmith_core/config/
gates.rs

1//! Section F — the layered exits.
2
3use super::yes;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct StopGates {
9    /// Hard ceiling on whole-loop iterations.
10    #[serde(default = "default_max_iterations")]
11    pub max_iterations: u32,
12    /// Per-node revision ceiling. A node that has been dispatched this many
13    /// times without its goals being satisfied stops being dispatched, so one
14    /// stuck node cannot burn the whole iteration budget.
15    #[serde(default = "default_max_revisions")]
16    pub max_revisions_per_node: u32,
17    /// Wall-clock budget for the whole run.
18    #[serde(default)]
19    pub max_wall_clock_seconds: Option<u64>,
20    /// Token budget for the whole run, summed across providers.
21    #[serde(default)]
22    pub max_tokens: Option<u64>,
23    /// Currency budget for the whole run.
24    #[serde(default)]
25    pub max_cost_usd: Option<f64>,
26    /// Halt when this many consecutive iterations produce no measurable
27    /// change. Jidoka: stop the line rather than spin.
28    #[serde(default = "default_no_progress")]
29    pub no_progress_iterations: u32,
30    /// Perturb the run after this many stalled iterations, instead of waiting
31    /// to halt at `no_progress_iterations`.
32    ///
33    /// Must be strictly less than `no_progress_iterations`: the point is to try
34    /// something different *before* giving up, and a threshold at or past the
35    /// halt point never fires. Leave it unset to halt without ever varying —
36    /// perturbation costs a provider call and changes what the loop does, so it
37    /// is opt-in.
38    #[serde(default)]
39    pub no_progress_iterations_randomness: Option<u32>,
40    /// Stop as soon as every `overall` success scenario is met.
41    #[serde(default = "yes")]
42    pub stop_on_overall_success: bool,
43}
44
45impl Default for StopGates {
46    fn default() -> Self {
47        Self {
48            max_iterations: default_max_iterations(),
49            max_revisions_per_node: default_max_revisions(),
50            max_wall_clock_seconds: None,
51            max_tokens: None,
52            max_cost_usd: None,
53            no_progress_iterations: default_no_progress(),
54            no_progress_iterations_randomness: None,
55            stop_on_overall_success: true,
56        }
57    }
58}
59
60fn default_max_iterations() -> u32 {
61    10
62}
63fn default_max_revisions() -> u32 {
64    3
65}
66fn default_no_progress() -> u32 {
67    3
68}