Skip to main content

oxicode_sdk/lifecycle/
mod.rs

1//! Lifecycle module — agent lifecycle management.
2//!
3//! Provides `AgentHandle`, `AgentSupervisor`, and `SnapshotStore` for
4//! spawn / suspend / resume / checkpoint / terminate operations.
5//!
6//! # Module layout
7//!
8//! | File              | Responsibility                                        |
9//! |-------------------|-------------------------------------------------------|
10//! | `mod.rs`          | `AgentStatus`, `AgentLifecycleEvent`, `MetricsSnapshot`, re-exports |
11//! | `supervisor.rs`   | `AgentSupervisor`, `SupervisorPolicy`, `RestartBackoff`, `AgentHandle` |
12//! | `snapshot.rs`     | `AgentSnapshot`, `ToolManifest`, `SnapshotStore`, `FileSnapshotStore` |
13
14mod agent_pool;
15pub mod hub;
16mod snapshot;
17mod subagent_coordinator;
18mod supervisor;
19
20// ── Re-exports (thin facade) ─────────────────────────────────────────────
21pub use agent_pool::AgentPool;
22pub use hub::{HubKind, HubStatus};
23pub use snapshot::{AgentSnapshot, FileSnapshotStore, SnapshotStore, ToolManifest};
24pub use subagent_coordinator::{
25    DEFAULT_MAX_SUBAGENT_DEPTH, SubagentCoordinator, SubagentCoordinatorError,
26    SubagentSpawnRequest, SubagentState, SubagentTracker,
27};
28pub use supervisor::{AgentHandle, AgentSupervisor, RestartBackoff, SupervisorPolicy};
29
30use serde::{Deserialize, Serialize};
31
32// ── AgentStatus ──────────────────────────────────────────────────────────
33
34/// Lifecycle status of an agent.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum AgentStatus {
38    /// Created but has not started any runs.
39    #[default]
40    Created,
41    /// Actively processing.
42    Running,
43    /// Suspended (can be resumed).
44    Suspended,
45    /// Completed all work (terminal).
46    Terminated,
47    /// Fatal error, cannot be resumed (terminal).
48    Failed,
49}
50
51impl std::fmt::Display for AgentStatus {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::Created => write!(f, "created"),
55            Self::Running => write!(f, "running"),
56            Self::Suspended => write!(f, "suspended"),
57            Self::Terminated => write!(f, "terminated"),
58            Self::Failed => write!(f, "failed"),
59        }
60    }
61}
62
63impl AgentStatus {
64    /// Whether this is a terminal state.
65    pub fn is_terminal(&self) -> bool {
66        matches!(self, Self::Terminated | Self::Failed)
67    }
68
69    /// Whether the agent can accept a new `run()`.
70    pub fn is_runnable(&self) -> bool {
71        matches!(self, Self::Created | Self::Suspended)
72    }
73}
74
75// ── AgentLifecycleEvent ──────────────────────────────────────────────────
76
77/// Events emitted during agent lifecycle transitions.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(tag = "type", rename_all = "snake_case")]
80#[non_exhaustive]
81pub enum AgentLifecycleEvent {
82    /// An agent was spawned into the pool.
83    Spawned {
84        /// Identifier of the spawned agent.
85        agent_id: String,
86        /// Identifier of the parent agent, if spawned as a child.
87        parent_id: Option<String>,
88        /// Identifier of the model the agent is configured to use.
89        model_id: String,
90        /// Wall-clock time of the event, in ms since Unix epoch.
91        timestamp_ms: u64,
92    },
93    /// An agent began a run.
94    RunStart {
95        /// Identifier of the agent.
96        agent_id: String,
97        /// Wall-clock time of the event, in ms since Unix epoch.
98        timestamp_ms: u64,
99    },
100    /// An agent completed a run.
101    RunEnd {
102        /// Identifier of the agent.
103        agent_id: String,
104        /// Wall-clock time of the event, in ms since Unix epoch.
105        timestamp_ms: u64,
106        /// Whether the run completed without error.
107        success: bool,
108    },
109    /// An agent was suspended and its state snapshotted.
110    Suspended {
111        /// Identifier of the agent.
112        agent_id: String,
113        /// Captured state snapshot at suspension time.
114        snapshot: Box<AgentSnapshot>,
115        /// Wall-clock time of the event, in ms since Unix epoch.
116        timestamp_ms: u64,
117    },
118    /// A suspended agent resumed execution.
119    Resumed {
120        /// Identifier of the agent.
121        agent_id: String,
122        /// Identifier of the snapshot resumed from, if any.
123        from_snapshot_id: Option<String>,
124        /// Wall-clock time of the event, in ms since Unix epoch.
125        timestamp_ms: u64,
126    },
127    /// An agent was permanently terminated.
128    Terminated {
129        /// Identifier of the agent.
130        agent_id: String,
131        /// Wall-clock time of the event, in ms since Unix epoch.
132        timestamp_ms: u64,
133    },
134    /// An agent switched its underlying model.
135    ModelSwitched {
136        /// Identifier of the agent.
137        agent_id: String,
138        /// Identifier of the previous model.
139        from_model: String,
140        /// Identifier of the new model.
141        to_model: String,
142        /// Wall-clock time of the event, in ms since Unix epoch.
143        timestamp_ms: u64,
144    },
145}
146
147impl AgentLifecycleEvent {
148    /// Current wall-clock time in ms since Unix epoch.
149    pub fn now_ms() -> u64 {
150        std::time::SystemTime::now()
151            .duration_since(std::time::UNIX_EPOCH)
152            .map(|d| d.as_millis() as u64)
153            .unwrap_or(0)
154    }
155}
156
157// Re-export metrics snapshot (used in snapshots and handles).
158pub use crate::metrics::MetricsSnapshot;
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn agent_status_display() {
166        assert_eq!(AgentStatus::Running.to_string(), "running");
167        assert_eq!(AgentStatus::Suspended.to_string(), "suspended");
168        assert_eq!(AgentStatus::Failed.to_string(), "failed");
169    }
170
171    #[test]
172    fn agent_status_terminal() {
173        assert!(AgentStatus::Terminated.is_terminal());
174        assert!(AgentStatus::Failed.is_terminal());
175        assert!(!AgentStatus::Running.is_terminal());
176        assert!(!AgentStatus::Created.is_terminal());
177    }
178
179    #[test]
180    fn agent_status_runnable() {
181        assert!(AgentStatus::Created.is_runnable());
182        assert!(AgentStatus::Suspended.is_runnable());
183        assert!(!AgentStatus::Running.is_runnable());
184        assert!(!AgentStatus::Terminated.is_runnable());
185    }
186
187    #[test]
188    fn lifecycle_event_now_ms() {
189        let ms = AgentLifecycleEvent::now_ms();
190        assert!(ms > 1_700_000_000_000); // after 2023
191    }
192
193    #[test]
194    fn metrics_snapshot_default() {
195        let m = MetricsSnapshot::default();
196        assert_eq!(m.total_runs, 0);
197    }
198}