Repository navigation
Description
Description
BreakLoop does not stop a declarative Foreach loop. The loop advances to the next item and executes the body for the remaining collection instead.
ForeachNextExecutor registers both handle_action(trigger: Any, ...) and handle_loop_control(control: LoopControl, ...). Handler discovery orders the generic handler before the loop-control handler, and dispatch selects the first matching handler. A LoopControl(action="break") therefore reaches ordinary iteration advancement instead of the existing break cleanup path.
Code Sample
import asyncio
from agent_framework_declarative import WorkflowFactory
async def main():
workflow = WorkflowFactory().create_workflow_from_definition({
"kind": "Workflow",
"name": "break-loop-repro",
"actions": [
{
"kind": "Foreach",
"source": ["A", "B", "C"],
"actions": [
{"kind": "SendActivity", "activity": "body"},
{"kind": "BreakLoop"},
],
},
{"kind": "SendActivity", "activity": "done"},
],
})
result = await workflow.run({})
print(result.get_outputs())
asyncio.run(main())Expected: ['body', 'done'].
Actual: ['body', 'body', 'body', 'done'].
The same behavior occurs when streaming events. Ordinary iteration and ContinueLoop complete normally. A fresh workflow restored from an in-memory checkpoint containing a pending break signal also executes the remaining iterations.
Error Messages / Stack Traces
No exception is raised; the workflow produces extra body outputs.
Package Versions
Source checkout at b9d24c8fb484c8330abe8bb9e7500ca3c3bbf46c; declarative package version 1.2.0, core version 1.20.0.
Python Version
Python 3.12.14 on Windows.
Additional Context
This reproducer uses local workflow execution without an external agent or service. I have a local fix that dispatches LoopControl to the existing handler inside ForeachNextExecutor, preserving the core executor dispatch rules and normal iteration behavior, and will submit it with regression tests.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity