Add ctx.schedule_new_workflow for detached workflows - #1177
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1177 +/- ##
==========================================
+ Coverage 82.84% 82.86% +0.02%
==========================================
Files 123 123
Lines 10130 10191 +61
==========================================
+ Hits 8392 8445 +53
- Misses 1738 1746 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Signed-off-by: Albert Callarisa <[email protected]>
9010aa9 to
acad112
Compare
There was a problem hiding this comment.
Pull request overview
Adds first-class detached workflow spawning to the synchronous workflow context.
Changes:
- Adds public and durable-task context APIs.
- Implements action generation, routing, replay reconciliation, and deterministic IDs.
- Adds unit tests, documentation, and an example.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
dapr/ext/workflow/workflow_context.py |
Defines the public context API. |
dapr/ext/workflow/dapr_workflow_context.py |
Delegates detached scheduling to durable-task. |
dapr/ext/workflow/_durabletask/task.py |
Defines the engine context contract. |
dapr/ext/workflow/_durabletask/worker.py |
Implements scheduling and replay handling. |
dapr/ext/workflow/_durabletask/internal/helpers.py |
Constructs detached actions and events. |
dapr/ext/workflow/AGENTS.md |
Documents detached workflow semantics. |
tests/ext/workflow/test_dapr_workflow_context.py |
Tests the public wrapper. |
tests/ext/workflow/durabletask/test_detached_workflow.py |
Tests engine behavior and replay. |
examples/workflow/detached.py |
Demonstrates detached fan-out. |
Suppressed comments (3)
dapr/ext/workflow/_durabletask/worker.py:1490
- Reject invalid detached options before consuming an action sequence. An explicit empty
instance_idcurrently emits an action the Dapr runtime rejects, whileapp_namespacewithoutapp_idcreates an unusable routing envelope; both cases are rejected by the upstream context API.
id = self.next_sequence_number()
workflow_name = workflow if isinstance(workflow, str) else task.get_name(workflow)
if instance_id is None:
self._detached_counter += 1
instance_id = f'{self.instance_id}-{self._detached_counter}'
tests/ext/workflow/durabletask/test_detached_workflow.py:34
- The helper leaves both
encoded_inputand its return value untyped, despite the repository's strong-typing requirement. Annotate the optional encoded payload and executor result explicitly.
def _run(registry: worker._Registry, entry_name: str, encoded_input=None):
dapr/ext/workflow/_durabletask/worker.py:2039
- A historical detached event can align with a current
completeWorkflowaction when updated workflow code now returns before this spawn. In that case_get_wrong_action_type_error()calls_get_method_name_for_action(), which has nocompleteWorkflowcase and raisesNotImplementedError, masking the intendedNonDeterminismError. Make the action-name lookup handle completion actions or safely fall back to the action type.
elif not action.HasField('createDetachedWorkflow'):
expected_method_name = task.get_name(ctx.schedule_new_workflow)
raise _get_wrong_action_type_error(task_id, expected_method_name, action)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| Unlike ``call_sub_orchestrator``, the spawned workflow is fully | ||
| decoupled from the caller: no parent linkage is recorded on the new | ||
| instance and no completion or failure flows back. There is no | ||
| awaitable task — the call resolves as soon as the runtime accepts the | ||
| action. |
| if __name__ == '__main__': | ||
| wfr.start() |
| import dapr.ext.workflow._durabletask.internal.protos as pb | ||
| from dapr.ext.workflow._durabletask import task, worker | ||
|
|
||
| logging.basicConfig(level=logging.DEBUG) |
| # Cancel any pending actions except detached-workflow spawns, which are | ||
| # fire-and-forget: the action is effective the moment schedule_new_workflow | ||
| # returns, so it must survive the caller's completion. |
Ref: dapr/dapr#9261
Summary
Adds first-class support for spawning fully decoupled workflows from within another workflow in the Python SDK, mirroring the API introduced upstream in dapr/durabletask-go#100 and the runtime support in dapr/dapr#9902. Users no longer need to wrap
DaprWorkflowClient.schedule_new_workflowinside an activity to start an independent workflow — it's now a first-class context method.Notes
9d3681cb…already includesCreateDetachedWorkflowActionandDetachedWorkflowInstanceCreatedEvent.dapr.ext.workflow.aio) is unchanged: the workflow context API is sync (generator-based) in this SDK; only the client has an async variant, and detached spawning is a context operation.