From 7c2f0a603e797ff353fe71bb52cbae1a8901900c Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Tue, 9 Dec 2025 11:09:19 -0800 Subject: [PATCH 1/2] Add workflow cancellation sample Add sample demonstrating how to cancel a running workflow using asyncio tasks. Shows both cancellation mid-execution and normal completion paths. Useful for implementing timeouts, graceful shutdown, or A2A executors. --- .../getting_started/workflows/README.md | 1 + .../control-flow/workflow_cancellation.py | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 python/samples/getting_started/workflows/control-flow/workflow_cancellation.py diff --git a/python/samples/getting_started/workflows/README.md b/python/samples/getting_started/workflows/README.md index f22d993669..df3262e112 100644 --- a/python/samples/getting_started/workflows/README.md +++ b/python/samples/getting_started/workflows/README.md @@ -73,6 +73,7 @@ Once comfortable with these, explore the rest of the samples below. | Switch-Case Edge Group | [control-flow/switch_case_edge_group.py](./control-flow/switch_case_edge_group.py) | Switch-case branching using classifier outputs | | Multi-Selection Edge Group | [control-flow/multi_selection_edge_group.py](./control-flow/multi_selection_edge_group.py) | Select one or many targets dynamically (subset fan-out) | | Simple Loop | [control-flow/simple_loop.py](./control-flow/simple_loop.py) | Feedback loop where an agent judges ABOVE/BELOW/MATCHED | +| Workflow Cancellation | [control-flow/workflow_cancellation.py](./control-flow/workflow_cancellation.py) | Cancel a running workflow using asyncio tasks | ### human-in-the-loop diff --git a/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py b/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py new file mode 100644 index 0000000000..20559cbf66 --- /dev/null +++ b/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py @@ -0,0 +1,103 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio + +from agent_framework import WorkflowBuilder, WorkflowContext, executor +from typing_extensions import Never + +""" +Sample: Workflow Cancellation + +A three-step workflow where each step takes 2 seconds. We cancel it after 3 seconds +to demonstrate mid-execution cancellation using asyncio tasks. + +Purpose: +Show how to cancel a running workflow by wrapping workflow.run() in an asyncio.Task. +This pattern is useful for implementing timeouts, graceful shutdown, or A2A executors +that need cancellation support. + +Prerequisites: +- No external services required. +""" + + +@executor(id="step1") +async def step1(text: str, ctx: WorkflowContext[str]) -> None: + """First step - simulates 2 seconds of work.""" + print("[Step1] Starting...") + await asyncio.sleep(2) + print("[Step1] Done") + await ctx.send_message(text.upper()) + + +@executor(id="step2") +async def step2(text: str, ctx: WorkflowContext[str]) -> None: + """Second step - simulates 2 seconds of work.""" + print("[Step2] Starting...") + await asyncio.sleep(2) + print("[Step2] Done") + await ctx.send_message(text + "!") + + +@executor(id="step3") +async def step3(text: str, ctx: WorkflowContext[Never, str]) -> None: + """Final step - simulates 2 seconds of work.""" + print("[Step3] Starting...") + await asyncio.sleep(2) + print("[Step3] Done") + await ctx.yield_output(f"Result: {text}") + + +def build_workflow(): + """Build a simple 3-step sequential workflow (~6 seconds total).""" + return ( + WorkflowBuilder() + .register_executor(lambda: step1, name="step1") + .register_executor(lambda: step2, name="step2") + .register_executor(lambda: step3, name="step3") + .add_edge("step1", "step2") + .add_edge("step2", "step3") + .set_start_executor("step1") + .build() + ) + + +async def run_with_cancellation() -> None: + """Cancel the workflow after 3 seconds (mid-execution during Step2).""" + print("=== Run with cancellation ===\n") + workflow = build_workflow() + + # Wrap workflow.run() in a task to enable cancellation + task = asyncio.create_task(workflow.run("hello world")) + + # Wait 3 seconds (Step1 completes, Step2 is mid-execution), then cancel + await asyncio.sleep(3) + print("\n--- Cancelling workflow ---\n") + task.cancel() + + try: + await task + except asyncio.CancelledError: + print("Workflow was cancelled") + + +async def run_to_completion() -> None: + """Let the workflow run to completion and get the result.""" + print("=== Run to completion ===\n") + workflow = build_workflow() + + # Run without cancellation - await the result directly + result = await workflow.run("hello world") + + print(f"\nWorkflow completed with output: {result.get_outputs()}") + + +async def main() -> None: + """Demonstrate both cancellation and completion scenarios.""" + await run_with_cancellation() + print("\n") + await run_to_completion() + + +if __name__ == "__main__": + asyncio.run(main()) From ae52dafc6f2d2e4a2c673a77291182f791697ae2 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Tue, 9 Dec 2025 11:13:37 -0800 Subject: [PATCH 2/2] update docstring --- .../workflows/control-flow/workflow_cancellation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py b/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py index 20559cbf66..2ebd5bd128 100644 --- a/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py +++ b/python/samples/getting_started/workflows/control-flow/workflow_cancellation.py @@ -12,9 +12,9 @@ to demonstrate mid-execution cancellation using asyncio tasks. Purpose: -Show how to cancel a running workflow by wrapping workflow.run() in an asyncio.Task. -This pattern is useful for implementing timeouts, graceful shutdown, or A2A executors -that need cancellation support. +Show how to cancel a running workflow by wrapping it in an asyncio.Task. This pattern +works with both workflow.run() and workflow.run_stream(). Useful for implementing +timeouts, graceful shutdown, or A2A executors that need cancellation support. Prerequisites: - No external services required.