Skip to content

Conversation

@ahmedmuhsin
Copy link

@ahmedmuhsin ahmedmuhsin commented Feb 2, 2026

Motivation and Context

Adds workflow orchestration support to Azure Functions, enabling multi-agent workflows with conditional routing, parallel execution, and human-in-the-loop (HITL) patterns using Azure Durable Functions.

Description

Core Package Changes (agent_framework_azurefunctions):

  • _workflow.py - Orchestration engine that executes MAF Workflows using Durable Functions' generator-based model:

    • Sequential and parallel agent/executor execution
    • Fan-out/fan-in patterns
    • Conditional routing via edge groups (switch/case, single edges)
    • HITL via wait_for_external_event with configurable timeouts
  • _context.py - CapturingRunnerContext for activity execution (captures messages/events without durable storage)

  • _serialization.py - Serialization utilities for cross-activity message passing (dataclasses, Pydantic models, ChatMessage)

  • _app.py - Extended AgentFunctionApp with:

    • New workflow parameter accepting a Workflow instance
    • Auto-registers agents from workflow executors
    • HTTP endpoints: POST /workflow/run, GET /workflow/status/{id}, POST /workflow/respond/{id}/{requestId}

New Samples (09-12):

Sample Pattern
09_workflow_shared_state Shared state + conditional routing (spam detection → email response)
10_workflow_no_shared_state Stateless workflow with switch/case routing
11_workflow_parallel Fan-out/fan-in for parallel agent execution
12_workflow_hitl Human-in-the-loop with approval/rejection flow

Tests:

  • Unit tests for _workflow.py, _serialization.py, _context.py, and _app.py workflow features (152 total)
  • Integration tests for all 4 new samples (14 tests)

API Fix:

  • Updated samples to use chat_client.as_agent() with default_options={"response_format": ...} instead of non-existent create_agent()

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No

@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation python labels Feb 3, 2026
@github-actions github-actions bot changed the title Durable Support for Workflows in Python Python: Durable Support for Workflows in Python Feb 3, 2026
@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Feb 3, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/azurefunctions/agent_framework_azurefunctions
   _app.py49218562%215–216, 221–222, 257–258, 261, 263–265, 271, 273, 275–278, 280–281, 283–285, 288, 291, 293, 295–296, 299–301, 303, 305, 313, 321–323, 326, 329, 334–335, 338–341, 344, 347–349, 360–363, 369, 371, 379–380, 383, 388–389, 391, 394, 397, 399, 401, 403–405, 409–412, 418, 420–421, 423, 434–436, 440–441, 443–444, 450, 461–466, 474, 476, 482–484, 490–491, 493–494, 500–503, 511, 517, 536, 635–636, 744, 752–753, 773–775, 781–783, 789–791, 824–825, 885–886, 935–936, 941, 1023, 1026, 1035–1037, 1039–1041, 1043, 1045, 1056, 1058–1061, 1063, 1065–1066, 1068, 1075–1076, 1078–1079, 1081–1082, 1084, 1088, 1098–1100, 1102–1103, 1105–1107, 1114, 1116–1117, 1119, 1140, 1145, 1157, 1232, 1242, 1249–1251, 1296, 1310, 1321–1323, 1325–1328, 1353, 1360, 1362, 1365
   _context.py65690%103, 107, 155–156, 164, 171
   _serialization.py1866763%105–106, 111–112, 131–132, 142, 144–146, 165, 175, 177–178, 182–183, 190, 192–194, 215, 221, 223, 226, 228–231, 234–238, 241–242, 244–247, 250–256, 258, 315, 322–327, 329–332, 334, 337, 346–347, 353–354, 360, 376–377
   _workflow.py34722335%132, 137, 139, 146, 195–197, 267–269, 271–273, 295, 301, 303–304, 327–328, 330–334, 336, 343, 368, 371–380, 383–384, 386, 413–414, 417–418, 421–427, 430–431, 434–439, 442–445, 448, 450–454, 469–471, 473, 476–477, 480–485, 487–488, 490–491, 493–494, 497, 515–519, 526, 550, 558–560, 563–564, 566, 614, 617–618, 621, 626, 628–630, 633, 638–642, 645–648, 650–651, 654–658, 660–661, 664–665, 668–669, 672, 674, 677–678, 681, 697–698, 701–702, 704, 706, 708, 711–712, 720–722, 724–727, 730, 733, 740–741, 746, 748, 751, 778–780, 783, 786–788, 790–792, 795–798, 808–810, 812–815, 825, 827, 841–842, 871–872, 887, 917, 920–922, 925, 928, 931, 933–934, 940, 944, 953, 957, 970, 976–977, 980–982, 985–988, 990–991, 993–994, 996–999, 1001–1002, 1005–1006
packages/core/agent_framework/_workflows
   _agent_executor.py1732486%94, 116, 122, 155, 171–172, 223–224, 226–227, 259–261, 269–271, 281–283, 285, 289, 293, 297–298
TOTAL16975238485% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
3837 235 💤 0 ❌ 0 🔥 1m 21s ⏱️

