Workflows
SwissArmyHammer provides a powerful workflow system that allows you to define and execute complex multi-step processes using Mermaid state diagrams. This guide covers creating, running, and managing workflows.
Overview
Workflows in SwissArmyHammer are defined using Mermaid state diagrams in markdown files. Each workflow consists of states (actions) and transitions that control the flow of execution. Workflows can:
- Execute prompts or other workflows
- Make decisions based on outputs
- Run actions in parallel
- Handle errors gracefully
- Resume from failures
Creating Workflows
Workflows are stored in .swissarmyhammer/workflows/
directories and use the .md
file extension. Each workflow file consists of:
- YAML Front Matter - Metadata about the workflow
- Mermaid State Diagram - The workflow structure
- Actions Section - Mappings of states to their actions
Here’s a basic workflow structure:
---
name: my-workflow
title: My Example Workflow
description: A workflow that demonstrates basic functionality
category: user
tags:
- example
- automation
---
# My Example Workflow
This workflow processes data through multiple stages.
For a complete example, see: [Simple Workflow](../examples/workflows/simple-workflow.md)
## Actions
- Start: Execute prompt "setup" with input="${data}"
- Process: Execute prompt "main-task"
- Success: Log "Task completed successfully"
- Failure: Log error "Task failed: ${error}"
Workflow Components
Front Matter
The YAML front matter contains workflow metadata:
---
name: workflow-id # Unique identifier for the workflow
title: Workflow Title # Human-readable title
description: Description # What the workflow does
category: builtin # Category (builtin, user, etc.)
tags: # Tags for organization
- automation
- data-processing
---
States
States represent steps in your workflow. They are defined in the Mermaid diagram and their actions are specified in the Actions section:
- Execute a prompt:
Execute prompt "prompt-name" with var="value"
- Run another workflow:
Run workflow "workflow-name" with data="${input}"
- Set variables:
Set result="${output}"
- Log messages:
Log "Processing complete"
- Wait:
Wait 5 seconds
Actions Section
The Actions section maps state names to their actions using the format:
## Actions
- StateName: Action description
- AnotherState: Execute prompt "example" with param="value"
Transitions
Transitions control flow between states:
- Always: Unconditional transition
- OnSuccess: Transition when action succeeds
- OnFailure: Transition when action fails
- Conditional: Based on regex matching or CEL expressions
- Regex patterns:
"pattern"
or/regex/
- CEL expressions: Complex conditions like
var.startsWith('Hello')
oris_error == true
- Regex patterns:
Special States
[*]
: Start and end states- Fork (
<<fork>>
) and Join (<<join>>
): For parallel execution
Mermaid Syntax Guide
SwissArmyHammer uses standard Mermaid state diagram syntax. The diagram defines the workflow structure, while actions are defined separately in the Actions section:
Basic Flow
stateDiagram-v2
[*] --> StateA
StateA --> StateB
StateB --> [*]
With corresponding actions:
## Actions
- StateA: Log "Starting process"
- StateB: Execute prompt "process-data"
Conditional Branching
stateDiagram-v2
[*] --> Check
Check --> OptionA: "pattern_a"
Check --> OptionB: "pattern_b"
Check --> Default: Always
Choice State Detection
SwissArmyHammer automatically detects choice states based on their transition patterns. A state is identified as a choice state when it has:
- Multiple outgoing transitions
- At least one transition with a condition (regex pattern or CEL expression)
- Different transition types (e.g., conditional and “always” transitions)
This automatic detection ensures that branching logic works correctly without requiring explicit choice state declarations in the Mermaid diagram.
Parallel Execution
See: Parallel Workflow
Action Reference
Execute Prompt
Execute a SwissArmyHammer prompt:
Execute prompt "prompt-name" with var1="value1" var2="${variable}"
Run Workflow
Delegate to another workflow:
Run workflow "workflow-name" with input="${data}"
Set Variable
Store values for later use:
Set variable_name="value"
Set result="${output}"
Log Messages
Output information:
Log "Information message"
Log error "Error message"
Log warning "Warning message"
Wait
Pause execution:
Wait 5 seconds
Wait 1 minute
System Commands
Execute shell commands:
Execute command "ls -la"
Execute command "npm test"
Variables and Context
Workflows have access to:
- Input variables passed via
--var
- Template variables passed via
--set
(for liquid template rendering) - Variables set in previous states
- Output from executed prompts (
${output}
) - Error messages (
${error}
) - Workflow metadata (
${workflow_name}
,${run_id}
)
Variable Interpolation
Use ${variable_name}
syntax to reference workflow variables:
Execute prompt "analyze" with file="${input_file}"
Set result="Analysis of ${input_file}: ${output}"
Liquid Template Support
Workflows support Liquid template rendering in action strings when using the --set
parameter. This allows dynamic parameterization of workflows at runtime:
## Actions
- start: Log "Starting workflow for {{ user_name | default: 'Guest' }}"
- greet: Execute prompt "say-hello" with name="{{ name }}" language="{{ language | default: 'English' }}"
- process: Set message="{{ greeting_type }} for {{ user_name }}!"
- farewell: Log "Goodbye, {{ name }}!"
Run the workflow with template variables:
# Pass template variables with --set
swissarmyhammer flow run greeting --set name=Alice --set language=French
# Template variables with default values
swissarmyhammer flow run greeting --set name=Bob
# The language will default to 'English'
# Complex template variables
swissarmyhammer flow run data-processor --set user.name=Alice --set user.role=admin
Template Features in Workflows
You can use all Liquid template features in workflow action strings:
Filters:
- log_user: Log "Processing user: {{ username | upcase }}"
- set_path: Set output_file="/tmp/{{ filename | slugify }}.json"
Conditionals:
- notify: Log "{% if priority == 'high' %}🚨 URGENT: {% endif %}{{ message }}"
Default Values:
- configure: Set timeout="{{ timeout | default: '30' }}"
- log_mode: Log "Running in {{ mode | default: 'development' }} mode"
Complex Objects:
- process_user: Execute prompt "user-handler" with name="{{ user.name }}" role="{{ user.role }}"
Combining –var and –set
You can use both --var
(for workflow variables) and --set
(for template variables) together:
swissarmyhammer flow run my-workflow \
--var input_file=data.json \
--var output_dir=/tmp \
--set user_name=Alice \
--set environment=production
--var
variables are available as${variable}
in the workflow--set
variables are available as{{ variable }}
in liquid templates
Template Rendering Behavior
- Templates are rendered before action parsing
- If a template variable is not provided, the original template syntax is preserved
- Template rendering errors are logged as warnings but don’t stop workflow execution
- Use the
default
filter to provide fallback values for optional variables
Error Handling
Workflows provide robust error handling:
Retry Logic
Note: Retry logic is handled automatically by Claude’s infrastructure. SwissArmyHammer workflows do not need to implement application-level retry mechanisms as Claude will automatically retry failed requests according to its built-in retry policies. This eliminates the need for manual retry implementation in your workflow logic.
Try-Catch Pattern
stateDiagram-v2
[*] --> Try
Try --> Success: OnSuccess
Try --> Catch: OnFailure
Catch --> Recovery
Success --> [*]
Recovery --> [*]
## Actions
- Try: Execute prompt "risky-operation"
- Catch: Log error "Operation failed: ${error}"
- Recovery: Execute prompt "cleanup"
- Success: Log "Operation completed successfully"
Abort Error Handling
Workflows support immediate termination through abort errors. When a prompt action’s result begins with ABORT ERROR:
, the workflow immediately exits all the way back to the root workflow with an error.
How Abort Errors Work
- Detection: When a prompt returns a response starting with
ABORT ERROR:
, it triggers immediate termination - Propagation: The error bypasses all normal error handling (retries, compensation, transitions)
- Root Exit: In nested workflows, the abort error propagates through all parent workflows to the root
Example Usage
See: User Confirmation Workflow
## Actions
- UserConfirmation: Execute prompt "confirm-destructive-action"
- ProcessData: Execute prompt "process-user-data"
- Complete: Log "Processing completed"
If the confirm-destructive-action
prompt returns ABORT ERROR: User cancelled the operation
, the workflow immediately terminates without executing ProcessData or Complete states.
Use Cases
- User Cancellation: Allow users to cancel long-running operations
- Critical Failures: Immediately stop on unrecoverable errors
- Safety Checks: Abort when safety conditions are not met
Important Notes
- Abort errors only trigger when the response starts with
ABORT ERROR:
(case-sensitive) - The error message after
ABORT ERROR:
is propagated in the error - Abort errors cannot be caught or handled within the workflow
- In sub-workflows, abort errors bubble up to terminate the parent workflow
Best Practices
1. Keep States Focused
Each state should perform one clear action:
Good:
ValidateInput: Execute prompt "validate-json" with file="${input}"
Bad:
DoEverything: Execute prompt "validate-and-transform-and-save"
2. Use Meaningful State Names
State names should describe what happens:
Good: ValidateConfiguration, ProcessUserData, GenerateReport
Bad: Step1, Step2, DoStuff
3. Handle All Paths
Ensure all states have clear exit paths:
stateDiagram-v2
[*] --> Process
Process --> Success: OnSuccess
Process --> Failure: OnFailure
Success --> [*]
Failure --> Cleanup: Always
Cleanup --> [*]
4. Use Variables Effectively
Pass data between states using variables:
ExtractData: Execute prompt "parse-file" with file="${input_file}"
ProcessData: Execute prompt "transform" with data="${output}"
SaveResults: Execute prompt "save" with content="${output}" path="${output_file}"
5. Document Complex Logic
Add comments to explain complex workflows:
stateDiagram-v2
%% This workflow processes user uploads
%% It validates, transforms, and stores the data
[*] --> Validate
%% Validation ensures file format is correct
Validate --> Transform: OnSuccess
Complete Example
Here’s a complete workflow file showing all components including liquid template support:
---
name: data-processor
title: Data Processing Workflow
description: Validates and processes incoming data files
category: user
tags:
- data-processing
- validation
- automation
---
# Data Processing Workflow
This workflow validates incoming data files, transforms them to the required format,
and stores the results. It includes error handling and retry logic.
```mermaid
stateDiagram-v2
[*] --> Initialize
Initialize --> ValidateFormat
ValidateFormat --> Transform: OnSuccess
ValidateFormat --> LogError: OnFailure
Transform --> StoreData: OnSuccess
Transform --> RetryTransform: OnFailure
RetryTransform --> Transform
StoreData --> NotifyComplete
LogError --> NotifyError
NotifyComplete --> [*]
NotifyError --> [*]
Actions
- Initialize: Log “Starting {{ environment | default: ‘development’ }} data processing for file: ${input_file}”
- ValidateFormat: Execute prompt “validate-json-schema” with file=“${input_file}” schema=“{{ schema | default: ‘default-schema.json’ }}”
- Transform: Execute prompt “transform-data” with input=“${output}” format=“{{ format | default: ‘json’ }}”
- StoreData: Execute prompt “store-to-database” with data=“${output}” table=“{{ db_table | default: ‘processed_data’ }}”
- RetryTransform: Wait {{ retry_delay | default: ‘5’ }} seconds
- NotifyComplete: Log “Successfully processed ${input_file} in {{ environment }} environment”
- LogError: Log error “Validation failed for ${input_file}: ${error}”
- NotifyError: Execute prompt “send-notification” with message=“Processing failed: ${error}” channel=“{{ alert_channel | default: ‘errors’ }}”
### Running the Example
```bash
# Basic run with defaults
swissarmyhammer flow run data-processor --var input_file=data.json
# Production run with custom settings
swissarmyhammer flow run data-processor \
--var input_file=data.json \
--set environment=production \
--set schema=production-schema.json \
--set db_table=prod_data \
--set alert_channel=prod-alerts
# Development run with custom retry
swissarmyhammer flow run data-processor \
--var input_file=test.json \
--set environment=development \
--set retry_delay=10
Running Workflows
Execute workflows using the flow
command:
# Run a workflow
swissarmyhammer flow run workflow-name
# Pass variables
swissarmyhammer flow run workflow-name --var input_file=data.json --var mode=production
# Resume from failure
swissarmyhammer flow run workflow-name --resume <run_id>
Monitoring and Debugging
View Workflow Runs
# List recent runs
swissarmyhammer flow list
# Show run details
swissarmyhammer flow show <run_id>
Debug Output
Workflows create detailed logs in .swissarmyhammer/workflows/runs/<run_id>.jsonl
:
- State entries and exits
- Variable values
- Prompt outputs
- Error messages
- Timing information
Visualization
Generate workflow diagrams:
# Visualize workflow structure
swissarmyhammer flow visualize workflow-name
# Show run path
swissarmyhammer flow visualize workflow-name --run <run_id>
Advanced Features
Nested Workflows
Workflows can call other workflows, enabling modular design:
stateDiagram-v2
[*] --> Initialize
Initialize --> RunSubWorkflow
RunSubWorkflow --> ProcessResults: OnSuccess
ProcessResults --> [*]
## Actions
- Initialize: Log "Starting main workflow"
- RunSubWorkflow: Run workflow "data-processor" with input="${raw_data}"
- ProcessResults: Log "Processed ${output}"
Dynamic Workflow Selection
Choose workflows at runtime:
stateDiagram-v2
[*] --> DetermineType
DetermineType --> RunTypeA: "type:A"
DetermineType --> RunTypeB: "type:B"
RunTypeA --> [*]
RunTypeB --> [*]
## Actions
- DetermineType: Execute prompt "detect-type" with data="${input}"
- RunTypeA: Run workflow "process-type-a" with data="${input}"
- RunTypeB: Run workflow "process-type-b" with data="${input}"
CEL Expression Branching
Use CEL expressions for complex conditional logic:
stateDiagram-v2
[*] --> BranchDecision
BranchDecision --> ProcessNormal: example_var.startsWith('Hello')
BranchDecision --> ProcessError: is_error == true
BranchDecision --> ProcessSpecial: count > 10 && status == 'active'
BranchDecision --> ProcessDefault: always
ProcessNormal --> [*]
ProcessError --> [*]
ProcessSpecial --> [*]
ProcessDefault --> [*]
## Actions
- BranchDecision: Execute prompt "analyze-data" with input="${data}"
- ProcessNormal: Log "Processing normal flow for ${example_var}"
- ProcessError: Execute prompt "handle-error" with error="${error}"
- ProcessSpecial: Execute prompt "special-handler" with count="${count}"
- ProcessDefault: Log "No special conditions met"
The choice state (BranchDecision) is automatically detected and will evaluate each condition in order, taking the first matching transition.
Parallel Processing
Process multiple items concurrently:
stateDiagram-v2
[*] --> Split
Split --> fork1: Always
state fork1 <<fork>>
fork1 --> ProcessItem1
fork1 --> ProcessItem2
fork1 --> ProcessItem3
ProcessItem1 --> join1: Always
ProcessItem2 --> join1: Always
ProcessItem3 --> join1: Always
state join1 <<join>>
join1 --> Aggregate
Aggregate --> [*]
Troubleshooting
Common Issues
- Workflow not found: Ensure workflow is in
.swissarmyhammer/workflows/
- Variable undefined: Check variable names and initialization
- Infinite loops: Add proper exit conditions
- Prompt not found: Verify prompt paths and names
Validation
Always validate workflows before running:
swissarmyhammer validate
This checks:
- Mermaid syntax
- State connectivity
- Action syntax
- Variable usage
- Circular dependencies
Next Steps
- Explore Example Workflows for practical patterns
- Read about Workflow Patterns for common solutions
- Check the CLI Reference for all flow commands
- Learn about Testing Workflows