Skip to main content

loopsmith_core/config/
constraints.rs

1//! Section H — constraints applied per node or globally.
2
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7#[serde(deny_unknown_fields)]
8pub struct Constraints {
9    /// Applied to every node unless overridden.
10    #[serde(default)]
11    pub global: ConstraintSet,
12    /// Keyed by node id.
13    #[serde(default)]
14    pub per_node: BTreeMap<String, ConstraintSet>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, Default)]
18#[serde(deny_unknown_fields)]
19pub struct ConstraintSet {
20    /// Literal rules injected into the node prompt.
21    #[serde(default)]
22    pub rules: Vec<String>,
23    #[serde(default)]
24    pub forbidden_paths: Vec<String>,
25    #[serde(default)]
26    pub forbidden_commands: Vec<String>,
27    #[serde(default)]
28    pub max_tokens: Option<u64>,
29    #[serde(default)]
30    pub max_seconds: Option<u64>,
31    /// Any action matching these requires a human before proceeding. Bezos
32    /// Type 1: irreversible decisions do not get made at machine speed.
33    #[serde(default)]
34    pub human_checkpoint: Vec<String>,
35}
36
37impl ConstraintSet {
38    /// The frozen git rule set that made large parallel runs safe in the
39    /// corpus. Emitted into every parallel node unless the author opts out.
40    pub fn frozen_git_rules() -> Vec<String> {
41        vec![
42            "Never git stash. Never git reset.".into(),
43            "No git command except committing a specific file.".into(),
44            "No slow commands before the test phase.".into(),
45        ]
46    }
47
48    /// Merge a global set with a node override; node rules append, node
49    /// limits win where present.
50    pub fn merged(global: &ConstraintSet, node: Option<&ConstraintSet>) -> ConstraintSet {
51        let mut out = global.clone();
52        if let Some(n) = node {
53            out.rules.extend(n.rules.iter().cloned());
54            out.forbidden_paths.extend(n.forbidden_paths.iter().cloned());
55            out.forbidden_commands
56                .extend(n.forbidden_commands.iter().cloned());
57            out.human_checkpoint
58                .extend(n.human_checkpoint.iter().cloned());
59            if n.max_tokens.is_some() {
60                out.max_tokens = n.max_tokens;
61            }
62            if n.max_seconds.is_some() {
63                out.max_seconds = n.max_seconds;
64            }
65        }
66        out
67    }
68}