Detached workflows

Schedule fire-and-forget workflows that run independently of their caller

In addition to 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.

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, 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.

Detached workflows compared to child workflows

Child workflowDetached workflow
Relationship to callerCoupled to the caller’s lifecycleFully independent (fire-and-forget)
Caller behaviorReceives an awaitable task and typically awaits its completionReceives the new instance ID synchronously and continues immediately
Return value and errorsOutput and exceptions surface back to the callerNothing flows back to the caller
Parent terminationTerminating the caller terminates the child workflowTerminating the caller has no effect on the detached workflow
Retry policiesSupportedNot 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 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.

// ParentWorkflow schedules a detached workflow and continues immediately,
// without waiting for it to finish.
func ParentWorkflow(ctx *workflow.WorkflowContext) (any, error) {
	// ScheduleNewDetachedWorkflow starts a detached, fire-and-forget workflow
	// and returns its instance ID synchronously. There is no task to await.
	instanceID, err := ctx.ScheduleNewDetachedWorkflow(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
}

ScheduleNewDetachedWorkflowAsync takes the workflow name and optional instanceId, input, and startTime arguments. When instanceId is omitted, the SDK generates a deterministic GUID. The .NET SDK does not take an app ID or app namespace for detached workflows.

// ParentWorkflow schedules a detached workflow and continues immediately,
// without waiting for it to finish.
public sealed class ParentWorkflow : Workflow<string, string>
{
    public override async Task<string> RunAsync(WorkflowContext context, string orderId)
    {
        // Returns the new instance ID. There is no result to await.
        string instanceId = await context.ScheduleNewDetachedWorkflowAsync(
            nameof(AuditWorkflow), input: orderId);
        return instanceId;
    }
}

// AuditWorkflow runs independently as its own top-level workflow instance.
public sealed class AuditWorkflow : Workflow<string, string>
{
    public override async Task<string> RunAsync(WorkflowContext context, string orderId)
    {
        await context.CreateTimer(TimeSpan.FromSeconds(3));
        return $"audit completed for {orderId}";
    }
}

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.

OptionDescription
Instance IDThe instance ID of the detached workflow. When omitted, a deterministic ID of the form <caller-instance-id>-<n> is generated.
InputThe input passed to the detached workflow.
Start timeDefers the start of the detached workflow until the given time.
App IDSchedules the detached workflow on a different application by target app ID, subject to the multi-application workflow rules.
App namespaceThe 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.