Parallel and Matrix Steps for Atmos Workflows
Atmos workflows can now run independent work concurrently with first-class parallel and matrix control steps. Add dependency-aware fan-out, readable grouped or live-prefixed output, and explicit failure behavior directly to your workflow YAML.
The Problem
Workflows are where teams encode the operational knowledge that should not live in someone's shell history: run the checks, build the thing, deploy the dependencies, then summarize what happened.
Until now, those steps were sequential. That was easy to reason about, but it meant a workflow with four independent checks took the sum of all four runtimes. The usual workaround was to drop into shell scripts, background jobs, wait, temp files, and hand-rolled log prefixes. That works until it doesn't:
- Output from concurrent commands interleaves into unreadable logs.
- Failure behavior is implicit and different in every script.
- Dependency relationships are hidden in shell control flow.
- Local workflows and CI matrices drift apart.
Infrastructure automation should not force you to choose between "simple but slow" and "fast but fragile."
What's New
Atmos now supports two new workflow control step types:
parallelruns sibling steps concurrently.matrixexpands literal axes and schedules the generated child steps.
Both support:
needsdependencies between sibling steps.max_concurrencyto bound parallelism.- Failure modes:
wait_all,fail_fast, andbest_effort. - Output modes:
grouped,prefixed, andnone. - Parent-owned summaries with success, failed, skipped, and canceled counts.
This is built into the workflow engine, so the orchestration rules are visible in the workflow file instead of buried in shell glue.
Parallel Checks
Run independent checks together, then run a dependent summary step only after both prerequisites succeed:
workflows:
checks:
steps:
- name: checks
type: parallel
max_concurrency: 4
fail:
mode: wait_all
output:
mode: grouped
order: completion
show_summary: true
prefix: "{{ .step.name }}"
steps:
- name: lint
type: shell
command: make lint
- name: test
type: shell
command: make test
- name: summarize
type: shell
needs: [lint, test]
command: ./scripts/summary.sh
The workflow is still declarative: summarize says what it needs, not how to poll for it. Atmos schedules everything else.
Matrix Fan-Out
Use matrix when the same step should run across combinations:
workflows:
test-matrix:
steps:
- name: test-matrix
type: matrix
max_concurrency: 3
output:
mode: grouped
order: definition
matrix:
os: [linux, darwin]
go: ["1.22", "1.23"]
steps:
- name: test
type: shell
command: make test OS={{ .matrix.os }} GO_VERSION={{ .matrix.go }}
That gives you CI-style fan-out without requiring the workflow to become a GitHub Actions-only construct. The same workflow can run locally, in CI, or inside a larger operational runbook.
