Description
When a Foreach loop body contains multiple sequential actions (e.g., SendActivity → InvokeAzureAgent → InvokeAzureAgent → SendActivity), the loop advances to the next iteration as soon as the first action completes, instead of waiting for all actions to finish. This effectively causes loop body actions to execute in parallel across iterations.
Root cause
In agent_framework_declarative/_workflows/_declarative_builder.py, method _create_foreach_structure(), around line 776:
# Body exit -> Next (get all exits from body and wire to next_executor)
body_exits = self._get_source_exits(body_entry)
for body_exit in body_exits:
builder.add_edge(source=body_exit, target=next_executor)
body_entry is the first executor returned by _create_executors_for_actions(). _get_source_exits() checks for branch_exits (not present on a regular executor), then falls back to _exit_executor (not set), and finally returns the executor itself — i.e., the first action in the body.
The existing _get_branch_exit() method correctly uses _chain_executors[-1] to find the last executor in a chain, but _create_foreach_structure doesn't use it.
Suggested fix
# Before (buggy):
body_exits = self._get_source_exits(body_entry)
for body_exit in body_exits:
builder.add_edge(source=body_exit, target=next_executor)
# After (fixed):
chain = getattr(body_entry, "_chain_executors", [body_entry])
last_in_chain = chain[-1]
body_exits = self._get_source_exits(last_in_chain)
for body_exit in body_exits:
builder.add_edge(source=body_exit, target=next_executor)
Reproduction
Workflow YAML
- kind: Foreach
id: my_loop
source: =Local.items
itemName: item
indexName: idx
actions:
- kind: SendActivity
id: step_1
activity:
text: '="Step 1 for: " & Local.item'
- kind: InvokeAzureAgent
id: step_2
agent:
name: my_agent
input:
messages: =Local.item
output:
responseObject: Local.result
autoSend: false
- kind: SendActivity
id: step_3
activity:
text: '="Done: " & Local.item'
Expected behavior
For a list of 2 items, execution should be:
step_1 (item 0) → step_2 (item 0) → step_3 (item 0) → step_1 (item 1) → step_2 (item 1) → step_3 (item 1)
Actual behavior
step_1 (item 0) → step_1 (item 1) → step_2 (item 0) → step_2 (item 1) → ...
The loop advances to item 1 as soon as step_1 for item 0 completes, because the exit edge is wired from step_1 (first action) to ForeachNextExecutor instead of from step_3 (last action).
Code Sample
Error Messages / Stack Traces
Package Versions
agent-framework-declarative 1.0.0b260424
Python Version
Python 3.11.2
Additional Context
No response
Description
When a
Foreachloop body contains multiple sequential actions (e.g.,SendActivity→InvokeAzureAgent→InvokeAzureAgent→SendActivity), the loop advances to the next iteration as soon as the first action completes, instead of waiting for all actions to finish. This effectively causes loop body actions to execute in parallel across iterations.Root cause
In
agent_framework_declarative/_workflows/_declarative_builder.py, method_create_foreach_structure(), around line 776:body_entryis the first executor returned by_create_executors_for_actions()._get_source_exits()checks forbranch_exits(not present on a regular executor), then falls back to_exit_executor(not set), and finally returns the executor itself — i.e., the first action in the body.The existing
_get_branch_exit()method correctly uses_chain_executors[-1]to find the last executor in a chain, but_create_foreach_structuredoesn't use it.Suggested fix
Reproduction
Workflow YAML
Expected behavior
For a list of 2 items, execution should be:
Actual behavior
The loop advances to item 1 as soon as
step_1for item 0 completes, because the exit edge is wired fromstep_1(first action) toForeachNextExecutorinstead of fromstep_3(last action).Code Sample
Error Messages / Stack Traces
Package Versions
agent-framework-declarative 1.0.0b260424
Python Version
Python 3.11.2
Additional Context
No response