Skip to main content

zig_core/
create.rs

1use std::collections::HashMap;
2use std::path::Path;
3
4use serde::Serialize;
5use zag_agent::builder::AgentBuilder;
6use zag_agent::manpages;
7
8use crate::error::ZigError;
9use crate::prompt;
10
11/// Build the system prompt for the create agent by rendering the template
12/// with injected variables.
13fn build_system_prompt(zag_help: &str, zag_orch: &str, examples_reference: &str) -> String {
14    let vars = HashMap::from([
15        ("zwf_format_spec", prompt::templates::config_sidecar()),
16        ("zag_help", zag_help),
17        ("zag_orch", zag_orch),
18        ("examples_reference", examples_reference),
19    ]);
20    prompt::render(prompt::templates::create(), &vars)
21}
22
23/// Return the zag CLI reference text previously fetched via `zag --help-agent`.
24///
25/// Pulled directly from the embedded `zag-agent` manpages so no subprocess or
26/// installed `zag` binary is required.
27pub(crate) fn get_zag_help() -> String {
28    manpages::HELP_AGENT.to_string()
29}
30
31/// Return the zag orchestration reference, previously fetched via
32/// `zag man orchestration`. Embedded at compile time via `zag-agent`.
33pub(crate) fn get_zag_orch_reference() -> String {
34    manpages::ORCHESTRATION.to_string()
35}
36
37/// Return a short one-sentence guidance about a specific orchestration pattern
38/// to append to the initial user prompt when `--pattern` is passed. The full
39/// example files live at `~/.zig/examples/` (see [`prompt::examples_reference_block`])
40/// so this helper no longer embeds example TOML inline.
41fn pattern_guidance(pattern: &str) -> String {
42    match pattern {
43        "sequential" => {
44            "Use the Sequential Pipeline pattern: steps run in order, each feeding \
45            its output to the next via inject_context. Structure it as a linear \
46            depends_on chain."
47        }
48        "fan-out" => {
49            "Use the Fan-Out / Gather pattern: multiple independent steps run in \
50            parallel and a final step synthesizes their results. The gathering step \
51            should depends_on all parallel steps with inject_context = true."
52        }
53        "generator-critic" => {
54            "Use the Generator / Critic pattern: a generation step is followed by a \
55            critique step that scores the output, looping back via the next field \
56            until a quality threshold is met. Use saves to extract score and \
57            feedback variables, and a condition to control the loop."
58        }
59        "coordinator-dispatcher" => {
60            "Use the Coordinator / Dispatcher pattern: an initial classification \
61            step routes to specialized handler steps via condition expressions. \
62            Use json + saves to extract the classification, then condition guards \
63            on each handler step."
64        }
65        "hierarchical-decomposition" => {
66            "Use the Hierarchical Decomposition pattern: a planning step breaks the \
67            problem into sub-tasks, multiple worker steps handle each sub-task in \
68            parallel, and a synthesis step combines the results."
69        }
70        "human-in-the-loop" => {
71            "Use the Human-in-the-Loop pattern: incorporate human approval gates \
72            between automated steps. Use condition expressions on approval \
73            variables that are set by human review."
74        }
75        "inter-agent-communication" => {
76            "Use the Inter-Agent Communication pattern: agents collaborate via \
77            shared variables. Design the vars section carefully so agents can \
78            read and write to common state."
79        }
80        _ => "",
81    }
82    .to_string()
83}
84
85/// Prepared parameters for workflow creation — used by both the CLI
86/// (which spawns zag directly) and the API (which returns data to the frontend).
87#[derive(Debug, Clone, Serialize)]
88pub struct CreateParams {
89    pub system_prompt: String,
90    pub initial_prompt: String,
91    pub output_path: String,
92    pub session_name: String,
93    pub session_tag: String,
94}
95
96/// Prepare the prompts and configuration for workflow creation without
97/// launching zag. Returns structured data that can be used by the CLI
98/// to spawn zag or by the API to return to the frontend.
99pub fn prepare_create(
100    name: Option<&str>,
101    output: Option<&str>,
102    pattern: Option<&str>,
103) -> Result<CreateParams, ZigError> {
104    // Write example files to ~/.zig/examples/ so the agent can inspect them.
105    if let Err(e) = prompt::write_examples_to_global_dir() {
106        eprintln!("Warning: could not write example files: {e}");
107    }
108
109    let zag_help = get_zag_help();
110    let zag_orch = get_zag_orch_reference();
111    let examples_reference = prompt::examples_reference_block();
112    let system_prompt = build_system_prompt(&zag_help, &zag_orch, &examples_reference);
113
114    let output_path = if let Some(o) = output {
115        o.to_string()
116    } else {
117        let global_dir = crate::paths::ensure_global_workflows_dir()?;
118        let filename = name
119            .map(|n| format!("{n}.zwf"))
120            .unwrap_or_else(|| "workflow.zwf".to_string());
121        crate::paths::collapse_home(&global_dir.join(&filename).to_string_lossy())
122    };
123
124    let mut initial_prompt = if let Some(n) = name {
125        format!(
126            "I want to create a workflow called \"{n}\". \
127             The output will be saved to {output_path}. \
128             Please help me design it — start by asking what process I want to automate."
129        )
130    } else {
131        format!(
132            "I want to create a new workflow. \
133             The output will be saved to {output_path}. \
134             Please help me design it — start by asking what process I want to automate."
135        )
136    };
137
138    if let Some(p) = pattern {
139        let guidance = pattern_guidance(p);
140        if !guidance.is_empty() {
141            initial_prompt.push(' ');
142            initial_prompt.push_str(&guidance);
143        }
144    }
145
146    Ok(CreateParams {
147        system_prompt,
148        initial_prompt,
149        output_path,
150        session_name: "zig-create".to_string(),
151        session_tag: "zig-workflow-creation".to_string(),
152    })
153}
154
155/// Launch an interactive zag session for workflow creation.
156///
157/// The agent is given full context about zag and the .zwf format, and guides
158/// the user through designing their workflow. Driven through
159/// [`AgentBuilder::run`], so no external `zag` binary is required.
160pub async fn run_create(
161    name: Option<&str>,
162    output: Option<&str>,
163    pattern: Option<&str>,
164) -> Result<(), ZigError> {
165    let params = prepare_create(name, output, pattern)?;
166
167    AgentBuilder::new()
168        .system_prompt(&params.system_prompt)
169        .name(&params.session_name)
170        .tag(&params.session_tag)
171        .run(Some(&params.initial_prompt))
172        .await
173        .map_err(|e| ZigError::Zag(format!("failed to run agent: {e}")))?;
174
175    // Validate the output if it was created
176    if Path::new(&params.output_path).exists() {
177        match crate::workflow::parser::parse_file(Path::new(&params.output_path)) {
178            Ok(workflow) => {
179                if let Err(errors) = crate::workflow::validate::validate(&workflow) {
180                    eprintln!("Warning: generated workflow has validation issues:");
181                    for e in &errors {
182                        eprintln!("  - {e}");
183                    }
184                }
185            }
186            Err(e) => {
187                eprintln!("Warning: could not parse generated file: {e}");
188            }
189        }
190    }
191
192    Ok(())
193}
194
195#[cfg(test)]
196#[path = "create_tests.rs"]
197mod tests;