Skip to content

sub-flow

sub-flow - execute another workflow inline

state-name:
tool: sub-flow
arguments:
workflow: <workflow-name>
variables: # optional
<var>: <value>

The sub-flow tool executes another workflow within the context of the current workflow. The sub-flow runs to completion before the parent continues, sharing the same instance directory but with namespaced variables.

Sub-flows enable modular workflow design by extracting reusable sequences into separate workflow files.

ArgumentRequiredDefaultDescription
workflowYes-Name or path of workflow to execute
variablesNo-Variables to pass to sub-flow
# By name (searches .flowstate/ directories)
workflow: helper-workflow
# Relative to parent workflow
workflow: ./helpers/processor.yaml
# From project root
workflow: @/workflows/shared/validator.yaml
CodeMeaning
0Sub-flow completed successfully
Non-zeroSub-flow failed (propagated exit code)
-1Sub-flow paused

The sub-flow’s last state output can be captured:

call-helper:
tool: sub-flow
arguments:
workflow: calculator
variables:
operation: add
a: "5"
b: "3"
output: var(result)
main-workflow:
tool: sub-flow
arguments:
workflow: setup-environment
next: continue-work
process-file:
tool: sub-flow
arguments:
workflow: file-processor
variables:
input_path: "{{ source_file }}"
output_format: json
validate: "true"
next: use-processed-file

The sub-flow workflow (file-processor/index.yaml):

name: file-processor
start-at: validate-input
variables:
input_path: ""
output_format: text
validate: "false"
states:
validate-input:
tool: bash
arguments:
command: '[ -f "{{ input_path }}" ] || exit 1'
on-error:
_: error-missing-file
next: process
process:
tool: bash
arguments:
command: './process.sh "{{ input_path }}" --format {{ output_format }}'
output: var(result)

Variables set by the sub-flow are namespaced:

run-helper:
tool: sub-flow
arguments:
workflow: data-fetcher
next: use-data
use-data:
tool: bash
arguments:
command: 'echo "Fetched: {{ data-fetcher::fetched_count }} records"'
call-risky-subflow:
tool: sub-flow
arguments:
workflow: external-api-workflow
on-error:
1: handle-api-error
_: handle-unknown-error
next: success-path

Sub-flows can call other sub-flows:

main.yaml
start-at: orchestrate
states:
orchestrate:
tool: sub-flow
arguments:
workflow: level-1
# level-1.yaml
states:
delegate:
tool: sub-flow
arguments:
workflow: level-2

State paths track the full nesting: orchestrate/delegate/inner-state

calculate:
tool: sub-flow
arguments:
workflow: calculator
variables:
x: "10"
y: "20"
output: var(sum)
next: show-result
show-result:
tool: bash
arguments:
command: 'echo "Result: {{ sum }}"'

Variables passed via variables override the sub-flow’s defaults:

# Parent passes environment
call-deploy:
tool: sub-flow
arguments:
workflow: deploy
variables:
environment: production # Overrides deploy's default

Variables set in the sub-flow are namespaced:

# Sub-flow sets: result = "success"
# Parent accesses: {{ deploy::result }}

Sub-flows cannot directly access parent variables unless explicitly passed:

# Parent has: user_id = "123"
# Sub-flow cannot access {{ user_id }} directly
# Must pass it: variables: { user_id: "{{ user_id }}" }

Sub-flows share the parent’s instance directory:

  • Same ./ path resolution
  • Output files are visible to both
  • Context is preserved across pause/resume

If a sub-flow pauses, the entire workflow pauses. The state is tracked as parent-state/subflow-state, enabling correct resume.

  • parallel - For concurrent sub-flows
  • pause - For sub-flow interruption