@ahmedmuhsin ahmedmuhsin changed the title Python: Durable Support for Workflows in Python Python: Durable Support for Workflows Feb 3, 2026
@ahmedmuhsin ahmedmuhsin marked this pull request as ready for review February 3, 2026 16:55
@ahmedmuhsin ahmedmuhsin requested a review from a team as a code owner February 3, 2026 16:55
Copilot AI review requested due to automatic review settings February 3, 2026 16:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive workflow orchestration support to Azure Functions, enabling multi-agent workflows with conditional routing, parallel execution, and human-in-the-loop (HITL) patterns using Azure Durable Functions.

Changes:

  • New workflow orchestration engine in _workflow.py that executes MAF Workflows using Durable Functions' generator-based model
  • Serialization utilities in _serialization.py for cross-activity message passing
  • Capturing runner context in _context.py for activity execution
  • Extended AgentFunctionApp with workflow parameter and auto-registration of agents
  • Four new samples (09-12) demonstrating shared state, stateless, parallel, and HITL workflow patterns
  • Comprehensive unit and integration tests (152 unit tests, 14 integration tests)
  • Bug fix: Updated chat_client.as_agent() usage with default_options parameter

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
_workflow.py Main orchestration engine with HITL support, parallel execution, and edge group routing
_serialization.py Serialization/deserialization for dataclasses, Pydantic models, and MAF types
_context.py CapturingRunnerContext for activity execution without durable storage
_app.py Extended AgentFunctionApp with workflow support and HTTP endpoints
_agent_executor.py Added agent property to expose underlying agent
Samples 09-12 Four new workflow samples with requirements, configs, and README files
Test files Unit tests for workflow utilities and integration tests for all samples
pyproject.toml Increased test timeout from 120s to 300s for workflow tests
Sample 07 Fixed type annotations for Azure Functions worker compatibility
Comments suppressed due to low confidence (1)

python/packages/azurefunctions/agent_framework_azurefunctions/_app.py:1056

  • This import of module asyncio is redundant, as it was previously imported on line 9.
        import asyncio

Comment on lines +209 to +213


app = AgentFunctionApp(workflow=workflow)


Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are blank lines between the workflow creation and AgentFunctionApp instantiation (lines 209-210, 212-213) that appear to be unnecessary and reduce code readability. These should be removed.

Suggested change
app = AgentFunctionApp(workflow=workflow)
app = AgentFunctionApp(workflow=workflow)

Copilot uses AI. Check for mistakes.
launch(durable=False)
else:
print("Usage: python function_app.py --maf")
print(" --maf Run in pure MAF mode with DevUI (http://localhost:8096)")
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text for non-MAF mode states the wrong port number. It says "http://localhost:8096" but the code uses port 8094 (line 233). This should be corrected to "http://localhost:8094".

Suggested change
print(" --maf Run in pure MAF mode with DevUI (http://localhost:8096)")
print(" --maf Run in pure MAF mode with DevUI (http://localhost:8094)")

Copilot uses AI. Check for mistakes.
# use the task hub name to separate orchestration state.
env["TASKHUB_NAME"] = f"test{uuid.uuid4().hex[:8]}"

log_file = tempfile.TemporaryFile() # noqa: SIM115
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The noqa comment # noqa: SIM115 suppresses a simplification warning about using tempfile.TemporaryFile() instead of a context manager. However, the log_file handle is passed around and needs to remain open beyond the scope of this function, so a context manager cannot be used here. The noqa is justified, but it would be better to add a brief comment explaining why the context manager pattern cannot be used here.

Suggested change
log_file = tempfile.TemporaryFile() # noqa: SIM115
# The log_file handle is returned to the caller and must remain open beyond this function,
# so we intentionally do not use a context manager here. # noqa: SIM115
log_file = tempfile.TemporaryFile()

Copilot uses AI. Check for mistakes.

### Replace INSTANCE_ID_GOES_HERE below with the value returned from the POST call
@instanceId=<INSTANCE_ID_GOES_HERE>
@instanceId=ccf3950407b5496893df93d1357a5afa
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instance ID on line 23 has been changed from a placeholder value like <INSTANCE_ID_GOES_HERE> to a hardcoded UUID ccf3950407b5496893df93d1357a5afa. This hardcoded value won't work for users following the instructions, as each workflow run generates a unique instance ID. The value should be reverted to a placeholder like <INSTANCE_ID_GOES_HERE> to match the comment on line 22.

Suggested change
@instanceId=ccf3950407b5496893df93d1357a5afa
@instanceId=<INSTANCE_ID_GOES_HERE>

Copilot uses AI. Check for mistakes.
Comment on lines +722 to +723
import json

Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The json module is imported inside a try-except block on line 722. However, json is already imported at the module level (line 22). This redundant import should be removed, and the module-level import should be used instead.

Suggested change
import json

Copilot uses AI. Check for mistakes.

# Get type hints for the dataclass
try:
import typing
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'typing' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.

import logging
import os
from typing import Any, Dict
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Dict' is not used.

Suggested change
from typing import Any, Dict
from typing import Any

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants