1use super::WorkflowTemplate;
11
12pub const MINIMAL_EXEC: &str = r##"# ═══════════════════════════════════════════════════════════════════
14# exec: — Run shell commands
15# ═══════════════════════════════════════════════════════════════════
16#
17# The simplest Nika verb. No API key, no provider — just your shell.
18#
19# Run: nika run workflows/01-exec.nika.yaml
20
21schema: "nika/workflow@0.12"
22workflow: exec-basics
23description: "Shell commands with exec:"
24
25tasks:
26 - id: hello
27 exec:
28 command: echo "Hello from Nika!"
29
30 - id: system_info
31 exec:
32 command: uname -a
33 timeout: 10
34
35 - id: count_files
36 depends_on: [hello]
37 exec:
38 command: ls -1 | wc -l
39 shell: true
40"##;
41
42pub const MINIMAL_FETCH: &str = r##"# ═══════════════════════════════════════════════════════════════════
44# fetch: — HTTP requests
45# ═══════════════════════════════════════════════════════════════════
46#
47# Make HTTP requests. No API key needed for public endpoints.
48#
49# Run: nika run workflows/02-fetch.nika.yaml
50
51schema: "nika/workflow@0.12"
52workflow: fetch-basics
53description: "HTTP requests with fetch:"
54
55tasks:
56 - id: get_ip
57 fetch:
58 url: "https://httpbin.org/ip"
59 method: GET
60
61 - id: post_data
62 fetch:
63 url: "https://httpbin.org/post"
64 method: POST
65 headers:
66 Content-Type: "application/json"
67 body: '{"message": "Hello from Nika"}'
68
69 - id: show_result
70 depends_on: [get_ip]
71 with:
72 ip_data: $get_ip
73 exec:
74 command: echo "Your IP data — {{with.ip_data}}"
75"##;
76
77pub const MINIMAL_INFER: &str = r##"# ═══════════════════════════════════════════════════════════════════
79# infer: — LLM generation
80# ═══════════════════════════════════════════════════════════════════
81#
82# Send prompts to an LLM. Requires a provider API key.
83#
84# Setup: nika provider set {{PROVIDER}}
85# Run: nika run workflows/03-infer.nika.yaml
86
87schema: "nika/workflow@0.12"
88workflow: infer-basics
89description: "LLM prompts with infer:"
90
91tasks:
92 - id: haiku
93 infer:
94 model: "{{MODEL}}"
95 prompt: "Write a haiku about open source software."
96 temperature: 0.8
97 max_tokens: 100
98
99 - id: explain
100 infer:
101 model: "{{MODEL}}"
102 system: "You are a concise technical writer."
103 prompt: "Explain what a DAG is in 2 sentences."
104 temperature: 0.3
105 max_tokens: 200
106
107 - id: combine
108 depends_on: [haiku, explain]
109 with:
110 poem: $haiku
111 definition: $explain
112 infer:
113 model: "{{MODEL}}"
114 prompt: |
115 Combine these into a short blog post intro:
116
117 HAIKU:
118 {{with.poem}}
119
120 DAG DEFINITION:
121 {{with.definition}}
122 temperature: 0.5
123 max_tokens: 400
124"##;
125
126pub const MINIMAL_INVOKE: &str = r##"# ═══════════════════════════════════════════════════════════════════
128# invoke: — MCP tool calls
129# ═══════════════════════════════════════════════════════════════════
130#
131# Call builtin tools or external MCP servers.
132# Builtin tools (nika:*) need no setup.
133#
134# Run: nika run workflows/04-invoke.nika.yaml
135
136schema: "nika/workflow@0.12"
137workflow: invoke-basics
138description: "Tool calls with invoke:"
139
140tasks:
141 - id: log_start
142 invoke:
143 tool: "nika:log"
144 params:
145 message: "Workflow started"
146 level: info
147
148 - id: emit_data
149 invoke:
150 tool: "nika:emit"
151 params:
152 key: "greeting"
153 value: "Hello from invoke!"
154
155 - id: assert_check
156 depends_on: [emit_data]
157 invoke:
158 tool: "nika:assert"
159 params:
160 condition: true
161 message: "Emit succeeded"
162"##;
163
164pub const MINIMAL_AGENT: &str = r##"# ═══════════════════════════════════════════════════════════════════
166# agent: — Multi-turn LLM with tools
167# ═══════════════════════════════════════════════════════════════════
168#
169# An autonomous agent that can use tools in a loop.
170# Requires a provider API key.
171#
172# Setup: nika provider set {{PROVIDER}}
173# Run: nika run workflows/05-agent.nika.yaml
174
175schema: "nika/workflow@0.12"
176workflow: agent-basics
177description: "Multi-turn agent with tools"
178
179tasks:
180 - id: scout
181 agent:
182 model: "{{MODEL}}"
183 system: |
184 You are a helpful file scout. List the files in the current
185 directory and describe what you find. Be concise.
186 prompt: "What files are in this project?"
187 tools:
188 - "nika:glob"
189 - "nika:read"
190 max_turns: 5
191 stop_condition: "answer_found"
192"##;
193
194pub fn get_minimal_workflows() -> Vec<WorkflowTemplate> {
196 vec![
197 WorkflowTemplate {
198 filename: "01-exec.nika.yaml",
199 tier_dir: "minimal",
200 content: MINIMAL_EXEC,
201 },
202 WorkflowTemplate {
203 filename: "02-fetch.nika.yaml",
204 tier_dir: "minimal",
205 content: MINIMAL_FETCH,
206 },
207 WorkflowTemplate {
208 filename: "03-infer.nika.yaml",
209 tier_dir: "minimal",
210 content: MINIMAL_INFER,
211 },
212 WorkflowTemplate {
213 filename: "04-invoke.nika.yaml",
214 tier_dir: "minimal",
215 content: MINIMAL_INVOKE,
216 },
217 WorkflowTemplate {
218 filename: "05-agent.nika.yaml",
219 tier_dir: "minimal",
220 content: MINIMAL_AGENT,
221 },
222 ]
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_minimal_workflow_count() {
231 let workflows = get_minimal_workflows();
232 assert_eq!(
233 workflows.len(),
234 5,
235 "Should have exactly 5 minimal workflows"
236 );
237 }
238
239 #[test]
240 fn test_minimal_filenames_unique() {
241 let workflows = get_minimal_workflows();
242 let mut names: Vec<&str> = workflows.iter().map(|w| w.filename).collect();
243 let len = names.len();
244 names.sort();
245 names.dedup();
246 assert_eq!(names.len(), len, "All filenames must be unique");
247 }
248
249 #[test]
250 fn test_minimal_all_have_schema() {
251 let workflows = get_minimal_workflows();
252 for w in &workflows {
253 assert!(
254 w.content.contains("schema: \"nika/workflow@0.12\""),
255 "Workflow {} must declare schema",
256 w.filename
257 );
258 }
259 }
260
261 #[test]
262 fn test_minimal_all_have_tasks() {
263 let workflows = get_minimal_workflows();
264 for w in &workflows {
265 assert!(
266 w.content.contains("tasks:"),
267 "Workflow {} must have tasks section",
268 w.filename
269 );
270 }
271 }
272
273 #[test]
274 fn test_minimal_all_nika_yaml_extension() {
275 let workflows = get_minimal_workflows();
276 for w in &workflows {
277 assert!(
278 w.filename.ends_with(".nika.yaml"),
279 "Workflow {} must end with .nika.yaml",
280 w.filename
281 );
282 }
283 }
284
285 #[test]
286 fn test_minimal_verbs_covered() {
287 let workflows = get_minimal_workflows();
288 let all_content: String = workflows.iter().map(|w| w.content).collect();
289 assert!(all_content.contains("exec:"), "Must cover exec: verb");
290 assert!(all_content.contains("fetch:"), "Must cover fetch: verb");
291 assert!(all_content.contains("infer:"), "Must cover infer: verb");
292 assert!(all_content.contains("invoke:"), "Must cover invoke: verb");
293 assert!(all_content.contains("agent:"), "Must cover agent: verb");
294 }
295
296 #[test]
297 fn test_minimal_valid_yaml() {
298 let workflows = get_minimal_workflows();
299 for w in &workflows {
300 if w.content.contains("{{PROVIDER}}") || w.content.contains("{{MODEL}}") {
302 continue;
303 }
304 let parsed: Result<serde_json::Value, _> = serde_saphyr::from_str(w.content);
305 assert!(
306 parsed.is_ok(),
307 "Workflow {} must be valid YAML: {:?}",
308 w.filename,
309 parsed.err()
310 );
311 }
312 }
313}