Workflow history propagation in the .NET SDK

Share ancestor workflow execution history with child workflows and activities using the .NET SDK

Overview

Workflow history propagation allows a parent workflow to share its execution history — and optionally its full ancestor chain — with the child workflows and activities it calls. The child can then inspect those upstream events at runtime.

Common use cases include:

  • Audit trails: Verifying a chain of custody across a multi-step workflow
  • Fraud detection: Inspecting upstream decisions before committing a transaction
  • AI agent orchestration: Passing context through hierarchical agent workflows

Conceptual guidance is covered in Workflow history propagation.

Propagation scopes

Propagation is opt-in and per-call. Each call to CallActivityAsync or CallChildWorkflowAsync can independently specify a HistoryPropagationScope:

ScopeDescription
NoneDefault. No history is propagated to the callee.
OwnHistoryPropagates the calling workflow’s own events only. Ancestor history is dropped, acting as a trust boundary.
LineagePropagates the calling workflow’s events plus the full ancestor chain it inherited from its own parent.

Propagate history to a child workflow

Use WithHistoryPropagation on ChildWorkflowTaskOptions to opt a child workflow into receiving the parent’s history:

public sealed class MerchantCheckoutWorkflow : Workflow<Order, CheckoutResult>
{
    public override async Task<CheckoutResult> RunAsync(WorkflowContext context, Order order)
    {
        // Activity without propagation — default behavior, no opt-in
        await context.CallActivityAsync(nameof(ValidateMerchantActivity), order.MerchantId);

        // Child workflow with full lineage propagation
        var options = new ChildWorkflowTaskOptions()
            .WithHistoryPropagation(HistoryPropagationScope.Lineage);

        var result = await context.CallChildWorkflowAsync<PaymentResult>(
            nameof(ProcessPaymentWorkflow), order, options);

        return new CheckoutResult(result);
    }
}

Calls that do not specify a propagation scope receive no history — other calls in the same workflow are unaffected by opt-ins.

Propagate history to an activity

The same WithHistoryPropagation extension is available on WorkflowTaskOptions for activity calls:

var options = new WorkflowTaskOptions()
    .WithHistoryPropagation(HistoryPropagationScope.OwnHistory);

var auditResult = await context.CallActivityAsync<AuditResult>(
    nameof(WriteAuditTrailActivity), payload, options);

Read propagated history

Inside a child workflow, call GetPropagatedHistory() on WorkflowContext to retrieve the history passed by the parent. The method returns null if propagation was not requested for this invocation.

public sealed class ProcessPaymentWorkflow : Workflow<Order, PaymentResult>
{
    public override async Task<PaymentResult> RunAsync(WorkflowContext context, Order order)
    {
        var history = context.GetPropagatedHistory();

        if (history != null)
        {
            foreach (var evt in history.Events)
            {
                // evt.Name, evt.InstanceId, evt.AppId
                // evt.Activities — activity results for this ancestor
                // evt.Workflows  — child workflow results for this ancestor
            }
        }

        return await context.CallActivityAsync<PaymentResult>(
            nameof(ChargeCardActivity), order);
    }
}

PropagatedHistory type

GetPropagatedHistory() returns a PropagatedHistory object (or null). Its Events property is an ordered list of PropagatedHistoryEvent values — one per ancestor workflow, in execution order (oldest ancestor first, immediate parent last).

Each PropagatedHistoryEvent represents a single ancestor workflow’s contribution to the propagated history:

MemberTypeDescription
AppIdstringDapr app ID that ran this workflow
InstanceIdstringWorkflow instance ID
NamestringThe name of the workflow
ActivitiesIReadOnlyList<PropagatedHistoryActivityResult>Activity results for this workflow, in execution order
WorkflowsIReadOnlyList<PropagatedHistoryWorkflowResult>Child workflow results for this workflow, in execution order

PropagatedHistoryActivityResult type

Each PropagatedHistoryActivityResult is a sealed record describing a single activity invocation:

