Loop Reference
This document describes loop control flow in AWF workflows, including while loops, for-each loops, and transition behavior within loop bodies.
Overview
AWF supports two types of loops for iterative execution:
- While loops (
type: while) - Repeat until a condition becomes false - For-each loops (
type: for_each) - Iterate over a list of items
Loops can contain transitions within body steps, enabling advanced control flow patterns such as:
- Intra-body transitions - Skip steps within the current iteration
- Early exit - Break out of the loop before completion
- Error handling - Retry patterns with
on_failuretransitions
Loop bodies execute sequentially by default. When transitions are defined in body steps, the loop executor evaluates them after each step and jumps to the target step or exits the loop.
While Loops
While loops repeat execution until a break_when condition evaluates to true or max_iterations is reached.
Syntax
my_loop:
type: while
while: 'true' # Optional condition (default: true)
break_when: 'states.check.Output contains "DONE"'
max_iterations: 10
body:
- step1
- step2
- step3
on_complete: next_state
on_failure: error_handlerOptions
| Field | Type | Required | Description |
|---|---|---|---|
while | string | No | Condition expression (default: true) |
break_when | string | Yes | Exit condition (evaluates each iteration) |
max_iterations | int | No | Maximum iterations (default: unlimited) |
body | array | Yes | List of step names to execute in order |
on_complete | string | No | Next state after loop completes normally |
on_failure | string | No | Next state if loop step fails |
Execution Flow
- Evaluate
whilecondition - if false, skip loop entirely - For each iteration:
- Execute body steps in order
- Evaluate transitions after each step (see Transitions Within Loop Bodies)
- Check
break_whencondition after body completes - Exit if condition is true or
max_iterationsreached
- Transition to
on_completestate
Example: Retry Until Success
retry_deploy:
type: while
while: 'true'
break_when: 'states.check_deployment.Output contains "SUCCESS"'
max_iterations: 5
body:
- deploy_app
- check_deployment
on_complete: notify_success
on_failure: notify_failure
deploy_app:
type: step
command: ./deploy.sh
on_success: retry_deploy
check_deployment:
type: step
command: curl -f https://app.example.com/health
on_success: retry_deployFor-Each Loops
For-each loops iterate over a list of items, executing the body once per item.
Syntax
process_files:
type: for_each
items:
- file1.txt
- file2.txt
- file3.txt
body:
- validate_file
- process_file
on_complete: summarize
on_failure: cleanupOptions
| Field | Type | Required | Description |
|---|---|---|---|
items | array | Yes | List of items to iterate over |
body | array | Yes | List of step names to execute per item |
on_complete | string | No | Next state after all items processed |
on_failure | string | No | Next state if any body step fails |
Item Access
Within loop body steps, access the current item using {{.loop.Item}}:
process_file:
type: step
command: |
echo "Processing {{.loop.Item}}"
cat "{{.loop.Item}}" | process.sh
on_success: process_filesExample: Process Multiple Environments
deploy_all:
type: for_each
items: ["dev", "staging", "prod"]
body:
- validate_env
- deploy_to_env
- verify_env
on_complete: done
validate_env:
type: step
command: |
echo "Validating {{.loop.Item}} environment"
./validate.sh --env={{.loop.Item}}
on_success: deploy_all
deploy_to_env:
type: step
command: |
echo "Deploying to {{.loop.Item}}"
./deploy.sh --env={{.loop.Item}}
on_success: deploy_all
verify_env:
type: step
command: |
curl -f https://{{.loop.Item}}.example.com/health
on_success: deploy_allTransitions Within Loop Bodies
Loop body steps can define transitions to control execution flow within iterations. The loop executor evaluates transitions after each step and performs one of the following actions:
- Intra-body jump - Target is another step in the loop body → skip to that step
- Early exit - Target is outside the loop body → break loop and goto target
- Sequential execution - No transition matches → continue to next body step
- Invalid target - Target doesn’t exist → log warning and continue sequentially
Target Resolution
When a transition matches, the executor resolves the target as follows:
- Check if target step exists in
bodyarray → jump forward or backward within iteration - Check if target step exists in workflow states → exit loop and transition to target
- If target not found → log warning and continue to next body step (graceful degradation)
Intra-Body Transitions (Skip Steps)
Transitions can jump to later steps in the body, skipping intermediate steps.
test_loop:
type: while
while: 'true'
break_when: 'states.run_tests.Output contains "ALL_PASSED"'
max_iterations: 3
body:
- run_tests
- check_results
- fix_code # Skipped when tests pass
- retry_build # Skipped when tests pass
- run_tests
on_complete: deploy
# When tests pass, skip fix_code and retry_build
check_results:
type: step
command: |
if grep -q "TESTS_PASSED" test-output.txt; then
echo "TESTS_PASSED"
else
echo "TESTS_FAILED"
fi
transitions:
- when: 'states.check_results.Output contains "TESTS_PASSED"'
goto: run_tests # Skip to final verification
- goto: fix_code # Continue to fix code
fix_code:
type: step
command: ./auto-fix.sh
on_success: test_loop
retry_build:
type: step
command: make build
on_success: test_loop
run_tests:
type: step
command: make test > test-output.txt
on_success: test_loopIn this example, when check_results detects passing tests, it transitions directly to run_tests, skipping both fix_code and retry_build.
Early Exit from Loops
Transitions targeting steps outside the loop body cause an immediate loop exit.
green_loop:
type: while
while: 'true'
break_when: 'states.verify.Output contains "COMPLETE"'
max_iterations: 10
body:
- implement
- test
- verify
on_complete: done
test:
type: step
command: ./run-tests.sh
transitions:
- when: 'states.test.ExitCode == 0'
goto: cleanup # Exit loop early - target outside body
- goto: implement
cleanup:
type: step
command: ./cleanup.sh
on_success: doneWhen the test succeeds, the transition to cleanup (outside the loop body) causes the loop to exit immediately, bypassing the verify step and break_when condition.
Sequential Execution Fallback
If no transition matches or the target is invalid, execution continues to the next body step sequentially. This provides graceful degradation and backward compatibility with existing workflows.
# Invalid target example - logs warning but continues
buggy_step:
type: step
command: echo "Processing"
transitions:
- when: 'states.buggy_step.Output contains "ERROR"'
goto: nonexistent_step # Warning logged, continues to next stepWhen an invalid transition target is encountered, AWF logs a warning and continues sequential execution. This prevents workflow failures due to misconfiguration.
Loop Context Variables
The following variables are available within loop bodies:
| Variable | Type | Availability | Description |
|---|---|---|---|
{{.loop.Index}} | integer | All loops | Current iteration index (0-based) |
{{.loop.Item}} | any | for_each only | Current item value |
{{.loop.Parent.*}} | any | Nested loops | Parent loop context (see Nested Loops) |
Example: Using Loop Index
retry_with_backoff:
type: while
while: 'true'
break_when: 'states.check.Output contains "SUCCESS"'
max_iterations: 5
body:
- wait_backoff
- attempt_operation
- check
wait_backoff:
type: step
command: |
# Exponential backoff: 2^index seconds
sleep $((2 ** {{.loop.Index}}))
on_success: retry_with_backoffNested Loops
Loops can be nested within each other. Each loop maintains its own context, and inner loops can access parent loop variables.
Nested Loop Context Isolation
Inner loop transitions only affect the inner loop. They cannot jump to steps in the parent loop body.
outer:
type: for_each
items: ["module1", "module2"]
body:
- setup_module
- inner_loop
- teardown_module
on_complete: done
inner_loop:
type: while
while: 'true'
break_when: 'states.test.Output contains "PASSED"'
max_iterations: 3
body:
- build
- test
on_complete: outer
test:
type: step
command: |
echo "Testing {{.loop.Parent.Item}}"
./test.sh --module={{.loop.Parent.Item}}
transitions:
- when: 'states.test.ExitCode == 0'
goto: outer # Early exit from inner loopIn this example:
- Inner loop uses
{{.loop.Parent.Item}}to access the outer loop’s current item - Transition to
outerexits the innerwhileloop but doesn’t skip steps in the outerfor_eachbody - After inner loop completes, execution continues to
teardown_modulein the outer loop
Parent Context Access
Access parent loop variables using the {{.loop.Parent.*}} prefix:
| Variable | Description |
|---|---|
{{.loop.Parent.Index}} | Parent loop iteration index |
{{.loop.Parent.Item}} | Parent loop current item (for_each only) |
Error Handling in Loops
On-Failure Transitions
Body steps can use on_failure to handle errors within the loop.
resilient_loop:
type: while
while: 'true'
break_when: 'states.process.Output contains "DONE"'
max_iterations: 10
body:
- fetch_data
- process
on_complete: done
on_failure: cleanup
fetch_data:
type: step
command: curl -f https://api.example.com/data
on_success: resilient_loop
on_failure: resilient_loop # Retry on same step (retry pattern)Retry Pattern Preservation
Transitioning to the same step name (e.g., on_failure: resilient_loop) creates a retry pattern. The loop executor preserves this behavior for backward compatibility with existing workflows.
# Retry pattern: on_failure transitions back to the loop itself
flaky_operation:
type: step
command: ./flaky-script.sh
retry:
max_attempts: 3
backoff: exponential
on_success: my_loop
on_failure: my_loop # Retry entire iterationEmpty Body Edge Case
Loops with empty bodies or no steps are valid but do nothing. The break_when condition is still evaluated each iteration.
# Edge case: empty body (valid but not useful)
wait_loop:
type: while
while: 'true'
break_when: 'states.external_check.Output contains "READY"'
max_iterations: 100
body: []
on_complete: proceedThis loop waits for an external condition without executing any steps. Consider using polling or event-driven patterns instead.
Backward Compatibility
Existing workflows without transitions in loop bodies continue to work unchanged. Sequential execution is the default behavior when no transitions are defined.
# Backward compatible: no transitions, sequential execution
simple_loop:
type: while
while: 'true'
break_when: 'states.step3.Output contains "DONE"'
body:
- step1
- step2
- step3
on_complete: done
step1:
type: step
command: echo "Step 1"
on_success: simple_loop
step2:
type: step
command: echo "Step 2"
on_success: simple_loop
step3:
type: step
command: echo "Step 3"
on_success: simple_loopThis workflow executes all three steps sequentially in each iteration, maintaining the behavior from previous AWF versions.
Examples
Example 1: TDD Loop with Skip Steps
This example demonstrates the TDD pattern from the F048 specification, where successful tests skip implementation steps.
name: tdd-workflow
version: "1.0.0"
states:
initial: green_loop
green_loop:
type: while
while: 'true'
break_when: 'states.check_tests_passed.Output contains "TESTS_PASSED"'
max_iterations: 10
body:
- run_tests_green
- check_tests_passed
- prepare_impl_prompt
- implement_item
- run_fmt
on_complete: done
run_tests_green:
type: step
command: |
make test > test-output.txt
echo "TEST_EXIT_CODE=$?" >> test-output.txt
on_success: green_loop
check_tests_passed:
type: step
command: |
if grep -q "TEST_EXIT_CODE=0" test-output.txt; then
echo "TESTS_PASSED"
else
echo "TESTS_FAILED"
fi
transitions:
- when: 'states.check_tests_passed.Output contains "TESTS_PASSED"'
goto: run_fmt # Skip prepare_impl_prompt and implement_item
- goto: prepare_impl_prompt
prepare_impl_prompt:
type: step
command: ./prepare-prompt.sh
on_success: green_loop
implement_item:
type: step
command: ./implement.sh
on_success: green_loop
run_fmt:
type: step
command: make fmt
on_success: green_loop
done:
type: terminal
status: successWhen tests pass, check_tests_passed transitions directly to run_fmt, skipping the implementation steps. This prevents unnecessary AI agent execution.
Example 2: Early Exit on Critical Error
This example shows how to exit a validation loop early when a critical error is detected.
name: validate-services
version: "1.0.0"
states:
initial: validate_loop
validate_loop:
type: for_each
items: ["auth", "api", "database", "cache"]
body:
- check_service
- validate_config
- test_connectivity
on_complete: all_healthy
on_failure: cleanup
check_service:
type: step
command: |
systemctl is-active "{{.loop.Item}}"
transitions:
- when: 'states.check_service.ExitCode != 0'
goto: critical_error # Exit loop immediately
on_success: validate_loop
validate_config:
type: step
command: |
validate-config --service={{.loop.Item}}
on_success: validate_loop
test_connectivity:
type: step
command: |
curl -f "http://localhost:{{.loop.Item}}-port/health"
on_success: validate_loop
critical_error:
type: terminal
status: failure
message: "Critical service validation failed"
all_healthy:
type: terminal
status: success
message: "All services validated"When check_service detects a stopped service, it transitions to critical_error, exiting the for-each loop immediately without validating remaining services.
Example 3: Nested Loops with Parent Context
This example demonstrates nested loops where the inner loop uses parent context variables.
name: test-matrix
version: "1.0.0"
states:
initial: env_loop
env_loop:
type: for_each
items: ["dev", "staging", "prod"]
body:
- setup_env
- browser_loop
- teardown_env
on_complete: done
setup_env:
type: step
command: |
echo "Setting up {{.loop.Item}} environment"
./setup.sh --env={{.loop.Item}}
on_success: env_loop
browser_loop:
type: for_each
items: ["chrome", "firefox", "safari"]
body:
- run_browser_tests
on_complete: env_loop
run_browser_tests:
type: step
command: |
echo "Testing {{.loop.Parent.Item}} with {{.loop.Item}}"
./test.sh --env={{.loop.Parent.Item}} --browser={{.loop.Item}}
transitions:
- when: 'states.run_browser_tests.ExitCode != 0'
goto: test_failed
on_success: browser_loop
teardown_env:
type: step
command: |
./teardown.sh --env={{.loop.Item}}
on_success: env_loop
test_failed:
type: terminal
status: failure
message: "Browser test failed"
done:
type: terminal
status: success
message: "All environment and browser tests passed"The inner browser_loop accesses the outer loop’s environment using {{.loop.Parent.Item}}, creating a test matrix that validates each browser against each environment.
Known Limitations
Nested Loop Max Iteration Handling
When a loop reaches max_iterations and its body contains nested loops (for_each, while, parallel, or call_workflow steps), AWF generates a specific error: “loop reached maximum iterations with nested complexity”.
Current Behavior: This error occurs even if the nested loop execution is otherwise successful. The outer loop fails when it hits max_iterations, regardless of whether the nested steps completed normally.
Impact: You cannot rely on max_iterations as a safety mechanism when using nested loops in the body. The workflow will fail with a complexity error instead of completing normally.
Example:
outer_while:
type: while
while: 'true'
break_when: 'false'
max_iterations: 2 # Will fail with "nested complexity" error
body:
- inner_foreach # Nested loop triggers complexity detection
on_complete: done
inner_foreach:
type: for_each
items: ["x", "y"]
body:
- processRecommendation:
- Always use
break_whenconditions to exit nested loops naturally - Set
max_iterationshigh enough that it’s never reached under normal conditions - For complex iteration patterns, consider:
- Breaking the workflow into multiple workflows using
call_workflowsteps - Using conditional transitions to flatten nested logic
- Restructuring data to reduce nesting requirements
- Breaking the workflow into multiple workflows using
Test Reference: See TestExecuteLoopStep_WhileContainingForEach in internal/application/execution_service_test.go for the documented behavior.
Future Enhancement: This limitation may be addressed in future AWF versions with improved nested loop iteration tracking.
See Also
- Workflow Syntax - General workflow structure and state types
- Variable Interpolation - Using
{{.loop.*}}and other variables - Validation - Loop validation rules and error messages