Workflow Composition¶
Prodigy enables building complex workflows from reusable components through imports, inheritance, templates, and parameters.
Overview¶
Workflow composition features:
- Imports: Include external workflow definitions
- Inheritance: Extend base workflows with extends
- Templates: Reusable workflow templates with parameters
- Parameters: Type-safe workflow parameterization
- Sub-workflows: Nested workflow execution with result passing
graph TD
Base[Base Workflow<br/>base-ci.yml] --> Child[Child Workflow<br/>specialized-ci.yml]
Import1[Common Steps<br/>common/ci-steps.yml] --> Main[Main Workflow<br/>main.yml]
Import2[Deploy Steps<br/>common/deploy.yml] --> Main
Template[Template<br/>rust-ci-template] --> Instance1[Instance 1<br/>project-a-ci]
Template --> Instance2[Instance 2<br/>project-b-ci]
Main --> Sub1[Sub-workflow<br/>build]
Main --> Sub2[Sub-workflow<br/>deploy]
style Base fill:#e1f5ff
style Template fill:#fff3e0
style Main fill:#f3e5f5
style Sub1 fill:#e8f5e9
style Sub2 fill:#e8f5e9
Figure: Workflow composition architecture showing imports, inheritance, templates, and sub-workflows.
Imports¶
Import workflow definitions from external files:
Best Practice
Use imports to share common steps across multiple workflows. This reduces duplication and ensures consistency across your CI/CD pipelines.
# main-workflow.yml
name: main-workflow
imports:
- path: "common/ci-steps.yml" # (1)!
alias: ci # (2)!
- path: "common/deploy-steps.yml"
alias: deploy
- shell: "cargo build"
- workflow: ci.test-suite # (3)!
- workflow: deploy.to-staging # (4)!
1. Path relative to workflow file location
2. Alias for referencing imported workflows
3. Execute imported workflow using alias
4. Access nested workflows with dot notation
Import Syntax¶
imports:
- path: "relative/path/to/workflow.yml"
alias: workflow_name # Optional alias for referencing
Import Caching¶
Imported workflows are cached in memory to avoid reloading the same file multiple times during composition. This improves performance when multiple workflows reference the same imports.
# Source: src/cook/workflow/composition/composer.rs:661-698
# Both workflows below share the same cached import
name: workflow-1
imports:
- path: "common/steps.yml"
alias: common
---
name: workflow-2
imports:
- path: "common/steps.yml" # Loaded from cache
alias: common
Inheritance¶
Extend base workflows to customize behavior:
# base-workflow.yml
name: base-ci
env:
RUST_BACKTRACE: "1"
- shell: "cargo build"
- shell: "cargo test"
---
# specialized-workflow.yml
name: specialized-ci
extends: "base-workflow.yml"
env:
EXTRA_FLAG: "true"
# Adds additional steps to base workflow
- shell: "cargo clippy"
- shell: "cargo doc"
Override Behavior¶
- Environment variables are merged (child overrides parent)
- Steps from parent executed first
- Child can override specific fields
Base Workflow Resolution¶
When using extends, base workflows are searched in priority order:
# Source: src/cook/workflow/composition/composer.rs:625-642
# Search order:
# 1. bases/{name}.yml
# 2. templates/{name}.yml
# 3. workflows/{name}.yml
# 4. {name}.yml (current directory)
name: my-workflow
extends: "common-steps" # Searches in order above
This allows organizing base workflows in dedicated directories while maintaining backward compatibility with workflows in the project root.
flowchart TD
Start[extends: common-steps] --> Check1{bases/<br/>common-steps.yml?}
Check1 -->|Found| Load1[Load Base Workflow]
Check1 -->|Not Found| Check2{templates/<br/>common-steps.yml?}
Check2 -->|Found| Load2[Load Base Workflow]
Check2 -->|Not Found| Check3{workflows/<br/>common-steps.yml?}
Check3 -->|Found| Load3[Load Base Workflow]
Check3 -->|Not Found| Check4{common-steps.yml<br/>current dir?}
Check4 -->|Found| Load4[Load Base Workflow]
Check4 -->|Not Found| Error[Error: Base Not Found]
Load1 --> Merge[Merge with Child]
Load2 --> Merge
Load3 --> Merge
Load4 --> Merge
Merge --> Result[Composed Workflow]
style Check1 fill:#e1f5ff
style Check2 fill:#e1f5ff
style Check3 fill:#e1f5ff
style Check4 fill:#e1f5ff
style Error fill:#ffebee
style Result fill:#e8f5e9
Figure: Base workflow resolution flow showing priority search order.
Templates¶
Create reusable workflow templates:
# Source: workflows/example-template.yml
# template: rust-ci-template
name: rust-ci
description: "Standard Rust CI workflow"
parameters:
target:
type: string
required: true
description: "Build target"
coverage:
type: boolean
default: false
description: "Run coverage analysis"
- shell: "cargo build --target ${target}"
- shell: "cargo test --target ${target}"
- shell: "cargo tarpaulin"
when: "${coverage} == true"
Template Detection
Workflow files using composition features (template, imports, extends, workflows, or parameters keywords) are automatically detected by Prodigy during workflow parsing via the is_composable_workflow() function and composed before execution.
Source: src/cook/workflow/composer_integration.rs:44-50
Using Templates¶
name: my-project-ci
template: "rust-ci-template"
parameters:
target: "x86_64-unknown-linux-gnu"
coverage: true
Template Storage¶
Templates are searched in priority order:
- Global (
~/.prodigy/templates/): Shared across all repositories - Project-local (
.prodigy/templates/): Repository-specific templates - Legacy (
templates/): Older project-local templates
Automatic Directory Creation
Template directories are automatically created by the template registry if they don't exist, so you can start using templates immediately.
Source: src/cook/workflow/composer_integration.rs:93-136
Parameters¶
Define workflow parameters with type checking:
parameters:
environment:
type: string
required: true
description: "Deployment environment"
validation: "environment in ['dev', 'staging', 'prod']"
replicas:
type: number
default: 3
description: "Number of replicas"
features:
type: array
default: []
description: "Feature flags to enable"
config:
type: object
required: false
description: "Additional configuration"
Parameter Types¶
- string: Text value
- number: Numeric value (integer or float)
- boolean: true/false
- array: List of values
- object: Nested key-value pairs
Parameter Validation¶
Validation Expressions
Parameter validation uses boolean expressions. If validation fails, the workflow will not execute. Always test validation rules with expected inputs.
parameters:
port:
type: number
validation: "port >= 1024 && port <= 65535" # (1)!
environment:
type: string
validation: "environment in ['dev', 'staging', 'prod']" # (2)!
1. Numeric range validation for port numbers
2. String enum validation for allowed environments
Using Parameters¶
Parameters can be used in commands, environment variables, and merge workflows:
# Source: src/cook/workflow/composition/composer.rs:438-453
parameters:
environment:
type: string
required: true
api_key:
type: string
required: true
# In commands
- shell: "deploy.sh --env ${environment}"
- shell: "kubectl apply -f k8s/${environment}/"
# In environment variables
env:
DEPLOY_ENV: "${environment}"
API_KEY: "${api_key}"
# In merge workflows
merge:
commands:
- shell: "git merge origin/${environment}-branch"
- claude: "/validate --env ${environment}"
Sub-Workflows¶
Execute workflows within workflows:
Modular Execution
Sub-workflows provide modular execution with independent contexts. Use them to break complex workflows into manageable, reusable pieces.
name: main-workflow
sub_workflows:
build:
name: build-subworkflow
- shell: "cargo build --release"
- shell: "cargo test --release"
deploy:
name: deploy-subworkflow
parameters:
environment: string # (1)!
- shell: "kubectl apply -f k8s/${environment}/"
# Execute sub-workflows
- workflow: build # (2)!
- workflow: deploy # (3)!
parameters:
environment: "staging"
# Access sub-workflow results
- shell: "echo Build completed: ${build.success}" # (4)!
1. Sub-workflow parameters define expected inputs
2. Execute sub-workflow without parameters
3. Pass parameters to parameterized sub-workflow
4. Access sub-workflow results in parent workflow
Sub-Workflow Features¶
- Independent execution contexts
- Parameter passing between workflows
- Result capture and access
- Conditional execution
Implementation Status
Sub-workflow execution is currently in development. The data structures and configuration parsing are complete, but the execution integration is still being finalized.
Source: src/cook/workflow/composition/sub_workflow.rs
Template Registry¶
Manage reusable templates centrally:
# Source: src/cli/args.rs:831-851, src/cli/router.rs:210-233
# Register template
prodigy template register ./templates/rust-ci.yml --name rust-ci
# List available templates
prodigy template list
Template Usage
Templates are used by referencing them in workflow YAML files with the template: field, not via CLI flags. CLI-based parameter passing to templates is planned for a future release.
Registry Structure¶
~/.prodigy/template-registry/
├── rust-ci/
│ ├── template.yml
│ └── metadata.json
├── python-ci/
│ └── template.yml
└── deploy/
└── template.yml
Template Metadata¶
Templates can include metadata:
name: rust-ci-template
version: "1.0.0"
description: "Standard Rust CI workflow with testing and linting"
author: "team@example.com"
tags: ["rust", "ci", "testing"]
parameters:
# ... parameter definitions
Composition in Action¶
Here's how the composition features work together in real workflows:
sequenceDiagram
participant User
participant Composer
participant Registry
participant Executor
User->>Composer: Load workflow.yml
Composer->>Registry: Resolve imports
Registry-->>Composer: Imported workflows
Composer->>Registry: Resolve base (extends)
Registry-->>Composer: Base workflow
Composer->>Composer: Merge base + child
Composer->>Composer: Interpolate parameters
Composer->>Composer: Compose sub-workflows
Composer-->>Executor: Composed workflow
Executor->>Executor: Execute steps
Executor->>Executor: Execute sub-workflows
Executor-->>User: Results
Note over Composer,Registry: Template caching<br/>optimization
Note over Executor: Sub-workflow<br/>isolation
Figure: Workflow composition execution sequence showing resolution, merging, and execution phases.
Examples¶
Modular CI Pipeline¶
# .prodigy/templates/common-ci.yml
name: common-ci-steps
- shell: "git fetch origin"
- shell: "git diff --name-only origin/main"
capture_output: changed_files
---
# rust-ci.yml
name: rust-project-ci
extends: ".prodigy/templates/common-ci.yml"
imports:
- path: ".prodigy/templates/rust-lint.yml"
alias: lint
- shell: "cargo build"
- workflow: lint.clippy
- shell: "cargo test"
Parameterized Deployment¶
name: deploy-workflow
parameters:
environment:
type: string
required: true
validation: "environment in ['dev', 'staging', 'prod']"
image_tag:
type: string
required: true
description: "Docker image tag to deploy"
replicas:
type: number
default: 3
validation: "replicas >= 1 && replicas <= 20"
env:
DEPLOY_ENV: "${environment}"
IMAGE_TAG: "${image_tag}"
REPLICAS: "${replicas}"
- shell: "kubectl set image deployment/app app=${IMAGE_TAG}"
- shell: "kubectl scale deployment/app --replicas=${REPLICAS}"
- shell: "kubectl rollout status deployment/app"
Template with Sub-Workflows¶
name: comprehensive-ci
description: "Full CI/CD pipeline with build, test, and deploy"
parameters:
deploy_enabled:
type: boolean
default: false
sub_workflows:
build:
- shell: "cargo build --release"
- shell: "docker build -t app:latest ."
test:
- shell: "cargo test"
- shell: "cargo clippy"
deploy:
parameters:
environment: string
- shell: "kubectl apply -f k8s/${environment}/"
# Main workflow
- workflow: build
- workflow: test
- workflow: deploy
when: "${deploy_enabled} == true"
parameters:
environment: "staging"