Conditional & Iterative Tasks¶
Most flows are static dependency graphs. Sometimes, though, you need a step
that branches on a value or repeats until a condition is met – “run this body
only if the input matches,” “retry until it succeeds,” “refine for up to N
rounds.” A task expresses this with a control: block.
control: turns a compound task into one of five constructs selected by
type::
if– run one branch or another based on a condition.match– pick the first matching case (a multi-way branch).while– repeat while a pre-condition holds (0 or more times).do-while– repeat until a post-condition holds (1 or more times).repeat– repeat a fixed number of times, with optional early exit.
control: is mutually exclusive with strategy: (a task uses one or the
other). It is independent of iff: – iff: gates whether the task runs at
all, while control: shapes how its body runs once it does.
Conditions are ${{ }} expressions and may reference the task’s input
(${{ in.<field> }}), loop state (${{ state.<field> }}), and the
auto-injected iteration counters ${{ _iter }} / ${{ _max_iter }}.
if¶
if evaluates cond once. If it is true, the task’s body: runs; if it
is false, the else: branch (declared inside control:) runs, or the
task passes its input through if there is no else:.
tasks:
- name: check_value
run: echo '{"value":5}'
- root: conditional_task
needs: [check_value]
control:
type: if
cond: '${{ in.value == 5 }}'
else:
- name: when_not_five
run: echo "Value is not 5"
body:
- name: when_five
run: echo "Value is 5"
The “then” branch is the task-level body:; the “else” branch is
control.else:.
match¶
match evaluates each entry in cases in order and runs the body: of
the first case whose when: condition is true. A case marked
default: true runs when no earlier case matches. If nothing matches and
there is no default, the task passes through.
- root: route_task
control:
type: match
cases:
- when: '${{ in.category == "bug" }}'
body:
- name: fix_bug
run: echo "Fixing bug"
- when: '${{ in.category == "feature" }}'
body:
- name: add_feature
run: echo "Adding feature"
- default: true
body:
- name: log_unknown
run: echo "Unknown category"
Each case carries its own body: (inside the case); only one case runs.
while¶
while is a pre-condition loop: it checks cond before each
iteration, so the body may run zero times. max_iter is required and bounds
the loop. state: seeds values that the condition and body can read.
- root: wait_loop
control:
type: while
cond: '${{ state.status != "ready" }}'
max_iter: 10
state:
init:
status: pending
attempt: 0
body:
- name: check_status
run: echo "checking..."
The loop runs at most max_iter times and stops as soon as cond becomes
false.
do-while¶
do-while is a post-condition loop: the body always runs at least once,
then until is checked after each iteration. max_iter is required. This
fits “attempt, then decide whether to retry” patterns.
- root: retry_loop
control:
type: do-while
until: '${{ state.success == true }}'
max_iter: 3
state:
init:
success: false
attempt: 0
body:
- name: attempt_task
run: echo "attempting..."
The loop repeats until until is true or max_iter is reached.
repeat¶
repeat runs the body a fixed count times. An optional until:
condition lets it exit early once satisfied.
- root: refine_loop
control:
type: repeat
count: 5
until: '${{ state.quality >= 0.9 }}'
state:
init:
quality: 0.5
body:
- name: improve
run: echo "refining..."
The body runs up to count times, stopping early if until becomes true.
Carrying state across iterations¶
Loops (while, do-while, repeat) maintain a state dictionary:
state.initprovides the values for the first iteration. They are readable in conditions and bodies as${{ state.<field> }}.A loop body reports updated values by producing output items whose data fields are merged into
statefor the next iteration. This is how a loop makes progress – e.g. a body that emitssuccess: truelets ado-whileuntil: '${{ state.success == true }}'terminate.The counters
${{ _iter }}(0-based) and${{ _max_iter }}are available inside the loop.A body that emits
_break: truecauses the loop to exit immediately.
Note
control: constructs are compound tasks: every body: (and each
match case body, and the else: branch) is a list of ordinary tasks
that may have their own needs, parameters, and implementations. Bounded
loops (max_iter / count) are required so a flow always terminates.
See Also¶
Expressions – the expression syntax used in
cond/until/when.Using Tasks – compound tasks and task bodies.
Error Handling – handling failures within a task body.