.NET: [Feature Branch] Add Human In the Loop support for durable workflows#4358
.NET: [Feature Branch] Add Human In the Loop support for durable workflows#4358kshyju wants to merge 10 commits intofeat/durable_taskfrom
Conversation
Add 06_WorkflowHITL Azure Functions sample demonstrating Human-in-the-Loop workflow support with HTTP endpoints for status checking and approval responses. The sample includes: - ExpenseReimbursement workflow with RequestPort for manager approval - Custom HTTP endpoint to check workflow status and pending approvals - Custom HTTP endpoint to send approval responses via RaiseEventAsync - demo.http file with step-by-step interaction examples
There was a problem hiding this comment.
Pull request overview
Adds Human-in-the-Loop (HITL) support to durable workflows by pausing at RequestPort nodes, surfacing “waiting for input” signals via streaming/custom status, and enabling resumption via typed responses (streaming API and Azure Functions HTTP endpoints).
Changes:
- Introduces RequestPort-backed “wait for external input” execution in the DurableTask workflow runner/dispatcher and exposes it via a new
DurableWorkflowWaitingForInputEvent+IStreamingWorkflowRun.SendResponseAsync. - Adds Azure Functions auto-generated HTTP endpoints for
respond(when RequestPorts exist) and optionalstatus(opt-in per workflow). - Adds console + Azure Functions samples and integration tests validating HITL flows.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs | Adds an integration test that drives the Azure Functions HITL sample via run/respond endpoints and log-based assertions. |
| dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/WorkflowConsoleAppSamplesValidation.cs | Adds an integration test that validates the console HITL sample’s sequential approval flow. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowsFunctionMetadataTransformer.cs | Auto-registers HTTP triggers for status (opt-in) and respond (when RequestPorts exist) per workflow. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowOptionsExtensions.cs | Adds Azure Functions–specific workflow registration overload (exposeStatusEndpoint). |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsDurableOptions.cs | Introduces Functions-specific DurableOptions subclass to track per-workflow status-endpoint enablement. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs | Ensures shared options instance is the Functions-specific subtype; wires middleware/transformer for new endpoints. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionMetadataFactory.cs | Extends HTTP trigger metadata creation to support configurable HTTP methods (e.g., GET for status). |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs | Adds HTTP handlers for workflow status and responding to pending RequestPorts. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs | Routes Azure Functions invocations to the new built-in status/respond handlers. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/PendingRequestPortStatus.cs | Defines the serialized durable custom-status representation of a pending RequestPort wait. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/IStreamingWorkflowRun.cs | Adds SendResponseAsync API to resume a workflow after a waiting-for-input event. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowWaitingForInputEvent.cs | Introduces the streaming event emitted when a workflow is paused at a RequestPort; includes typed input deserialization helper. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowRunner.cs | Publishes a richer live status payload (events + pending RequestPorts) into custom status for streaming/polling. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowOptions.cs | Exposes parent options via ParentOptions (used for cross-feature coordination). |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowLiveStatus.cs | Adds new live-status payload type that includes streamed events + pending RequestPorts and parsing helper. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowJsonContext.cs | Updates source-gen JSON context registrations for new live-status/pending-request types. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowCustomStatus.cs | Removes the prior events-only custom status type (replaced by live status payload). |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableStreamingWorkflowRun.cs | Streams both normal workflow events and new waiting-for-input events; implements SendResponseAsync. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs | Adds RequestPort dispatch path that writes pending status + waits for external events. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/ServiceCollectionExtensions.cs | Excludes RequestPort binding from activity registration (handled via external events instead). |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Logs.cs | Adds log messages for waiting/received external RequestPort events used by tests and diagnostics. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/DurableOptions.cs | Makes DurableOptions non-sealed to enable hosting-specific extension. |
| dotnet/samples/Durable/Workflow/ConsoleApps/08_WorkflowHITL/README.md | Documents the console HITL sample usage and expected output. |
| dotnet/samples/Durable/Workflow/ConsoleApps/08_WorkflowHITL/Program.cs | Adds the console sample demonstrating sequential RequestPort pauses and streaming resumption. |
| dotnet/samples/Durable/Workflow/ConsoleApps/08_WorkflowHITL/Executors.cs | Adds sample executors and request/response types for the console HITL scenario. |
| dotnet/samples/Durable/Workflow/ConsoleApps/08_WorkflowHITL/08_WorkflowHITL.csproj | Adds the console HITL sample project. |
| dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/host.json | Adds host configuration for local DTS emulator + logging for the Functions HITL sample. |
| dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/demo.http | Adds a runnable HTTP script showing run/status/respond flow. |
| dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/README.md | Documents the Azure Functions HITL sample endpoints and usage. |
| dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/Program.cs | Adds the Azure Functions sample wiring a workflow with exposeStatusEndpoint: true. |
| dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/Executors.cs | Adds sample executors and request/response types for the Functions HITL scenario. |
| dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/03_WorkflowHITL.csproj | Adds the Azure Functions HITL sample project. |
| dotnet/agent-framework-dotnet.slnx | Registers the new console and Azure Functions HITL samples in the solution. |
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsDurableOptions.cs
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
Outdated
Show resolved
Hide resolved
...src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowOptionsExtensions.cs
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs
Show resolved
Hide resolved
...sts/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
Show resolved
Hide resolved
dotnet/samples/Durable/Workflow/ConsoleApps/08_WorkflowHITL/README.md
Outdated
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionMetadataFactory.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 34 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowsFunctionMetadataTransformer.cs:145
- RequestPort executors are dispatched via external events (not Durable Task activities), but this metadata transformer currently treats any non-agent/non-subworkflow binding as an activity trigger. This will register an activity function for RequestPortBinding executors even though they’re never called and aren’t registered as activities in Microsoft.Agents.AI.DurableTask. Consider skipping RequestPortBinding here (similar to SubworkflowBinding) to avoid generating unused activity triggers and potential confusion.
dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs
Show resolved
Hide resolved
…gEvents.Add`/`RemoveAll` and `SetCustomStatus` in `ExecuteRequestPortAsync`. The guards broke fan-out scenarios where parallel RequestPorts need to be discoverable after replay. `SetCustomStatus` is idempotent metadata that doesn't affect replay determinism.eanup
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
Outdated
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableStreamingWorkflowRun.cs
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableStreamingWorkflowRun.cs
Show resolved
Hide resolved
cgillum
left a comment
There was a problem hiding this comment.
A few initial comments about the samples. Will look at the implementation next.
dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/README.md
Show resolved
Hide resolved
dotnet/samples/Durable/Workflow/AzureFunctions/03_WorkflowHITL/README.md
Outdated
Show resolved
Hide resolved
dotnet/samples/Durable/Workflow/ConsoleApps/08_WorkflowHITL/Program.cs
Outdated
Show resolved
Hide resolved
cgillum
left a comment
There was a problem hiding this comment.
Need to sign off for the day, but a couple other small things for now.
dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs
Show resolved
Hide resolved
| HashSet<string> registeredFunctions = []; | ||
|
|
||
| foreach (var workflow in this._options.Workflows) | ||
| foreach (var workflow in this._options.Workflows.Workflows) |
There was a problem hiding this comment.
non-blocking nit: this looks weird. I'd expect something like this._options.WorkflowOptions.Workflows.
Motivation and Context
This PR introduces Human-in-the-Loop (HITL) support for durable workflows. It allows workflows to pause at defined checkpoints and wait for external input, enabling scenarios such as approval processes, human reviews, and other interactive decision-making workflows.
Key scenarios enabled:
RequestPortto wait for external input (e.g., manager approval)DurableWorkflowWaitingForInputEventto callers so they can discover what input is neededSendResponseAsyncor HTTP endpointsDescription
This PR introduces RequestPort-based HITL support for durable workflows, with both a streaming client API and auto-generated HTTP endpoints for Azure Functions hosting.
New Public APIs
IStreamingWorkflowRun.SendResponseAsyncDurableWorkflowWaitingForInputEventto resume the workflow.DurableWorkflowWaitingForInputEventRequestPort. Contains the serialized input and theRequestPortdefinition.DurableWorkflowWaitingForInputEvent.GetInputAs<T>()DurableWorkflowOptionsExtensions.AddWorkflow(workflow, exposeStatusEndpoint)Azure Functions Auto-Generated Endpoints
When a workflow contains
RequestPortnodes, the framework auto-generates a respond endpoint. A status endpoint is also available via opt-in./api/workflows/{name}/respond/{runId}/api/workflows/{name}/status/{runId}exposeStatusEndpoint: true)Internal Infrastructure Changes
DurableExecutorDispatcherExecuteRequestPortAsyncmethod that publishes pending request toDurableWorkflowLiveStatus, waits for external event viaWaitForExternalEvent, and cleans up on response.DurableWorkflowLiveStatusPendingEventslist for HITL signaling. Includes a sharedTryParsemethod for deserialization.DurableStreamingWorkflowRunPendingEventsin the durable workflow status and yieldsDurableWorkflowWaitingForInputEventfor each new pending request. ImplementsSendResponseAsyncviaRaiseEventAsync.DurableWorkflowRunnerDurableWorkflowLiveStatusonSuperstepStatefor consistent status updates across dispatch and result processing.ServiceCollectionExtensionsRequestPortBindingfrom activity registration (handled as external events).FunctionsDurableOptionsDurableOptionswith Azure Functions–specific configuration (status endpoint opt-in).BuiltInFunctionsGetWorkflowStatusAsyncandRespondToWorkflowAsyncHTTP handlers with input validation.BuiltInFunctionExecutorDurableWorkflowsFunctionMetadataTransformerFunctionMetadataFactorymethodsparameter toCreateHttpTriggerfor GET endpoints.How HITL Works
When the workflow reaches a
RequestPortexecutor, the orchestration:PendingRequestPortStatusentry toDurableWorkflowLiveStatus.PendingEventsSetCustomStatusso external clients can discover pending requestsWaitForExternalEventuntil the response arrivesExternal actors respond via:
run.SendResponseAsync(requestEvent, response)after receiving aDurableWorkflowWaitingForInputEvent/api/workflows/{name}/respond/{runId}with{"eventName": "...", "response": {...}}Validation/Testing
Samples Added:
08_WorkflowHITL(Console)03_WorkflowHITL(Azure Functions)demo.httpfor testing. No Azure OpenAI required.Integration Tests Added:
WorkflowHITLSampleValidationAsync(Console)HITLWorkflowSampleValidationAsync(Azure Functions)Both samples have been manually verified against the local DTS emulator.
Contribution Checklist