Skip to main content

perspt_agent/runtime/
settings.rs

1//! Run settings, model routes, and the run summary for one PSP-9 session.
2
3use perspt_sdk::{ApprovalPolicy, NodeTerminalOutcome};
4
5/// Finite settings for one PSP-9 run.
6#[derive(Debug, Clone)]
7pub struct Psp9RunConfig {
8    pub max_turns: u32,
9    pub max_calls_per_turn: u32,
10    pub rejection_budget: u32,
11    pub rho_gate: f64,
12    pub approval_policy: ApprovalPolicy,
13    /// Explicit reduced-isolation mode for hosts without a registered process
14    /// sandbox, or embedders that isolate the complete Perspt process.
15    pub allow_unisolated_verifiers: bool,
16    pub max_parallel_verifiers: usize,
17    /// Persist signed grant intent across sessions. Disabled by default;
18    /// resume still re-mints fresh, epoch-bound capabilities.
19    pub persistent_grants: bool,
20    /// Explicit opt-in for governed dependency mutation (Gate J). Off by
21    /// default: `MutateDependencies` stays withheld from every grant.
22    pub allow_dependency_mutation: bool,
23    /// Concurrent work-graph nodes (Gate P). 1 keeps the single-node path
24    /// verbatim; above 1 the multi-node dispatcher runs and a governed
25    /// architect planning turn may decompose the task.
26    pub max_parallel_nodes: usize,
27    /// Per-model-call wall-clock deadline (seconds). Exceeding it is a
28    /// transport failure that consumes sticky failover.
29    pub turn_deadline_secs: u64,
30    /// Declare the plugin `format` verifier stage as an acceptance sensor
31    /// (`[verification] require_format`). Off by default.
32    pub require_format: bool,
33    /// How project tests contribute to acceptance. Ordinary development uses
34    /// `Evolving`; stronger regression or protected-oracle evidence is
35    /// deliberately opt-in.
36    pub test_policy: perspt_core::TestPolicy,
37    /// Protected acceptance material for `TestPolicy::ExternalOracle`.
38    pub external_oracle: Option<perspt_core::ExternalOracleConfig>,
39    /// Per-stage governed verifier wall-clock limits
40    /// (`[verification] stage_timeout_secs` + per-stage overrides).
41    pub verifier_timeouts: crate::verifier::VerifierTimeouts,
42    /// Definition 6 context reserves and working-set bounds (`[context]`).
43    pub resident: crate::toolloop::ResidentReserves,
44    /// Seven-arm evaluation ablation: drop typed correction packets and
45    /// fall back to the legacy first-direction text. Never set in
46    /// production configurations.
47    pub ablate_correction_packets: bool,
48    /// Seven-arm evaluation ablation: skip live resident-context paging.
49    /// Never set in production configurations.
50    pub ablate_context_paging: bool,
51}
52
53impl Default for Psp9RunConfig {
54    fn default() -> Self {
55        Self {
56            max_turns: 12,
57            max_calls_per_turn: 8,
58            rejection_budget: 4,
59            rho_gate: 0.5,
60            approval_policy: ApprovalPolicy::Ask,
61            allow_unisolated_verifiers: false,
62            max_parallel_verifiers: 4,
63            persistent_grants: false,
64            allow_dependency_mutation: false,
65            max_parallel_nodes: 1,
66            turn_deadline_secs: crate::turn::DEFAULT_TURN_DEADLINE_SECS,
67            require_format: false,
68            test_policy: perspt_core::TestPolicy::Evolving,
69            external_oracle: None,
70            verifier_timeouts: crate::verifier::VerifierTimeouts::default(),
71            resident: crate::toolloop::ResidentReserves::default(),
72            ablate_correction_packets: false,
73            ablate_context_paging: false,
74        }
75    }
76}
77
78#[derive(Debug, Clone)]
79pub struct Psp9RunSummary {
80    pub session_id: String,
81    pub node_id: String,
82    pub outcome: NodeTerminalOutcome,
83    pub turns_used: u32,
84    pub ledger_head: String,
85    pub promoted_paths: Vec<String>,
86}
87
88/// Explicit model-plane routes for one PSP-9 session. Role routes are not
89/// interchangeable: only `fallbacks` may replace the actuator after a recorded
90/// transport failure.
91#[derive(Debug, Clone, Default)]
92pub struct Psp9ModelRoutes {
93    pub primary: Option<String>,
94    pub actuator: Option<String>,
95    pub explorer: Option<String>,
96    pub adjudicator: Option<String>,
97    pub fallbacks: Vec<String>,
98}
99
100/// Fold the file configuration into the run settings: `[models]`
101/// turn deadline, `[verification]` format gating, and `[context]` reserves.
102pub(super) fn apply_config_overrides(
103    mut run_config: Psp9RunConfig,
104    config: &perspt_core::Config,
105) -> Psp9RunConfig {
106    if let Some(secs) = config.models.as_ref().and_then(|m| m.turn_timeout_secs) {
107        run_config.turn_deadline_secs = secs.max(1);
108    }
109    if let Some(verification) = &config.verification {
110        run_config.allow_unisolated_verifiers |= verification.allow_unisolated.unwrap_or(false);
111        run_config.require_format = verification.require_format.unwrap_or(false);
112        run_config.test_policy = verification.test_policy.unwrap_or_default();
113        run_config.external_oracle = verification.external_oracle.clone();
114        let defaults = crate::verifier::VerifierTimeouts::default();
115        run_config.verifier_timeouts = crate::verifier::VerifierTimeouts {
116            default_secs: verification
117                .stage_timeout_secs
118                .unwrap_or(defaults.default_secs),
119            syntax: verification.syntax_timeout_secs,
120            build: verification.build_timeout_secs,
121            test: verification.test_timeout_secs,
122            lint: verification.lint_timeout_secs,
123            format: verification.format_timeout_secs,
124        };
125    }
126    fold_context_overrides(&mut run_config, config);
127    run_config
128}
129
130fn fold_context_overrides(run_config: &mut Psp9RunConfig, config: &perspt_core::Config) {
131    if let Some(context) = &config.context {
132        let defaults = crate::toolloop::ResidentReserves::default();
133        run_config.resident = crate::toolloop::ResidentReserves {
134            paging_enabled: defaults.paging_enabled,
135            output_reserve_tokens: context
136                .output_reserve_tokens
137                .unwrap_or(defaults.output_reserve_tokens),
138            guard_reserve_tokens: context
139                .guard_reserve_tokens
140                .unwrap_or(defaults.guard_reserve_tokens),
141            frame_tokens: context
142                .synopsis_frame_tokens
143                .unwrap_or(defaults.frame_tokens),
144            pinned_tail: context
145                .working_set_turns
146                .map(|turns| turns as usize)
147                .unwrap_or(defaults.pinned_tail),
148        };
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn verification_timeouts_fold_from_the_config_file() {
158        let mut config = perspt_core::Config::default();
159        config.verification = Some(perspt_core::config::VerificationConfig {
160            stage_timeout_secs: Some(240),
161            test_timeout_secs: Some(600),
162            ..Default::default()
163        });
164        let folded = apply_config_overrides(Psp9RunConfig::default(), &config);
165        assert_eq!(folded.verifier_timeouts.default_secs, 240);
166        assert_eq!(folded.verifier_timeouts.test, Some(600));
167        assert_eq!(folded.verifier_timeouts.build, None);
168        use perspt_core::plugin::VerifierStage;
169        let timeouts = folded.verifier_timeouts;
170        assert_eq!(timeouts.for_stage(Some(VerifierStage::Test)).as_secs(), 600);
171        assert_eq!(
172            timeouts.for_stage(Some(VerifierStage::Build)).as_secs(),
173            240
174        );
175        assert_eq!(timeouts.for_stage(None).as_secs(), 240);
176    }
177
178    #[test]
179    fn default_timeouts_stay_at_180_seconds() {
180        let timeouts = crate::verifier::VerifierTimeouts::default();
181        assert_eq!(
182            timeouts
183                .for_stage(Some(perspt_core::plugin::VerifierStage::Lint))
184                .as_secs(),
185            180
186        );
187    }
188
189    #[test]
190    fn test_policy_folds_from_verification_config() {
191        let mut config = perspt_core::Config::default();
192        config.verification = Some(perspt_core::config::VerificationConfig {
193            test_policy: Some(perspt_core::TestPolicy::BackwardCompatible),
194            ..Default::default()
195        });
196        let folded = apply_config_overrides(Psp9RunConfig::default(), &config);
197        assert_eq!(
198            folded.test_policy,
199            perspt_core::TestPolicy::BackwardCompatible
200        );
201        assert!(folded.external_oracle.is_none());
202    }
203
204    #[test]
205    fn reduced_isolation_requires_an_explicit_config_opt_in() {
206        let strict = apply_config_overrides(Psp9RunConfig::default(), &Default::default());
207        assert!(!strict.allow_unisolated_verifiers);
208
209        let mut config = perspt_core::Config::default();
210        config.verification = Some(perspt_core::config::VerificationConfig {
211            allow_unisolated: Some(true),
212            ..Default::default()
213        });
214        let folded = apply_config_overrides(Psp9RunConfig::default(), &config);
215        assert!(folded.allow_unisolated_verifiers);
216    }
217}