Skip to main content

aios_protocol/
mode.rs

1//! Operating modes and gating profiles.
2//!
3//! The operating mode represents what the agent is currently doing.
4//! The gating profile controls what the agent is allowed to do.
5
6use crate::event::RiskLevel;
7use serde::{Deserialize, Serialize};
8
9/// The agent's current operating mode.
10///
11/// Mode transitions are driven by the homeostasis controller
12/// based on the AgentStateVector.
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum OperatingMode {
16    /// High uncertainty — gathering information, read-only tools preferred.
17    Explore,
18    /// Default productive mode — executing tools, making progress.
19    #[default]
20    Execute,
21    /// High side-effect pressure — validating before committing.
22    Verify,
23    /// Error streak >= threshold — rollback, change strategy.
24    Recover,
25    /// Pending approvals or human input needed.
26    AskHuman,
27    /// Progress >= 98% or awaiting next signal.
28    Sleep,
29}
30
31/// Dynamic constraints output by the homeostasis controller.
32///
33/// Enforced at the harness boundary in the runtime. Tighter than
34/// static policy (which is the hard floor), gating provides
35/// dynamic safety based on agent health state.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct GatingProfile {
38    /// Whether side effects (writes, deletes, network) are allowed.
39    pub allow_side_effects: bool,
40    /// Minimum risk level that requires human approval.
41    pub require_approval_for_risk: RiskLevel,
42    /// Maximum tool calls allowed per tick.
43    pub max_tool_calls_per_tick: u32,
44    /// Maximum file mutations allowed per tick.
45    pub max_file_mutations_per_tick: u32,
46    /// Whether network access is allowed.
47    pub allow_network: bool,
48    /// Whether shell execution is allowed.
49    pub allow_shell: bool,
50}
51
52impl Default for GatingProfile {
53    fn default() -> Self {
54        Self {
55            allow_side_effects: true,
56            require_approval_for_risk: RiskLevel::High,
57            max_tool_calls_per_tick: 10,
58            max_file_mutations_per_tick: 5,
59            allow_network: true,
60            allow_shell: true,
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn operating_mode_serde_roundtrip() {
71        for mode in [
72            OperatingMode::Explore,
73            OperatingMode::Execute,
74            OperatingMode::Verify,
75            OperatingMode::Recover,
76            OperatingMode::AskHuman,
77            OperatingMode::Sleep,
78        ] {
79            let json = serde_json::to_string(&mode).unwrap();
80            let back: OperatingMode = serde_json::from_str(&json).unwrap();
81            assert_eq!(mode, back);
82        }
83    }
84
85    #[test]
86    fn gating_profile_default() {
87        let g = GatingProfile::default();
88        assert!(g.allow_side_effects);
89        assert!(g.allow_network);
90        assert!(g.allow_shell);
91        assert_eq!(g.max_tool_calls_per_tick, 10);
92    }
93
94    #[test]
95    fn gating_profile_serde_roundtrip() {
96        let g = GatingProfile::default();
97        let json = serde_json::to_string(&g).unwrap();
98        let back: GatingProfile = serde_json::from_str(&json).unwrap();
99        assert_eq!(back.max_tool_calls_per_tick, g.max_tool_calls_per_tick);
100    }
101}