MemberTypeDescription
NamestringThe scheduled name of the activity
StatusPropagatedHistoryStatusLifecycle status — Pending, Completed, or Failed
Inputstring?JSON-encoded input payload, or null when unset
Outputstring?JSON-encoded output payload, or null when the activity has not completed
FailureDetailsWorkflowTaskFailureDetails?Failure details when Status is Failed, otherwise null

PropagatedHistoryWorkflowResult type

Each PropagatedHistoryWorkflowResult is a sealed record describing a single child workflow invocation:

MemberTypeDescription
NamestringThe scheduled name of the child workflow
StatusPropagatedHistoryStatusLifecycle status — Pending, Completed, or Failed
Outputstring?JSON-encoded output payload, or null when the workflow has not completed
FailureDetailsWorkflowTaskFailureDetails?Failure details when Status is Failed, otherwise null

PropagatedHistoryStatus enum

PropagatedHistoryStatus reflects how far a task progressed past scheduling:

ValueDescription
PendingThe task was scheduled but has not yet completed or failed
CompletedThe task completed successfully
FailedThe task failed

Query propagated history

PropagatedHistory query methods

PropagatedHistory provides Get methods that return lists and TryGet methods that return a single match (the most recent) via an out parameter. All Get methods return an empty list when no match is found.

MethodReturn typeDescription
GetByAppId(string)IReadOnlyList<PropagatedHistoryEvent>All events from the given Dapr app ID
GetByInstanceId(string)IReadOnlyList<PropagatedHistoryEvent>All events from the given workflow instance ID
GetEventsByWorkflowName(string)IReadOnlyList<PropagatedHistoryEvent>All events with the given workflow name
TryGetLastWorkflowEventByName(string, out PropagatedHistoryEvent?)boolGets the most recent event matching the workflow name
GetAppIds()IReadOnlyList<string>Ordered, deduplicated list of app IDs in the history

PropagatedHistoryEvent query methods

Each PropagatedHistoryEvent also provides query methods to inspect the activities and child workflows within that ancestor:

MethodReturn typeDescription
GetActivitiesByName(string)IReadOnlyList<PropagatedHistoryActivityResult>All activities matching the given name
TryGetLastActivityByName(string, out PropagatedHistoryActivityResult?)boolGets the most recent activity matching the name
GetWorkflowsByName(string)IReadOnlyList<PropagatedHistoryWorkflowResult>All child workflows matching the given name
TryGetLastWorkflowByName(string, out PropagatedHistoryWorkflowResult?)boolGets the most recent child workflow matching the name

Example

var history = context.GetPropagatedHistory();

if (history != null)
{
    // By app ID — useful in multi-app workflows
    var fromOrderApp = history.GetByAppId("order-app");

    // By workflow instance ID
    var fromSpecificRun = history.GetByInstanceId("checkout-abc123");

    // By workflow name — returns all matches (e.g. recursion or ContinueAsNew)
    var checkoutEvents = history.GetEventsByWorkflowName(nameof(MerchantCheckoutWorkflow));

    // TryGet for a single match — avoids null ambiguity
    if (history.TryGetLastWorkflowEventByName(nameof(MerchantCheckoutWorkflow), out var parentEvent))
    {
        // Inspect the parent's activities
        var failedActivities = parentEvent.Activities
            .Where(a => a.Status == PropagatedHistoryStatus.Failed)
            .ToList();

        // Or look up a specific activity by name
        if (parentEvent.TryGetLastActivityByName(nameof(ValidateMerchantActivity), out var validation))
        {
            // validation.Status, validation.Output, validation.FailureDetails
        }
    }
}

Security considerations

By default, Dapr uses mutual TLS (mTLS) between sidecars for all cross-app communication, providing transport-layer protection for propagated history in multi-app workflow scenarios.

For stronger guarantees in production, enable WorkflowHistorySigning. This feature uses SPIFFE identity to cryptographically sign each history chunk, so the receiving workflow can verify the integrity and origin of the propagated history. Without signing enabled, Dapr emits a warning that propagated chunks lack cryptographic verification.

See Workflow history propagation for details on configuring WorkflowHistorySigning.

Next steps