Skip to main content

forge_pilot/
config.rs

1//! Configuration types for the forge-pilot OODA loop.
2//!
3//! `LoopConfig` carries all tuning knobs, scope identity, workspace paths,
4//! runtime settings, and operator-provided patch plan seeds.
5
6use forge_engine::{ExperimentConfig, ForgeConfig, StructuredPatch};
7use knowledge_runtime::{RuntimeConfig, Scope};
8use verification_policy::{ApprovalRecord, PolicySnapshot};
9
10/// An operator-provided patch plan seed that overrides automatic plan selection.
11#[derive(Debug, Clone)]
12pub struct PatchPlanSeed {
13    pub target_key: String,
14    pub fixture_path: String,
15    pub patch: StructuredPatch,
16    pub experiment_config: ExperimentConfig,
17    pub description: String,
18}
19
20/// Full configuration for an OODA loop run.
21///
22/// Includes budget limits, scope identity, workspace paths, runtime and
23/// Forge engine settings, policy snapshots, and optional patch seeds.
24#[derive(Debug, Clone)]
25pub struct LoopConfig {
26    pub max_iterations: u32,
27    pub time_budget_secs: u64,
28    pub cooldown_secs: u64,
29    pub halt_urgency_threshold: f64,
30    pub max_retries_per_target: u32,
31    pub allow_advisory_only_steps: bool,
32    pub scope: Scope,
33    pub workspace_path: String,
34    pub memory_dir: String,
35    pub forge_db_path: String,
36    pub include_hyperedges: bool,
37    pub oracle_max_iterations: u32,
38    pub minimal_perturbation_budget: usize,
39    pub observation_import_limit: usize,
40    pub observation_projection_limit: usize,
41    pub runtime_config: RuntimeConfig,
42    pub forge_config: ForgeConfig,
43    pub patch_plan_seeds: Vec<PatchPlanSeed>,
44    pub policy_snapshots: Vec<PolicySnapshot>,
45    pub approval_records: Vec<ApprovalRecord>,
46    pub calibration_abstention_threshold_micros: u64,
47}
48
49impl LoopConfig {
50    /// Builds the default loop configuration for a scope and workspace path.
51    pub fn default_for_scope(scope: Scope, workspace_path: impl Into<String>) -> Self {
52        Self {
53            scope: scope.clone(),
54            workspace_path: workspace_path.into(),
55            runtime_config: RuntimeConfig {
56                default_scope: scope,
57                ..RuntimeConfig {
58                    default_scope: Scope::new("default"),
59                    query: Default::default(),
60                    entity: Default::default(),
61                    projection: Default::default(),
62                    strict_temporal: false,
63                    strict_scope: false,
64                }
65            },
66            ..Self::default()
67        }
68    }
69
70    /// Returns the configured patch seed for a target key, if one exists.
71    pub fn patch_seed_for(&self, target_key: &str) -> Option<&PatchPlanSeed> {
72        self.patch_plan_seeds
73            .iter()
74            .find(|seed| seed.target_key == target_key)
75            .or_else(|| {
76                let target_family = patch_seed_target_family(target_key)?;
77                self.patch_plan_seeds.iter().find(|seed| {
78                    patch_seed_target_family(seed.target_key.as_str()) == Some(target_family)
79                })
80            })
81    }
82}
83
84fn patch_seed_target_family(target_key: &str) -> Option<&str> {
85    target_key.split_once(':').map(|(family, _)| family)
86}
87
88impl Default for LoopConfig {
89    fn default() -> Self {
90        let default_scope = Scope::new("default");
91        Self {
92            max_iterations: 4,
93            time_budget_secs: 30,
94            cooldown_secs: 0,
95            halt_urgency_threshold: 0.25,
96            max_retries_per_target: 2,
97            allow_advisory_only_steps: false,
98            scope: default_scope.clone(),
99            workspace_path: ".".into(),
100            memory_dir: "./memory".into(),
101            forge_db_path: "./forge.db".into(),
102            include_hyperedges: true,
103            oracle_max_iterations: 4,
104            minimal_perturbation_budget: 2,
105            observation_import_limit: 16,
106            observation_projection_limit: 128,
107            runtime_config: RuntimeConfig {
108                default_scope,
109                query: Default::default(),
110                entity: Default::default(),
111                projection: Default::default(),
112                strict_temporal: false,
113                strict_scope: false,
114            },
115            forge_config: ForgeConfig::default(),
116            patch_plan_seeds: Vec::new(),
117            policy_snapshots: vec![PolicySnapshot::permissive(
118                "forge-pilot.default",
119                "2026-03-12T00:00:00Z",
120            )],
121            approval_records: Vec::new(),
122            calibration_abstention_threshold_micros: 500_000,
123        }
124    }
125}