use std::path::PathBuf;
use anyhow::{bail, Context, Result};
#[derive(Debug, Clone, Copy, Default)]
pub enum TemplateType {
#[default]
Minimal,
Full,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum PatternType {
#[default]
Sequence,
Parallel,
Conditional,
Loop,
Delegate,
}
impl PatternType {
fn as_str(&self) -> &'static str {
match self {
PatternType::Sequence => "sequence",
PatternType::Parallel => "parallel",
PatternType::Conditional => "conditional",
PatternType::Loop => "loop",
PatternType::Delegate => "delegate",
}
}
}
pub fn execute(file: PathBuf, pattern: Option<String>, template: Option<String>) -> Result<()> {
if file.exists() {
bail!(
"File already exists: {}. Use a different path or remove the file first.",
file.display()
);
}
let pattern_type = match pattern.as_deref() {
Some("sequence") => PatternType::Sequence,
Some("parallel") => PatternType::Parallel,
Some("conditional") => PatternType::Conditional,
Some("loop") => PatternType::Loop,
Some("delegate") => PatternType::Delegate,
None => PatternType::default(),
Some(p) => bail!(
"Unknown pattern: {}. Valid: sequence, parallel, conditional, loop, delegate",
p
),
};
let template_type = match template.as_deref() {
Some("minimal") => TemplateType::Minimal,
Some("full") => TemplateType::Full,
None => TemplateType::default(),
Some(t) => bail!("Unknown template: {}. Valid: minimal, full", t),
};
let content = generate_template(&pattern_type, &template_type);
std::fs::write(&file, content)
.with_context(|| format!("Failed to write {}", file.display()))?;
println!("✅ Created: {}", file.display());
println!(" Pattern: {}", pattern_type.as_str());
println!(
" Template: {}",
match template_type {
TemplateType::Minimal => "minimal",
TemplateType::Full => "full",
}
);
Ok(())
}
fn generate_template(pattern: &PatternType, template: &TemplateType) -> String {
match template {
TemplateType::Minimal => generate_minimal_template(pattern),
TemplateType::Full => generate_full_template(pattern),
}
}
fn generate_minimal_template(pattern: &PatternType) -> String {
let pattern_name = pattern.as_str();
let pattern_yaml = match pattern {
PatternType::Sequence => {
"flow:\n type: sequence\n steps:\n - worker-a"
}
PatternType::Parallel => {
"flow:\n type: parallel\n branches:\n - worker-a\n - worker-b"
}
PatternType::Conditional => {
"flow:\n type: conditional\n condition: '{{input.should_run}}'\n then: worker-a\n else: worker-b"
}
PatternType::Loop => {
"flow:\n type: loop\n over: '{{input.items}}'\n do: worker-a"
}
PatternType::Delegate => {
"flow:\n type: delegate\n swarm: child.swarm.yaml"
}
};
format!(
"# SwarmFile - {pattern_name} pattern\n\
apiVersion: bzzz.dev/v1\n\
kind: swarm\n\
id: my-swarm\n\
workers:\n\
- name: worker-a\n\
spec: agents/worker-a.yaml\n\
{pattern_yaml}\n"
)
}
fn generate_full_template(pattern: &PatternType) -> String {
let pattern_name = pattern.as_str();
let pattern_yaml = match pattern {
PatternType::Sequence => {
"flow:\n type: sequence\n steps:\n - worker-a\n # Add more steps here"
}
PatternType::Parallel => {
"flow:\n type: parallel\n branches:\n - worker-a\n - worker-b\n fail_fast: false # Stop on first failure if true"
}
PatternType::Conditional => {
"flow:\n type: conditional\n condition: '{{input.should_run}}' # Expression evaluated at runtime\n then: worker-a\n else: worker-b # Optional fallback"
}
PatternType::Loop => {
"flow:\n type: loop\n over: '{{input.items}}' # Array from input\n do: worker-a\n max_iterations: 100 # Safety limit (0 = unlimited)"
}
PatternType::Delegate => {
"flow:\n type: delegate\n swarm: child.swarm.yaml\n input_mapping:\n query: '{{input.search}}'\n output_mapping:\n result: '{{steps.delegate.output.data}}'\n failure_inherit: true # Propagate failures to parent"
}
};
let header = "# SwarmFile - ".to_string()
+ pattern_name
+ " pattern (full template)\n\
#\n\
# This template includes all optional fields for reference.\n\
# Remove unused fields to keep your SwarmFile minimal.\n\
#\n\
# Usage:\n\
# bzzz run -f this.swarm.yaml\n\
# bzzz validate -f this.swarm.yaml\n\
# bzzz inspect -f this.swarm.yaml\n\
\n\
apiVersion: bzzz.dev/v1\n\
kind: swarm\n\
id: my-swarm\n\
\n\
# Workers define the Agents participating in this swarm\n\
workers:\n\
- name: worker-a\n\
spec: agents/worker-a.yaml # Local agent spec (mutually exclusive with a2a)\n\
# a2a: https://agent.example.com # Remote A2A agent\n\
runtime: local # Optional: override runtime (local, docker, http)\n\
input:\n\
key: '{{input.query}}' # Parameter substitution\n\
\n\
# Optional: Swarm-level runtime default\n\
# runtime: docker\n\
\n\
# Optional: Failure behavior\n\
# on_failure: fail_fast # Options: fail_fast, continue, ignore\n\
\n\
# Optional: Timeout in seconds\n\
# timeout: 60\n\
\n\
# Optional: Output behavior\n\
# output: last # Options: all, last, aggregate\n\
\n\
# Optional: Interface definition for composition\n\
# interface:\n\
# input:\n\
# schema:\n\
# type: object\n\
# properties:\n\
# query: { type: string }\n\
# required: [query]\n\
# required: true\n\
# output:\n\
# schema:\n\
# type: object\n\
# properties:\n\
# result: { type: string }\n\
\n\
# Optional: Output exposure mapping\n\
# expose:\n\
# - name: result\n\
# from: steps.worker-a.output.value\n\
\n";
header + pattern_yaml + "\n"
}