website/versioned_docs/version-1.0.0/workflows/formulas.md
Formulas are declarative workflow templates.
Formulas can be written in TOML (preferred) or JSON:
formula = "feature-workflow"
description = "Standard feature development workflow"
version = 1
type = "workflow"
[vars.feature_name]
description = "Name of the feature"
required = true
[[steps]]
id = "design"
title = "Design {{feature_name}}"
type = "human"
description = "Create design document"
[[steps]]
id = "implement"
title = "Implement {{feature_name}}"
needs = ["design"]
[[steps]]
id = "review"
title = "Code review"
needs = ["implement"]
type = "human"
[[steps]]
id = "merge"
title = "Merge to main"
needs = ["review"]
{
"formula": "feature-workflow",
"description": "Standard feature development workflow",
"version": 1,
"type": "workflow",
"vars": {
"feature_name": {
"description": "Name of the feature",
"required": true
}
},
"steps": [
{
"id": "design",
"title": "Design {{feature_name}}",
"type": "human"
},
{
"id": "implement",
"title": "Implement {{feature_name}}",
"needs": ["design"]
}
]
}
| Type | Description |
|---|---|
workflow | Standard step sequence |
expansion | Template for expansion operator |
aspect | Cross-cutting concerns |
convoy | Multi-agent coordination (parallel workers) |
Convoy formulas coordinate multiple agents working in parallel. Use them for code review with multiple reviewers, design review sessions, or any workflow requiring parallel human or agent coordination.
formula = "multi-reviewer"
description = "Parallel code review with multiple reviewers"
version = 1
type = "convoy"
[vars.reviewers]
description = "Comma-separated reviewer names"
required = true
[[steps]]
id = "review-setup"
title = "Prepare review artifacts"
[[steps]]
id = "collect"
title = "Collect reviews"
needs = ["review-setup"]
waits_for = "all-children"
Define variables with defaults and constraints:
[vars.version]
description = "Release version"
required = true
pattern = "^\\d+\\.\\d+\\.\\d+$"
[vars.environment]
description = "Target environment"
default = "staging"
enum = ["staging", "production"]
Use variables in steps:
[[steps]]
title = "Deploy {{version}} to {{environment}}"
| Type | Description |
|---|---|
task | Normal work step (default) |
human | Requires human action |
gate | Async coordination point |
[[steps]]
id = "step1"
title = "First step"
[[steps]]
id = "step2"
title = "Second step"
needs = ["step1"]
[[steps]]
id = "test-unit"
title = "Unit tests"
[[steps]]
id = "test-integration"
title = "Integration tests"
[[steps]]
id = "deploy"
title = "Deploy"
needs = ["test-unit", "test-integration"] # Waits for both
Steps can be made conditional based on a variable:
[vars.run_security_scan]
description = "Whether to run security scan"
default = "true"
[[steps]]
id = "security-scan"
title = "Run security scan"
condition = "{{run_security_scan}}"
[[steps]]
id = "deploy"
title = "Deploy to production"
condition = "{{environment}} == production"
Condition formats:
"{{var}}" - truthy (non-empty, non-false)"!{{var}}" - negated"{{var}} == value" - equality"{{var}} != value" - inequalityConditions are evaluated at cook/pour time. Steps that don't match are removed from the workflow.
Steps can iterate using the loop field:
[[steps]]
id = "retry"
title = "Attempt {{i}}"
[steps.loop]
count = 3
var = "i"
[[steps.loop.body]]
id = "try-{{i}}"
title = "Attempt {{i}}"
[[steps]]
id = "phases"
title = "Phase iteration"
[steps.loop]
range = "1..5"
var = "phase_num"
[[steps.loop.body]]
id = "phase-{{phase_num}}"
title = "Execute phase {{phase_num}}"
Range supports expressions: "1..2^{disks}", "{start}..{count}".
[steps.loop]
until = "step.status == 'complete'"
max = 10
var = "attempt"
[[steps.loop.body]]
id = "attempt-{{attempt}}"
title = "Attempt {{attempt}}"
The max field is required for until loops to prevent unbounded iteration.
Steps can trigger dynamic work when they complete using on_complete with for_each:
[[steps]]
id = "survey"
title = "Survey available workers"
[steps.on_complete]
for_each = "output.workers"
bond = "worker-arm"
parallel = true
[steps.on_complete.vars]
worker_name = "{item.name}"
rig = "{item.rig}"
This creates a new molecule from the worker-arm formula for each item in the output.workers array. Use sequential = true instead of parallel to run them one at a time.
Formulas can inherit from parent formulas using extends:
formula = "secure-release"
description = "Release with security audit"
version = 1
type = "workflow"
extends = ["release"]
# Inherits all vars and steps from "release"
# Add new steps or override existing ones by ID:
[[steps]]
id = "security-audit"
title = "Security audit"
needs = ["test"]
# Override parent step
[[steps]]
id = "publish"
title = "Publish release (with audit)"
needs = ["tag", "security-audit"]
Child formulas inherit all vars, steps, and compose rules from parents. Child definitions with the same ID override parent definitions.
bd cook compiles a formula into a resolved proto. Two modes:
Produces a proto with {{variable}} placeholders intact. Useful for modeling, estimation, and planning.
bd cook feature-workflow # Template with placeholders
bd cook feature-workflow --dry-run # Preview steps
Produces a fully-resolved proto with variables substituted. Requires all variables to have values.
bd cook feature-workflow --var feature_name=auth # Substitute vars
bd cook feature-workflow --mode=runtime --var name=x # Explicit runtime
For most workflows, prefer using bd pour or bd mol wisp directly - they cook the formula inline.
Add gates for async coordination:
[[steps]]
id = "approval"
title = "Manager approval"
type = "human"
[steps.gate]
type = "human"
approvers = ["manager"]
[[steps]]
id = "deploy"
title = "Deploy to production"
needs = ["approval"]
Gate types: human, timer, gh:run (GitHub Actions), gh:pr (pull request).
Apply transformations to matching steps:
formula = "security-scan"
type = "aspect"
[[advice]]
target = "*.deploy" # Match all deploy steps
[advice.before]
id = "security-scan-{step.id}"
title = "Security scan before {step.title}"
Aspects support before, after, and around advice. Apply aspects to a formula via compose.aspects:
[compose]
aspects = ["security-scan", "logging"]
Formulas are searched in order:
.beads/formulas/ (project-level)~/.beads/formulas/ (user-level)# List available formulas
bd formula list
# Cook (compile) a formula
bd cook <formula-name> [--var key=value]
# Pour formula into molecule (persistent)
bd mol pour <formula-name> --var key=value
# Create wisp from formula (ephemeral)
bd mol wisp <formula-name> --var key=value
# Preview what would be created
bd mol pour <formula-name> --dry-run
.beads/formulas/my-workflow.formula.tomlbd mol pour my-workflowformula = "release"
description = "Standard release workflow"
version = 1
[vars.version]
required = true
pattern = "^\\d+\\.\\d+\\.\\d+$"
[[steps]]
id = "bump-version"
title = "Bump version to {{version}}"
[[steps]]
id = "changelog"
title = "Update CHANGELOG"
needs = ["bump-version"]
[[steps]]
id = "test"
title = "Run full test suite"
needs = ["changelog"]
[[steps]]
id = "build"
title = "Build release artifacts"
needs = ["test"]
[[steps]]
id = "tag"
title = "Create git tag v{{version}}"
needs = ["build"]
[[steps]]
id = "publish"
title = "Publish release"
needs = ["tag"]
type = "human"