Skip to main content

loopsmith_core/config/
constraints.rs

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