Parallel Execution
The parallel construct runs multiple steps at the same time. The flow continues only after all parallel branches have finished — whether they succeeded, failed, or were skipped.
Basic usage
Section titled “Basic usage”Wrap a list of participant references in a parallel block:
flow: - parallel: - lint - test - build - reportlint, test, and build start simultaneously. report only runs after all three complete.
Inline participants in parallel
Section titled “Inline participants in parallel”Each branch in a parallel block follows the same syntax as any other flow step. You can define participants inline — named or anonymous — directly inside the block:
flow: - parallel: - as: lint type: exec run: npm run lint timeout: 30s
- as: test type: exec run: npm test timeout: 2m
- as: typecheck type: exec run: npm run typecheck timeout: 30s
- reportAccessing outputs after parallel
Section titled “Accessing outputs after parallel”Named step outputs
Section titled “Named step outputs”After a parallel block completes, every named branch output is accessible by name — exactly like any sequential step:
flow: - parallel: - tests - lint
- if: condition: tests.status == "success" && lint.status == "success" then: - deploy else: - notifyFailureEach parallel branch writes to its own namespace. tests.output, lint.output, tests.status, and lint.status are all available downstream.
The I/O chain output
Section titled “The I/O chain output”The implicit I/O chain (the output automatically passed to the next sequential step) after a parallel block is an array containing the outputs of all branches in declaration order:
flow: - parallel: - stepA # index 0 in the output array - stepB # index 1 in the output array - stepC # index 2 in the output array
- as: aggregator type: exec run: ./aggregate.sh # receives [ stepA.output, stepB.output, stepC.output ] as inputAnonymous participants in parallel
Section titled “Anonymous participants in parallel”Anonymous branches (without as) are included positionally in the chain output array, but cannot be referenced by name in subsequent CEL expressions:
flow: - parallel: - as: namedBranch type: exec run: echo "named"
- type: exec # anonymous — output accessible only via the array run: echo "anonymous"
- as: next type: exec run: ./process.sh # input[0] is namedBranch output, input[1] is the anonymous outputError handling in parallel steps
Section titled “Error handling in parallel steps”Each branch has independent error handling. Configure onError per participant:
participants: lint: type: exec run: npm run lint onError: skip
test: type: exec run: npm test onError: fail
audit: type: exec run: npm audit onError: skip
flow: - parallel: - lint - test - audit- If
testfails → the workflow stops immediately (onError: fail). - If
lintorauditfail → those branches are markedskippedand the others continue running.
You can also override onError at the flow level for a specific invocation:
flow: - parallel: - lint: onError: skip # overrides the participant-level setting for this invocation - test - auditSub-workflows in parallel
Section titled “Sub-workflows in parallel”parallel branches can call sub-workflows, enabling large concurrent workloads to be encapsulated in separate files:
flow: - parallel: - as: lintCheck type: workflow path: ./lint.flow.yaml
- as: securityScan type: workflow path: ./security.flow.yaml
- as: buildArtifact type: workflow path: ./build.flow.yaml
- deploy: when: lintCheck.output.passed == true && securityScan.output.clean == trueEach sub-workflow runs in its own isolated execution.context and returns its output when complete.
Parallel inside other constructs
Section titled “Parallel inside other constructs”parallel can appear inside loops, conditionals, and other flow constructs.
Inside a loop
Section titled “Inside a loop”flow: - loop: max: 3 steps: - parallel: - fetchA - fetchB - mergeOn each iteration, fetchA and fetchB run concurrently, then merge runs after both complete.
Inside a conditional branch
Section titled “Inside a conditional branch”flow: - runTests - if: condition: runTests.output.passed == true then: - parallel: - buildFrontend - buildBackend - deploy else: - notifyFailureComplete example
Section titled “Complete example”A CI pipeline that runs quality checks in parallel, then deploys only if all checks pass:
id: ci-pipelinename: CI Pipelineversion: "1"
defaults: timeout: 10m
inputs: branch: type: string default: "main"
participants: lint: type: exec run: npm run lint timeout: 2m onError: skip
test: type: exec run: npm test timeout: 5m onError: fail
typecheck: type: exec run: npm run typecheck timeout: 2m onError: skip
audit: type: exec run: npm audit --audit-level=high timeout: 1m onError: skip
build: type: exec run: npm run build timeout: 5m onError: fail
deploy: type: exec run: ./deploy.sh timeout: 10m onError: fail
notifySuccess: type: http url: https://hooks.example.com/success method: POST onError: skip
notifyFailure: type: http url: https://hooks.example.com/failure method: POST onError: skip
flow: - parallel: - lint - test - typecheck - audit
- build: when: test.status == "success"
- deploy: when: build.status == "success"
- if: condition: deploy.status == "success" then: - notifySuccess else: - notifyFailure
output: lintStatus: lint.status testStatus: test.status buildStatus: build.status deployStatus: deploy.statusWalk-through:
- All four quality checks run concurrently.
testwill stop the workflow if it fails; the others skip on failure. buildruns only iftestpassed.deployruns only ifbuildsucceeded.- The final
ifsends the appropriate notification.