From 19f6d63096c52ac61d28e5d0c65a84b003321db5 Mon Sep 17 00:00:00 2001 From: Albert Callarisa Date: Wed, 17 Jun 2026 18:08:01 +0200 Subject: [PATCH 1/2] Document detached workflows Signed-off-by: Albert Callarisa --- .../workflow/workflow-detached.md | 104 ++++++++++++++++++ .../workflow/workflow-features-concepts.md | 7 ++ .../workflow/workflow-overview.md | 6 + 3 files changed, 117 insertions(+) create mode 100644 daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md new file mode 100644 index 00000000000..3231f8dcd93 --- /dev/null +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md @@ -0,0 +1,104 @@ +--- +type: docs +title: "Detached workflows" +linkTitle: "Detached workflows" +weight: 2600 +description: "Schedule fire-and-forget workflows that run independently of their caller" +--- + +Just like [child workflows]({{% ref "workflow-features-concepts.md#child-workflows" %}}), a workflow can schedule another workflow. With a _detached workflow_, however, the relationship is **fire-and-forget**: the calling workflow asks the runtime to start a new, fully independent workflow instance and then immediately continues with the rest of its own logic, without waiting for the detached workflow to complete. + +When you schedule a detached workflow, the call returns the new instance ID synchronously. The detached workflow then runs as its own top-level instance, with its own instance ID, history, and lifecycle, exactly as if it had been scheduled directly by a client. There is no parent linkage: the detached workflow's success or failure does not flow back to the caller, and terminating the caller does not terminate the detached workflow. + +This reference is one-directional. The calling workflow records the spawn as a single event in its own history, so it keeps a reference to the detached instance it created. The detached workflow, however, holds no reference back to its caller: it has no parent instance and behaves like a standalone, top-level workflow. + +Recording the spawn in the caller's history is also what keeps the calling workflow deterministic. As with any workflow action, the spawn must be captured in the history so that, when the workflow [replays]({{% ref "workflow-features-concepts.md#workflow-replay" %}}), the runtime knows the detached workflow was already created and does not schedule it a second time. Without this event, a replay would have no way to know whether the detached workflow had already been started. + +{{% alert title="Note" color="primary" %}} +Detached workflows are available in Dapr v1.19 and later. SDK support is currently available in Go, with support for the other SDKs being added soon. +{{% /alert %}} + +## Detached workflows compared to child workflows + +| | Child workflow | Detached workflow | +| --- | --- | --- | +| **Relationship to caller** | Coupled to the caller's lifecycle | Fully independent (fire-and-forget) | +| **Caller behavior** | Receives an awaitable task and typically awaits its completion | Receives the new instance ID synchronously and continues immediately | +| **Return value and errors** | Output and exceptions surface back to the caller | Nothing flows back to the caller | +| **Parent termination** | Terminating the caller terminates the child workflow | Terminating the caller has no effect on the detached workflow | +| **Retry policies** | Supported | Not applicable, as the caller does not track the outcome | + +If you need the result of the scheduled workflow, want its failures to propagate, or want it to be terminated alongside its parent, use a [child workflow]({{% ref "workflow-features-concepts.md#child-workflows" %}}) instead. + +## When to use detached workflows + +Detached workflows are a good fit when a workflow needs to trigger independent work that should be isolated from the caller, such as: + +- **Multi-tenant fan-out**: a workflow that iterates over tenants, organizations, or customers and starts an isolated workflow per entity, where you want hard isolation between each tenant's work and the orchestrating workflow. +- **Fire-and-forget downstream work**: triggering follow-on processing (notifications, audits, cleanup) whose outcome should not affect the workflow that started it. +- **Forking a new history chain**: starting a long-running or independently-managed workflow that should not inflate the caller's history or share its failure domain. + +## Schedule a detached workflow + +The following example schedules a detached `AuditWorkflow` from within `ParentWorkflow`. Unlike a child workflow, there is no task to await: the call returns the new instance ID synchronously and the scheduling workflow continues immediately. Both the scheduling workflow and the detached workflow must be registered with the worker, the same way as any other workflow. + +{{< tabpane text=true >}} + +{{% tab "Go" %}} + + + +```go +// ParentWorkflow schedules a detached workflow and continues immediately, +// without waiting for it to finish. +func ParentWorkflow(ctx *workflow.WorkflowContext) (any, error) { + // ScheduleNewWorkflow starts a detached, fire-and-forget workflow and + // returns its instance ID synchronously. There is no task to await. + instanceID, err := ctx.ScheduleNewWorkflow(AuditWorkflow, + workflow.WithDetachedWorkflowInput("order-1234"), + ) + if err != nil { + return nil, err + } + + // The parent continues without waiting. The detached workflow's success + // or failure does not affect this workflow. + return instanceID, nil +} + +// AuditWorkflow runs independently as its own top-level workflow instance. +func AuditWorkflow(ctx *workflow.WorkflowContext) (any, error) { + var orderID string + if err := ctx.GetInput(&orderID); err != nil { + return nil, err + } + if err := ctx.CreateTimer(3 * time.Second).Await(nil); err != nil { + return nil, err + } + return "audit completed for " + orderID, nil +} +``` + +{{% /tab %}} + +{{< /tabpane >}} + +### Detached workflow options + +When scheduling a detached workflow, you can set the following options to control the new instance. The exact API and naming depend on the SDK. + +| Option | Description | +| --- | --- | +| **Instance ID** | The instance ID of the detached workflow. When omitted, a deterministic ID of the form `-` is generated. | +| **Input** | The input passed to the detached workflow. | +| **Start time** | Defers the start of the detached workflow until the given time. | +| **App ID** | Schedules the detached workflow on a different application by target app ID, subject to the [multi-application workflow]({{% ref workflow-multi-app.md %}}) rules. | +| **App namespace** | The Dapr namespace of the target application. Must be combined with the app ID and, per the multi-application rules, must match the caller's namespace. | + +## Related links + +- [Workflow overview]({{% ref workflow-overview.md %}}) +- [Workflow features and concepts]({{% ref workflow-features-concepts.md %}}) +- [Child workflows]({{% ref "workflow-features-concepts.md#child-workflows" %}}) +- [Multi application workflows]({{% ref workflow-multi-app.md %}}) +- [How to: Author workflows]({{% ref howto-author-workflow.md %}}) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md index 9974c2c3c80..7f70a814e2c 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md @@ -23,6 +23,7 @@ There are several different kinds of tasks that a workflow can schedule, includi - [Activities]({{% ref "workflow-features-concepts.md#workflow-activities" %}}) for executing custom logic - [Durable timers]({{% ref "workflow-features-concepts.md#durable-timers" %}}) for putting the workflow to sleep for arbitrary lengths of time - [Child workflows]({{% ref "workflow-features-concepts.md#child-workflows" %}}) for breaking larger workflows into smaller pieces +- [Detached workflows]({{% ref "workflow-features-concepts.md#detached-workflows" %}}) for spawning independent, fire-and-forget workflows that run on their own - [External event waiters]({{% ref "workflow-features-concepts.md#external-events" %}}) for blocking workflows until they receive external event signals. These tasks are described in more details in their corresponding sections. ## Workflow Instance Management @@ -166,6 +167,12 @@ The return value of a child workflow is its output. If a child workflow fails wi Terminating a parent workflow terminates all of the child workflows created by the workflow instance. See [the terminate workflow api]({{% ref "workflow_api.md#terminate-workflow-request" %}}) for more information. +## Detached workflows + +In addition to [child workflows]({{% ref "#child-workflows" %}}), a workflow can schedule another workflow as a _detached workflow_: a fire-and-forget instance that runs fully independently of the caller. The call returns the new instance ID synchronously and the scheduling workflow continues immediately, without waiting for the detached workflow. Unlike a child workflow, there is no parent linkage: the detached workflow's success or failure does not flow back to the caller, and terminating the caller does not terminate the detached workflow. + +For the comparison with child workflows, guidance on when to use them, and a code example, see [Detached workflows]({{% ref workflow-detached.md %}}). + ## Durable timers Dapr Workflows allow you to schedule reminder-like durable delays for any time range, including minutes, days, or even years. These _durable timers_ can be scheduled by workflows to implement simple delays or to set up ad-hoc timeouts on other async tasks. More specifically, a durable timer can be set to trigger on a particular date or after a specified duration. There are no limits to the maximum duration of durable timers, which are internally backed by internal actor reminders. For example, a workflow that tracks a 30-day free subscription to a service could be implemented using a durable timer that fires 30-days after the workflow is created. Workflows can be safely unloaded from memory while waiting for a durable timer to fire. diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 3a2b69da68e..3f5e144f568 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -46,6 +46,12 @@ Child workflow also supports automatic retry policies. [Learn more about child workflows.]({{% ref "workflow-features-concepts.md#child-workflows" %}}) +### Detached workflows + +You can also schedule a new workflow as a _detached workflow_: a fire-and-forget instance that runs fully independently of the workflow that started it. The calling workflow receives the new instance ID immediately and continues, and the detached workflow's success or failure does not flow back to the caller. This is useful for triggering isolated, independent work, such as per-tenant fan-out. + +[Learn more about detached workflows.]({{% ref "workflow-detached.md" %}}) + ### Multi-application workflows Multi-application workflows, enable you to orchestrate complex business processes that span across multiple applications. This allows a workflow to call activities or start child workflows in different applications, distributing the workflow execution while maintaining the security, reliability and durability guarantees of Dapr's workflow engine. From 4551c068fdfbe71351869d2aac63cec7d890ad4f Mon Sep 17 00:00:00 2001 From: Albert Callarisa Date: Fri, 19 Jun 2026 06:46:42 +0200 Subject: [PATCH 2/2] Update daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md Co-authored-by: Marc Duiker Signed-off-by: Albert Callarisa --- .../building-blocks/workflow/workflow-detached.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md index 3231f8dcd93..f9f4b990568 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-detached.md @@ -6,7 +6,7 @@ weight: 2600 description: "Schedule fire-and-forget workflows that run independently of their caller" --- -Just like [child workflows]({{% ref "workflow-features-concepts.md#child-workflows" %}}), a workflow can schedule another workflow. With a _detached workflow_, however, the relationship is **fire-and-forget**: the calling workflow asks the runtime to start a new, fully independent workflow instance and then immediately continues with the rest of its own logic, without waiting for the detached workflow to complete. +In addition to [child workflows]({{% ref "#child-workflows" %}}), a workflow can schedule another workflow as a _detached workflow_. With a _detached workflow_, however, the relationship is **fire-and-forget**: the calling workflow asks the runtime to start a new, fully independent workflow instance and then immediately continues with the rest of its own logic, without waiting for the detached workflow to complete. When you schedule a detached workflow, the call returns the new instance ID synchronously. The detached workflow then runs as its own top-level instance, with its own instance ID, history, and lifecycle, exactly as if it had been scheduled directly by a client. There is no parent linkage: the detached workflow's success or failure does not flow back to the caller, and terminating the caller does not terminate the detached workflow.