Skip to main content

zig_core/workflow/
model.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5/// A complete workflow definition parsed from a `.zug` file.
6///
7/// A workflow describes a DAG of agent steps with shared variables,
8/// conditional routing, and data flow between steps. It maps directly
9/// to zag orchestration commands at execution time.
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct Workflow {
12    /// Workflow metadata.
13    pub workflow: WorkflowMeta,
14
15    /// Reusable role definitions that can be referenced by steps.
16    /// Keys are role names; values define the role's system prompt.
17    #[serde(default)]
18    pub roles: HashMap<String, Role>,
19
20    /// Shared variables that flow between steps.
21    /// Keys are variable names; values define type, default, and description.
22    #[serde(default)]
23    pub vars: HashMap<String, Variable>,
24
25    /// Ordered list of workflow steps. Each step maps to a zag agent invocation.
26    #[serde(default, rename = "step")]
27    pub steps: Vec<Step>,
28}
29
30/// Workflow-level metadata.
31#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32pub struct WorkflowMeta {
33    /// Human-readable workflow name (used as filename if not overridden).
34    pub name: String,
35
36    /// Short description of what this workflow does.
37    #[serde(default)]
38    pub description: String,
39
40    /// Tags for discovery and filtering.
41    #[serde(default)]
42    pub tags: Vec<String>,
43
44    /// Workflow version string (e.g., "1.0.0").
45    #[serde(default)]
46    pub version: Option<String>,
47
48    /// Default zag provider for all steps (claude, codex, gemini, copilot, ollama).
49    /// Individual steps can override this with their own `provider` field.
50    #[serde(default)]
51    pub provider: Option<String>,
52
53    /// Default model name or size alias for all steps (small, medium, large, or specific name).
54    /// Individual steps can override this with their own `model` field.
55    #[serde(default)]
56    pub model: Option<String>,
57}
58
59/// A reusable role definition that can be referenced by steps.
60///
61/// Roles define system prompts that shape agent behavior. Each role can
62/// provide its prompt inline or load it from an external file, enabling
63/// maintainable workflows with many distinct personas.
64#[derive(Debug, Clone, Default, Serialize, Deserialize)]
65pub struct Role {
66    /// Inline system prompt for this role. Supports `${var}` references.
67    #[serde(default)]
68    pub system_prompt: Option<String>,
69
70    /// Path to a file containing the system prompt (relative to the .zug file).
71    /// Loaded at execution time. Supports `${var}` references in the file content.
72    #[serde(default)]
73    pub system_prompt_file: Option<String>,
74}
75
76/// A workflow variable — shared state between steps.
77///
78/// Variables can be referenced in step prompts via `${var_name}` and updated
79/// by agents through the zig MCP server during execution.
80#[derive(Debug, Clone, Default, Serialize, Deserialize)]
81pub struct Variable {
82    /// Variable type: "string", "number", "bool", or "json".
83    #[serde(rename = "type")]
84    pub var_type: VarType,
85
86    /// Default value (as a TOML value). If absent, the variable must be
87    /// provided at runtime or set by a preceding step.
88    #[serde(default)]
89    pub default: Option<toml::Value>,
90
91    /// Path to a file whose contents become the default value (relative to .zug file).
92    /// Mutually exclusive with `default`.
93    #[serde(default)]
94    pub default_file: Option<String>,
95
96    /// Human-readable description of this variable's purpose.
97    #[serde(default)]
98    pub description: String,
99
100    // --- Input binding ---
101    /// Bind this variable to an input source. Currently only `"prompt"` is
102    /// supported, which assigns the CLI user prompt to this variable.
103    #[serde(default)]
104    pub from: Option<String>,
105
106    // --- Constraints ---
107    /// If true, the variable must have a non-empty value before execution.
108    #[serde(default)]
109    pub required: bool,
110
111    /// Minimum string length (only valid for `type = "string"`).
112    #[serde(default)]
113    pub min_length: Option<u32>,
114
115    /// Maximum string length (only valid for `type = "string"`).
116    #[serde(default)]
117    pub max_length: Option<u32>,
118
119    /// Minimum numeric value (only valid for `type = "number"`).
120    #[serde(default)]
121    pub min: Option<f64>,
122
123    /// Maximum numeric value (only valid for `type = "number"`).
124    #[serde(default)]
125    pub max: Option<f64>,
126
127    /// Regex pattern the value must match (only valid for `type = "string"`).
128    #[serde(default)]
129    pub pattern: Option<String>,
130
131    /// Restrict value to one of these specific values.
132    #[serde(default)]
133    pub allowed_values: Option<Vec<toml::Value>>,
134}
135
136/// Supported variable types.
137#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "lowercase")]
139pub enum VarType {
140    #[default]
141    String,
142    Number,
143    Bool,
144    Json,
145}
146
147/// A single workflow step — one agent invocation.
148///
149/// Each step maps to a `zag spawn` (or `zag exec` for terminal steps).
150/// Steps form a DAG via `depends_on` and can conditionally execute based
151/// on workflow variable values.
152#[derive(Debug, Clone, Default, Serialize, Deserialize)]
153pub struct Step {
154    /// Unique step identifier (used in `depends_on` references).
155    pub name: String,
156
157    /// Prompt template sent to the agent. May contain `${var_name}` references
158    /// that are resolved against workflow variables before execution.
159    pub prompt: String,
160
161    /// Zag provider to use (claude, codex, gemini, copilot, ollama).
162    /// Falls back to the project/global zag default if not set.
163    #[serde(default)]
164    pub provider: Option<String>,
165
166    /// Model name or size alias (small, medium, large).
167    #[serde(default)]
168    pub model: Option<String>,
169
170    /// Steps that must complete before this step starts.
171    #[serde(default)]
172    pub depends_on: Vec<String>,
173
174    /// If true, dependency outputs are automatically injected into the prompt.
175    #[serde(default)]
176    pub inject_context: bool,
177
178    /// Condition expression that must evaluate to true for this step to run.
179    /// Uses a simple expression language: `var < 8`, `status == "done"`, etc.
180    /// If the condition is false, the step is skipped.
181    #[serde(default)]
182    pub condition: Option<String>,
183
184    /// Request structured JSON output from the agent.
185    #[serde(default)]
186    pub json: bool,
187
188    /// JSON schema to validate agent output against (implies `json = true`).
189    #[serde(default)]
190    pub json_schema: Option<String>,
191
192    /// Output format override: "text", "json", "json-pretty", "stream-json", "native-json".
193    /// When set, maps to `-o <FORMAT>` on zag and overrides the `json` bool field.
194    #[serde(default)]
195    pub output: Option<String>,
196
197    /// Map of variable names to save from this step's output.
198    /// Values are JSONPath-like selectors (e.g., `"$.score"`).
199    /// If the output is plain text, use `"$"` to capture the full output.
200    #[serde(default)]
201    pub saves: HashMap<String, String>,
202
203    /// Step timeout (e.g., "5m", "30s", "1h").
204    #[serde(default)]
205    pub timeout: Option<String>,
206
207    /// Tags applied to the spawned zag session.
208    #[serde(default)]
209    pub tags: Vec<String>,
210
211    /// Behavior on step failure: "fail" (default), "continue", or "retry".
212    #[serde(default)]
213    pub on_failure: Option<FailurePolicy>,
214
215    /// Maximum retry attempts when `on_failure = "retry"`.
216    #[serde(default)]
217    pub max_retries: Option<u32>,
218
219    /// Explicit next step to jump to after completion (enables loops).
220    /// Without this, execution follows the DAG order.
221    #[serde(default)]
222    pub next: Option<String>,
223
224    /// System prompt override for this step's agent.
225    /// Mutually exclusive with `role`.
226    #[serde(default)]
227    pub system_prompt: Option<String>,
228
229    /// Role name or `${var}` reference — resolved to a role from `[roles]` at runtime.
230    /// The role's system prompt is used as this step's system prompt.
231    /// Mutually exclusive with `system_prompt`.
232    #[serde(default)]
233    pub role: Option<String>,
234
235    /// Maximum number of agentic turns for this step.
236    #[serde(default)]
237    pub max_turns: Option<u32>,
238
239    // --- Observability ---
240    /// Human-readable description of this step's purpose.
241    #[serde(default)]
242    pub description: String,
243
244    // --- Execution environment ---
245    /// If true, spawn a long-lived interactive session (FIFO-based).
246    /// Enables Human-in-the-Loop and Inter-Agent Communication patterns.
247    #[serde(default)]
248    pub interactive: bool,
249
250    /// If true, auto-approve all agent actions (skip permission prompts).
251    #[serde(default)]
252    pub auto_approve: bool,
253
254    /// Working directory override for this step's agent.
255    #[serde(default)]
256    pub root: Option<String>,
257
258    /// Additional directories to include in the agent's scope.
259    #[serde(default)]
260    pub add_dirs: Vec<String>,
261
262    /// Per-step environment variables.
263    #[serde(default)]
264    pub env: HashMap<String, String>,
265
266    /// Files to attach to the agent prompt.
267    #[serde(default)]
268    pub files: Vec<String>,
269
270    // --- Context injection ---
271    /// Session IDs to inject as context (beyond depends_on).
272    /// Maps to `--context <SESSION_ID>` flags on zag.
273    #[serde(default)]
274    pub context: Vec<String>,
275
276    /// Path to a plan file to prepend as context.
277    /// Maps to `--plan <PATH>` on zag.
278    #[serde(default)]
279    pub plan: Option<String>,
280
281    /// Per-step MCP configuration (JSON string or file path, Claude only).
282    /// Maps to `--mcp-config <CONFIG>` on zag.
283    #[serde(default)]
284    pub mcp_config: Option<String>,
285
286    // --- Isolation ---
287    /// If true, run this step in an isolated git worktree.
288    #[serde(default)]
289    pub worktree: bool,
290
291    /// Docker sandbox name. If set, the step runs inside a sandbox.
292    #[serde(default)]
293    pub sandbox: Option<String>,
294
295    // --- Advanced orchestration ---
296    /// Race group name. Steps sharing a race_group run in parallel;
297    /// when the first completes, the rest are cancelled.
298    #[serde(default)]
299    pub race_group: Option<String>,
300
301    /// Model to use when retrying this step (only applies when
302    /// on_failure = "retry"). Enables escalation to a larger model.
303    #[serde(default)]
304    pub retry_model: Option<String>,
305
306    // --- Command step types ---
307    /// Zag command to invoke for this step. Default (None) uses `zag run`.
308    /// Other options: "review", "plan", "pipe", "collect", "summary".
309    #[serde(default)]
310    pub command: Option<StepCommand>,
311
312    /// Review uncommitted changes (only valid when `command = "review"`).
313    #[serde(default)]
314    pub uncommitted: bool,
315
316    /// Base branch for review diff (only valid when `command = "review"`).
317    #[serde(default)]
318    pub base: Option<String>,
319
320    /// Specific commit to review (only valid when `command = "review"`).
321    #[serde(default)]
322    pub commit: Option<String>,
323
324    /// Title for the review (only valid when `command = "review"`).
325    #[serde(default)]
326    pub title: Option<String>,
327
328    /// Output path for generated plan (only valid when `command = "plan"`).
329    #[serde(default)]
330    pub plan_output: Option<String>,
331
332    /// Additional instructions for plan generation (only valid when `command = "plan"`).
333    #[serde(default)]
334    pub instructions: Option<String>,
335}
336
337/// What to do when a step fails.
338#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
339#[serde(rename_all = "lowercase")]
340pub enum FailurePolicy {
341    /// Abort the workflow (default).
342    #[default]
343    Fail,
344    /// Skip this step and continue.
345    Continue,
346    /// Retry the step up to `max_retries` times.
347    Retry,
348}
349
350/// Zag command type for a step. When set, changes which zag subcommand
351/// is invoked instead of the default `zag run`.
352#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
353#[serde(rename_all = "lowercase")]
354pub enum StepCommand {
355    /// Code review: `zag review`.
356    Review,
357    /// Implementation plan generation: `zag plan`.
358    Plan,
359    /// Chain session results into new agent: `zag pipe`.
360    Pipe,
361    /// Gather results from multiple sessions: `zag collect`.
362    Collect,
363    /// Log-based summary/stats: `zag summary`.
364    Summary,
365}
366
367impl std::fmt::Display for VarType {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        match self {
370            VarType::String => write!(f, "string"),
371            VarType::Number => write!(f, "number"),
372            VarType::Bool => write!(f, "bool"),
373            VarType::Json => write!(f, "json"),
374        }
375    }
376}