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}