Python: HITL respond-URL addressing from inside workflows#7001
Draft
ahmedmuhsin wants to merge 4 commits into
Draft
Python: HITL respond-URL addressing from inside workflows#7001ahmedmuhsin wants to merge 4 commits into
ahmedmuhsin wants to merge 4 commits into
Conversation
…ecutors
Let a workflow notify a human reviewer (e.g. email an approval link) from inside the
graph, without the caller threading the instanceId/requestId by hand.
- durabletask: the orchestrator injects host_context {instance_id, workflow_name,
request_path_prefix} into each activity input; CapturingRunnerContext surfaces it as
host_metadata. No new core API.
- azurefunctions: WorkflowHitlContext.from_context(ctx) builds the canonical
respond/status URLs (returns None in-process so callers degrade gracefully).
Re-exported through the agent_framework.azure lazy namespace.
- Nested sub-workflows: the address context (root instance + workflow name +
accumulated {executor}~{ordinal}~ prefix) propagates down call_sub_orchestrator via a
new SUBWORKFLOW_ADDRESS_KEY marker, so an executor at any depth builds a URL that
targets the addressable top-level instance with a qualified request id. The per-child
ordinal matches the read-side enumerate() index used by the status/respond endpoints.
The marker is stripped from untrusted input alongside SUBWORKFLOW_INPUT_KEY
(confused-deputy / info-leak guard).
- Samples 12 and 13 reworked into the retry-safe two-step notify pattern: the emitter
generates an explicit request id and a downstream NotifyExecutor builds the URL and
notifies, so failed upstream retries never produce a dead link.
Tests: unit coverage for the metadata round-trip, address/ordinal agreement (fan-out at
depth and nested prefix accumulation), marker stripping, and URL building; integration
tests assert the helper-built URL equals the server respondUrl and resumes the run, for
both the flat (12) and nested (13) samples.
…g one in samples Add WorkflowHitlContext.pending_request_id(ctx), an async helper that returns the id request_info just generated (read from the runner context's pending request-info events). This works on any host via the core RunnerContext protocol method, so it needs no core change. Samples 12 and 13 now call request_info() and read the id back to forward to the NotifyExecutor, instead of minting a uuid by hand and passing request_id=. The read-back happens in the same activity execution that generated the id, so the pending request event and the notify message still commit together with the same id (retry-safe; failed upstream retries notify no one).
…afety Tighten pending_request_id docstring to require calling it immediately after request_info, and explain why that is safe on the durable host (each executor runs in its own activity with its own runner context, so the pending set only holds this executor's requests and the newest is the one just emitted). Document the two-step notify pattern in the 12 and 13 sample READMEs, including the downstream-notifier retry safety and the nested address-prefix propagation.
5 tasks
Contributor
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enables Python Durable Functions workflows (including nested sub-workflows) to construct canonical HITL /respond (and /status) URLs from inside executors, by surfacing orchestration addressing metadata onto the runner context and adding a small helper (WorkflowHitlContext) in agent-framework-azurefunctions.
Changes:
- Surface durable-host orchestration metadata (root instance/workflow + nested request-path prefix) to executors via durabletask runner context
host_metadata. - Add
WorkflowHitlContexthelper to build respond/status URLs and to read back the latestrequest_info-generated request id from the runner context. - Update samples + add unit/integration tests, including nested sub-workflow address propagation and marker-stripping security coverage.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| python/samples/04-hosting/azure_functions/13_subworkflow_hitl/README.md | Documents in-subworkflow notification pattern using WorkflowHitlContext. |
| python/samples/04-hosting/azure_functions/13_subworkflow_hitl/function_app.py | Adds inner-workflow notifier executor and wires it into the nested HITL sample. |
| python/samples/04-hosting/azure_functions/12_workflow_hitl/README.md | Documents in-workflow reviewer notification + respond URL building. |
| python/samples/04-hosting/azure_functions/12_workflow_hitl/function_app.py | Adds notifier executor and routes approval result explicitly to publish step. |
| python/packages/durabletask/tests/test_workflow_serialization.py | Adds tests ensuring subworkflow address marker is stripped from untrusted input. |
| python/packages/durabletask/tests/test_workflow_activity.py | Adds tests asserting host_context is surfaced as runner_context.host_metadata. |
| python/packages/durabletask/tests/test_subworkflow_orchestration.py | Adds tests for address resolution + per-superstep subworkflow ordinal/prefix agreement. |
| python/packages/durabletask/agent_framework_durabletask/_workflows/serialization.py | Introduces SUBWORKFLOW_ADDRESS_KEY and strips it alongside SUBWORKFLOW_INPUT_KEY. |
| python/packages/durabletask/agent_framework_durabletask/_workflows/runner_context.py | Adds host_metadata plumbing to the durable activity runner context. |
| python/packages/durabletask/agent_framework_durabletask/_workflows/orchestrator.py | Threads address context into activity/subworkflow dispatch and propagates nested prefix. |
| python/packages/durabletask/agent_framework_durabletask/_workflows/activity.py | Reads host_context and sets it on the runner context for executor access. |
| python/packages/core/agent_framework/azure/init.pyi | Re-exports WorkflowHitlContext from the agent_framework.azure facade typings. |
| python/packages/core/agent_framework/azure/init.py | Adds lazy export mapping for WorkflowHitlContext. |
| python/packages/azurefunctions/tests/test_hitl_context.py | New unit tests for WorkflowHitlContext URL building/base URL resolution/pending id. |
| python/packages/azurefunctions/tests/integration_tests/test_13_workflow_subworkflow_hitl.py | Integration test asserts helper-built URL matches server qualified respondUrl (nested). |
| python/packages/azurefunctions/tests/integration_tests/test_12_workflow_hitl.py | Integration test asserts helper-built URL matches server respondUrl (flat). |
| python/packages/azurefunctions/agent_framework_azurefunctions/_hitl_context.py | New helper implementation for HITL URL construction + pending request id read-back. |
| python/packages/azurefunctions/agent_framework_azurefunctions/init.py | Exports WorkflowHitlContext from the package public surface. |
- test_subworkflow_orchestration: replace the mypy-only type:ignore[arg-type] with a cast so the ty checker passes too (the other four checkers already honored the ignore). - samples 12/13 README: guard the notify snippet against None before build_respond_url to match the documented graceful-degradation behavior. - integration tests 12/13: reword comments that implied request_info now generates an explicit uuid4; it generates the id internally by default.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
The durable function HITL flow pauses a workflow with
request_infoand waits for a human to POST a response to the/respondendpoint. The gap was that nothing inside the workflow could build that respond URL. An executor only receives its message payload, not the orchestrationinstanceId, and therequestIddoes not exist untilrequest_inforuns. So to email someone an approval link you had to pull both ids from the HTTP run response or poll the status endpoint from outside the app.This came out of a real POC where someone wanted Agent B to email a reviewer when it finished, then resume to Agent C once approved, but had no way to address the running workflow from within it. This change lets a workflow build its own respond URL and notify a human from inside the graph with no caller plumbing of
instanceIdorrequestId, and it works for nested sub workflows too.Description & Review Guide
What are the major changes?
instanceId, the workflow name, and for nested workflows the request path prefix) onto each executor through the durable runner context. There is no new core API.WorkflowHitlContexthelper inagent-framework-azurefunctionsbuilds the canonical respond and status URLs from that metadata. It returnsNonein process so the same executor degrades gracefully when it is not on the durable host.WorkflowHitlContext.pending_request_id(ctx)reads the idrequest_infojust generated back off the context, so the user never mints an id by hand. It works on any host through the existing core runner context protocol.call_sub_orchestratorvia a newSUBWORKFLOW_ADDRESS_KEYmarker, so an executor at any depth builds a URL that targets the top level instance with a qualified request id. That marker is stripped from untrusted input alongside the existing input marker, so a top level caller cannot forge it.What is the impact of these changes?
respondUrland that posting to it resumes the run, for both the flat (12) and nested (13) samples.What do you want reviewers to focus on?
enumerateindex the status and respond endpoints use when they resolve a nested request. That agreement is the one spot where a mistake would send an emailed URL to the wrong child or a 404.Related Issue
No separate issue. This builds on the Durable Task multi-workflow and sub-workflow hosting work that landed on
mainin #6696, and now targetsmaindirectly.Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.