Skip to main content

enact_core/kernel/
execution_state.rs

1//! Execution State Machine
2//!
3//! Defines the explicit state machine for execution lifecycle.
4//! This is the authoritative definition - all state transitions
5//! must go through the reducer.
6
7use serde::{Deserialize, Serialize};
8
9/// Execution lifecycle state - explicit state machine
10///
11/// @see packages/enact-schemas/src/streaming.schemas.ts - executionEventDataSchema.status
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[serde(rename_all = "PascalCase")]
14pub enum ExecutionState {
15    /// Execution created but not yet started
16    #[default]
17    Created,
18    /// Execution is in planning phase (Agentic DAG planning)
19    Planning,
20    /// Execution is actively running
21    Running,
22    /// Execution is paused (can be resumed)
23    Paused,
24    /// Execution is waiting for external input (approval, tool result, etc.)
25    Waiting(WaitReason),
26    /// Execution completed successfully
27    Completed,
28    /// Execution failed with an error
29    Failed,
30    /// Execution was cancelled by user/system
31    Cancelled,
32}
33
34impl ExecutionState {
35    /// Check if execution is in a terminal state
36    pub fn is_terminal(&self) -> bool {
37        matches!(self, Self::Completed | Self::Failed | Self::Cancelled)
38    }
39
40    /// Check if execution can be resumed
41    pub fn can_resume(&self) -> bool {
42        matches!(self, Self::Paused | Self::Waiting(_))
43    }
44
45    /// Check if execution is active (running or waiting)
46    pub fn is_active(&self) -> bool {
47        matches!(self, Self::Running | Self::Waiting(_))
48    }
49}
50
51/// Reason for waiting state
52///
53/// This enum covers all documented wait scenarios for long-running executions.
54/// @see docs/TECHNICAL/27-LONG-RUNNING-EXECUTIONS.md
55/// @see docs/TECHNICAL/31-MID-EXECUTION-GUIDANCE.md
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum WaitReason {
59    /// Waiting for human approval (HITL)
60    Approval,
61    /// Waiting for user input
62    UserInput,
63    /// Waiting due to rate limits
64    RateLimit,
65    /// Waiting for an external dependency (API, tool, resource)
66    External,
67    /// Waiting for checkpoint operation to complete
68    Checkpoint,
69    /// Waiting due to cost threshold reached (enforcement middleware)
70    CostThreshold,
71    /// Waiting due to memory pressure (context compaction needed)
72    MemoryPressure,
73    /// Waiting for sub-agent execution to complete
74    SubAgent,
75}
76
77/// Step lifecycle state
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
79#[serde(rename_all = "PascalCase")]
80pub enum StepState {
81    /// Step created/queued
82    #[default]
83    Pending,
84    /// Step is running
85    Running,
86    /// Step completed successfully
87    Completed,
88    /// Step failed
89    Failed,
90    /// Step was skipped
91    Skipped,
92}
93
94impl StepState {
95    /// Check if step is in a terminal state
96    pub fn is_terminal(&self) -> bool {
97        matches!(self, Self::Completed | Self::Failed | Self::Skipped)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_execution_state_terminal() {
107        assert!(!ExecutionState::Created.is_terminal());
108        assert!(!ExecutionState::Planning.is_terminal());
109        assert!(!ExecutionState::Running.is_terminal());
110        assert!(!ExecutionState::Paused.is_terminal());
111        assert!(ExecutionState::Completed.is_terminal());
112        assert!(ExecutionState::Failed.is_terminal());
113        assert!(ExecutionState::Cancelled.is_terminal());
114    }
115
116    #[test]
117    fn test_execution_state_can_resume() {
118        assert!(!ExecutionState::Created.can_resume());
119        assert!(!ExecutionState::Planning.can_resume());
120        assert!(!ExecutionState::Running.can_resume());
121        assert!(ExecutionState::Paused.can_resume());
122        assert!(ExecutionState::Waiting(WaitReason::Approval).can_resume());
123    }
124
125    #[test]
126    fn test_execution_state_is_active() {
127        assert!(!ExecutionState::Created.is_active());
128        assert!(!ExecutionState::Planning.is_active());
129        assert!(ExecutionState::Running.is_active());
130        assert!(!ExecutionState::Paused.is_active());
131        assert!(ExecutionState::Waiting(WaitReason::Approval).is_active());
132        assert!(ExecutionState::Waiting(WaitReason::External).is_active());
133        assert!(!ExecutionState::Completed.is_active());
134        assert!(!ExecutionState::Failed.is_active());
135        assert!(!ExecutionState::Cancelled.is_active());
136    }
137
138    #[test]
139    fn test_execution_state_serde() {
140        // Test all variants serialize/deserialize correctly
141        let states = vec![
142            ExecutionState::Created,
143            ExecutionState::Planning,
144            ExecutionState::Running,
145            ExecutionState::Paused,
146            ExecutionState::Waiting(WaitReason::Approval),
147            ExecutionState::Waiting(WaitReason::UserInput),
148            ExecutionState::Waiting(WaitReason::RateLimit),
149            ExecutionState::Waiting(WaitReason::External),
150            ExecutionState::Waiting(WaitReason::Checkpoint),
151            ExecutionState::Waiting(WaitReason::CostThreshold),
152            ExecutionState::Waiting(WaitReason::MemoryPressure),
153            ExecutionState::Waiting(WaitReason::SubAgent),
154            ExecutionState::Completed,
155            ExecutionState::Failed,
156            ExecutionState::Cancelled,
157        ];
158        for state in states {
159            let json = serde_json::to_string(&state).unwrap();
160            let parsed: ExecutionState = serde_json::from_str(&json).unwrap();
161            assert_eq!(state, parsed);
162        }
163    }
164
165    #[test]
166    fn test_step_state_terminal() {
167        assert!(!StepState::Pending.is_terminal());
168        assert!(!StepState::Running.is_terminal());
169        assert!(StepState::Completed.is_terminal());
170        assert!(StepState::Failed.is_terminal());
171        assert!(StepState::Skipped.is_terminal());
172    }
173
174    #[test]
175    fn test_step_state_serde() {
176        let states = vec![
177            StepState::Pending,
178            StepState::Running,
179            StepState::Completed,
180            StepState::Failed,
181            StepState::Skipped,
182        ];
183        for state in states {
184            let json = serde_json::to_string(&state).unwrap();
185            let parsed: StepState = serde_json::from_str(&json).unwrap();
186            assert_eq!(state, parsed);
187        }
188    }
189}