Skip to main content

minion_engine/workflow/
schema.rs

1use std::collections::HashMap;
2
3use serde::Deserialize;
4
5/// Declared output type for a step — controls how raw output is parsed
6#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
7#[serde(rename_all = "snake_case")]
8pub enum OutputType {
9    #[default]
10    Text,
11    Json,
12    Integer,
13    Lines,
14    Boolean,
15}
16
17/// Top-level workflow definition
18#[derive(Debug, Clone, Deserialize)]
19pub struct WorkflowDef {
20    pub name: String,
21    #[serde(default)]
22    pub version: u32,
23    pub description: Option<String>,
24    #[serde(default)]
25    pub config: WorkflowConfig,
26    pub prompts_dir: Option<String>,
27    #[serde(default)]
28    pub scopes: HashMap<String, ScopeDef>,
29    pub steps: Vec<StepDef>,
30}
31
32/// Config block with 4 layers
33#[derive(Debug, Clone, Default, Deserialize)]
34pub struct WorkflowConfig {
35    #[serde(default)]
36    pub global: HashMap<String, serde_yaml::Value>,
37    #[serde(default)]
38    pub agent: HashMap<String, serde_yaml::Value>,
39    #[serde(default)]
40    pub cmd: HashMap<String, serde_yaml::Value>,
41    #[serde(default)]
42    pub chat: HashMap<String, serde_yaml::Value>,
43    #[serde(default)]
44    pub gate: HashMap<String, serde_yaml::Value>,
45    #[serde(default)]
46    pub patterns: HashMap<String, HashMap<String, serde_yaml::Value>>,
47    /// Dynamic plugin definitions to load at workflow startup
48    #[serde(default)]
49    pub plugins: Vec<PluginDef>,
50    /// Event subscriber configuration
51    pub events: Option<EventsConfig>,
52}
53
54/// Definition of a dynamic plugin to load from a shared library
55#[derive(Debug, Clone, Default, Deserialize)]
56pub struct PluginDef {
57    /// Name used to reference this plugin as a step type in the workflow YAML
58    pub name: String,
59    /// Path to the shared library file (.so / .dylib)
60    pub path: String,
61}
62
63/// Configuration for event subscribers
64#[derive(Debug, Clone, Default, Deserialize)]
65pub struct EventsConfig {
66    /// HTTP endpoint to POST events to (fire-and-forget)
67    pub webhook: Option<String>,
68    /// File path to append events as JSONL
69    pub file: Option<String>,
70}
71
72/// Named scope (sub-workflow)
73#[derive(Debug, Clone, Deserialize)]
74pub struct ScopeDef {
75    pub steps: Vec<StepDef>,
76    pub outputs: Option<String>,
77}
78
79/// Individual step definition
80#[derive(Debug, Clone, Deserialize)]
81pub struct StepDef {
82    pub name: String,
83    #[serde(rename = "type")]
84    pub step_type: StepType,
85
86    // cmd fields
87    pub run: Option<String>,
88
89    // agent/chat fields
90    pub prompt: Option<String>,
91
92    // gate fields
93    pub condition: Option<String>,
94    pub on_pass: Option<String>,
95    pub on_fail: Option<String>,
96    pub message: Option<String>,
97
98    // repeat/map/call fields
99    pub scope: Option<String>,
100    pub max_iterations: Option<usize>,
101    pub initial_value: Option<serde_yaml::Value>,
102
103    // map fields
104    pub items: Option<String>,
105    pub parallel: Option<usize>,
106
107    // parallel step fields (nested steps)
108    pub steps: Option<Vec<StepDef>>,
109
110    // per-step config override
111    #[serde(default)]
112    pub config: HashMap<String, serde_yaml::Value>,
113
114    // scope output
115    #[allow(dead_code)]
116    pub outputs: Option<String>,
117
118    // output parsing
119    pub output_type: Option<OutputType>,
120
121    // async execution flag (named async_exec to avoid Rust keyword conflict)
122    #[serde(default)]
123    pub async_exec: Option<bool>,
124}
125
126/// All supported step types in a workflow.
127///
128/// Each variant corresponds to a `type:` value in the YAML step definition.
129#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
130#[serde(rename_all = "snake_case")]
131pub enum StepType {
132    /// Execute a shell command via `/bin/sh -c`.
133    Cmd,
134    /// Invoke the Claude Code CLI and parse streaming JSON output.
135    Agent,
136    /// Call the Anthropic (or OpenAI-compatible) API directly.
137    Chat,
138    /// Evaluate a Tera boolean expression and branch control flow.
139    Gate,
140    /// Run a named scope repeatedly until break or max_iterations.
141    Repeat,
142    /// Run a named scope once per item in a comma-separated list.
143    Map,
144    /// Run nested steps concurrently.
145    Parallel,
146    /// Invoke a named scope once (no looping).
147    Call,
148    /// Render a prompt template file and store the result.
149    Template,
150    /// Evaluate an inline Rhai script and store the return value.
151    Script,
152}
153
154impl std::fmt::Display for StepType {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        match self {
157            StepType::Cmd => write!(f, "cmd"),
158            StepType::Agent => write!(f, "agent"),
159            StepType::Chat => write!(f, "chat"),
160            StepType::Gate => write!(f, "gate"),
161            StepType::Repeat => write!(f, "repeat"),
162            StepType::Map => write!(f, "map"),
163            StepType::Parallel => write!(f, "parallel"),
164            StepType::Call => write!(f, "call"),
165            StepType::Template => write!(f, "template"),
166            StepType::Script => write!(f, "script"),
167        }
168    }
169}