Motivation
Workflows often need to iterate over a list of items, such as processing multiple files, running agent tasks per item, or collecting results. Today users have to inline bash for or while loops inside a single bash step, which breaks the flowsh step model and makes it impossible to use per-item vars, agent steps, or structured logging.
Proposed Design: for wrapper step type
Add a new for step type that wraps a sequence of child steps and iterates over a variable from a vars step.
YAML shape
steps:
- type: vars
name: Collect items
values:
ITEMS: |
printf "file-a\nfile-b\nfile-c"
- type: for
name: Process each item
in: ITEMS
item: ITEM
steps:
- type: bash
name: Process
run: |
echo "Processing $ITEM"
- type: agent
name: Review
expandPrompt: true
prompt: |
Review the file $ITEM and suggest improvements.
How it generates
Inner steps are rendered as top-level bash functions (once, outside the loop), then called via run_step inside a while read loop:
# Inner step -- defined once outside the loop
for_0_process() {
bash -euo pipefail <<'BASH_EOF'
echo "Processing $ITEM"
BASH_EOF
}
# Loop step
step_process_for_item() {
while IFS= read -r ITEM; do
export ITEM
run_step for_0_process
run_step for_0_review
done <<< "$ITEMS"
}
run_stateful_step step_process_for_item
This gives every inner step:
- Per-iteration logging via
run_step
- Failure catching via
catch() with the correct exit code
- Dry-run support
- State preservation via
export ITEM
Models change
Add ForStep to models.py:
class ForStep(BaseStep):
type: Literal["for"]
in_: str = Field(..., validation_alias="in")
item: str = Field(...)
steps: list[Step] = Field(min_length=1)
Add to Step union.
Renderer change
In render.py:render_step, add a new elif isinstance(step, ForStep) branch:
- Emit each inner step as its own function using the existing
step_function_name and used_function_names for unique names.
- Emit the loop function body with
run_step <name> calls inside while IFS= read -r ....
- Use
run_stateful_step for the outer for function.
Accepted restrictions / downsides
- Items are newline-delimited
- Loop variable must match
[A-Z_][A-Z0-9_]*
agent inner steps need expandPrompt: true to see the loop variable
- No nested
for loops in v1
- Flat function namespace
Decisions
Motivation
Workflows often need to iterate over a list of items, such as processing multiple files, running agent tasks per item, or collecting results. Today users have to inline bash
fororwhileloops inside a singlebashstep, which breaks the flowsh step model and makes it impossible to use per-itemvars,agentsteps, or structured logging.Proposed Design:
forwrapper step typeAdd a new
forstep type that wraps a sequence of child steps and iterates over a variable from avarsstep.YAML shape
How it generates
Inner steps are rendered as top-level bash functions (once, outside the loop), then called via
run_stepinside awhile readloop:This gives every inner step:
run_stepcatch()with the correct exit codeexport ITEMModels change
Add
ForSteptomodels.py:Add to
Stepunion.Renderer change
In
render.py:render_step, add a newelif isinstance(step, ForStep)branch:step_function_nameandused_function_namesfor unique names.run_step <name>calls insidewhile IFS= read -r ....run_stateful_stepfor the outerforfunction.Accepted restrictions / downsides
[A-Z_][A-Z0-9_]*agentinner steps needexpandPrompt: trueto see the loop variableforloops in v1Decisions
vars,bash, andagent.