Skip to main content

agent_base/types/
plan.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ExecutionPlan {
6    pub id: String,
7    pub objective: String,
8    pub steps: Vec<PlanStep>,
9    pub status: PlanStatus,
10    #[serde(default)]
11    pub context: Value,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct PlanStep {
16    pub id: String,
17    pub description: String,
18    /// Business-agnostic payload. Each domain defines its own schema.
19    /// For example, an ops step might be:
20    /// `{"type":"ssh_command","command":"df -h","host_id":"host1"}`
21    pub payload: Value,
22    #[serde(default)]
23    pub dependencies: Vec<String>,
24    pub status: StepStatus,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub result: Option<StepResult>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum PlanStatus {
31    Created,
32    Approved,
33    Executing,
34    Completed,
35    Failed,
36    Cancelled,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum StepStatus {
41    Pending,
42    Running,
43    Completed,
44    Failed,
45    Skipped,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct StepResult {
50    pub success: bool,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub output: Option<String>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub error: Option<String>,
55    pub duration_ms: u64,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59pub enum RecoveryAction {
60    Retry,
61    Skip,
62    Abort,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct PlanStoreData {
67    pub plan: ExecutionPlan,
68    #[serde(default)]
69    pub metadata: Value,
70}
71
72impl ExecutionPlan {
73    pub fn new(id: impl Into<String>, objective: impl Into<String>) -> Self {
74        Self {
75            id: id.into(),
76            objective: objective.into(),
77            steps: Vec::new(),
78            status: PlanStatus::Created,
79            context: Value::Null,
80        }
81    }
82
83    pub fn current_step(&self) -> Option<&PlanStep> {
84        self.steps.iter().find(|s| s.status == StepStatus::Running)
85    }
86
87    pub fn next_pending_step(&self) -> Option<&PlanStep> {
88        self.steps.iter().find(|s| s.status == StepStatus::Pending)
89    }
90
91    pub fn is_completed(&self) -> bool {
92        self.status == PlanStatus::Completed
93            || self
94                .steps
95                .iter()
96                .all(|s| matches!(s.status, StepStatus::Completed | StepStatus::Skipped))
97    }
98
99    pub fn has_failed(&self) -> bool {
100        self.status == PlanStatus::Failed || self.steps.iter().any(|s| s.status == StepStatus::Failed)
101    }
102
103    pub fn progress(&self) -> (usize, usize) {
104        let total = self.steps.len();
105        let completed = self
106            .steps
107            .iter()
108            .filter(|s| matches!(s.status, StepStatus::Completed | StepStatus::Skipped))
109            .count();
110        (completed, total)
111    }
112}
113
114impl PlanStep {
115    pub fn new(
116        id: impl Into<String>,
117        description: impl Into<String>,
118        payload: Value,
119    ) -> Self {
120        Self {
121            id: id.into(),
122            description: description.into(),
123            payload,
124            dependencies: Vec::new(),
125            status: StepStatus::Pending,
126            result: None,
127        }
128    }
129
130    pub fn with_dependencies(mut self, dependencies: Vec<String>) -> Self {
131        self.dependencies = dependencies;
132        self
133    }
134}
135
136impl StepResult {
137    pub fn success(output: impl Into<String>, duration_ms: u64) -> Self {
138        Self {
139            success: true,
140            output: Some(output.into()),
141            error: None,
142            duration_ms,
143        }
144    }
145
146    pub fn failure(error: impl Into<String>, duration_ms: u64) -> Self {
147        Self {
148            success: false,
149            output: None,
150            error: Some(error.into()),
151            duration_ms,
152        }
153    }
154}