Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0fc9c08
Ignore repo worktrees
torosent Mar 31, 2026
0307de4
feat: add orchestrator version attribute
torosent Apr 2, 2026
b64fa00
fix: align version attribute tests with spec
torosent Apr 2, 2026
1ca9f89
feat: store orchestrator registrations by version
torosent Apr 2, 2026
fc09993
fix: preserve unversioned worker registry behavior
torosent Apr 2, 2026
f9c2743
fix: keep task 2 worker changes compile-safe
torosent Apr 2, 2026
2e12c4e
docs: clarify staged task 2 versioning behavior
torosent Apr 2, 2026
57698dc
feat: route orchestrators by scheduled version
torosent Apr 2, 2026
3911f61
feat: include orchestrator versions in work item filters
torosent Apr 2, 2026
12cf3ab
Add versioned standalone generator helpers
torosent Apr 2, 2026
a875f47
Make versioned helper grouping case-insensitive
torosent Apr 2, 2026
f6d25a0
Add Azure Functions duplicate orchestrator diagnostic
torosent Apr 2, 2026
99d81a8
chore: clean generator diagnostics metadata
torosent Apr 2, 2026
179fff0
fix: detect mixed azure functions collisions
torosent Apr 2, 2026
5f9c5f4
test: add versioned class syntax coverage
torosent Apr 2, 2026
cccce85
fix: preserve unversioned version dispatch
torosent Apr 2, 2026
09fc7f2
test: cover mixed orchestrator version fallback
torosent Apr 2, 2026
b90aef5
docs: add versioning sample design spec
torosent Apr 2, 2026
91d6ede
docs: add DTS versioning sample plan
torosent Apr 2, 2026
047aea9
samples: add worker-level versioning demo
torosent Apr 2, 2026
3789699
feat: add DTS versioning sample apps
torosent Apr 2, 2026
433412b
feat: add ContinueAsNew migration demo with loop guard
torosent Apr 2, 2026
a193237
feat: add ActivityVersionKey and version-aware activity registry
torosent Apr 2, 2026
96f80d9
feat: version-aware activity dispatch and work-item filters
torosent Apr 2, 2026
d973a65
feat: complete activity versioning support
torosent Apr 2, 2026
6c0732c
feat: add activity version options
torosent Apr 2, 2026
4d3febf
docs: correct activity options summary
torosent Apr 2, 2026
f762183
feat: support explicit activity version overrides
torosent Apr 2, 2026
6c3fe3b
feat: stamp generated activity helper versions
torosent Apr 2, 2026
bf28e2e
test: cover activity retry handler override
torosent Apr 2, 2026
ac3229b
refactor: narrow generated activity helper emission
torosent Apr 2, 2026
0720a16
samples: add activity versioning sample
torosent Apr 2, 2026
543732d
docs: update README for Durable Task Scheduler usage and clarify docu…
torosent Apr 2, 2026
2381e5b
fix: finalize versioning review follow-ups
torosent Apr 2, 2026
ca9efc8
Merge branch 'main' into torosent/orchestration-versioning
torosent Apr 2, 2026
08e6c4a
chore: import protobuf contract update
torosent Apr 2, 2026
5f9dc39
chore: refresh protobuf import from main
torosent Apr 2, 2026
6386215
Merge branch 'main' into torosent/orchestration-versioning
torosent Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ MigrationBackup/

# Rider (cross platform .NET/C# tools) working folder
.idea/
.worktrees/
AzuriteConfig
__azurite_db_*
__blobstorage__
55 changes: 52 additions & 3 deletions Microsoft.DurableTask.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32901.215
Expand Down Expand Up @@ -115,6 +115,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NamespaceGenerationSample",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReplaySafeLoggerFactorySample", "samples\ReplaySafeLoggerFactorySample\ReplaySafeLoggerFactorySample.csproj", "{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerVersioningSample", "samples\WorkerVersioningSample\WorkerVersioningSample.csproj", "{26988639-D204-4E0B-80BE-F4E11952DFF8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureManaged", "AzureManaged", "{D4587EC0-1B16-8420-7502-A967139249D4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureManaged", "AzureManaged", "{53193780-CD18-2643-6953-C26F59EAEDF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerOrchestratorVersioningSample", "samples\PerOrchestratorVersioningSample\PerOrchestratorVersioningSample.csproj", "{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActivityVersioningSample", "samples\ActivityVersioningSample\ActivityVersioningSample.csproj", "{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -701,7 +711,42 @@ Global
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}.Release|x64.Build.0 = Release|Any CPU
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}.Release|x86.ActiveCfg = Release|Any CPU
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}.Release|x86.Build.0 = Release|Any CPU

{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x64.ActiveCfg = Debug|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x64.Build.0 = Debug|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x86.ActiveCfg = Debug|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x86.Build.0 = Debug|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|Any CPU.Build.0 = Release|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x64.ActiveCfg = Release|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x64.Build.0 = Release|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x86.ActiveCfg = Release|Any CPU
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x86.Build.0 = Release|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x64.ActiveCfg = Debug|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x64.Build.0 = Debug|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x86.ActiveCfg = Debug|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x86.Build.0 = Debug|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|Any CPU.Build.0 = Release|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x64.ActiveCfg = Release|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x64.Build.0 = Release|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x86.ActiveCfg = Release|Any CPU
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x86.Build.0 = Release|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x64.ActiveCfg = Debug|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x64.Build.0 = Debug|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x86.ActiveCfg = Debug|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x86.Build.0 = Debug|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|Any CPU.Build.0 = Release|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x64.ActiveCfg = Release|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x64.Build.0 = Release|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x86.ActiveCfg = Release|Any CPU
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -759,7 +804,11 @@ Global
{4A7305AE-AAAE-43AE-AAB2-DA58DACC6FA8} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{5A69FD28-D814-490E-A76B-B0A5F88C25B2} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}

{26988639-D204-4E0B-80BE-F4E11952DFF8} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{D4587EC0-1B16-8420-7502-A967139249D4} = {1C217BB2-CE16-41CC-9D47-0FC0DB60BDB3}
{53193780-CD18-2643-6953-C26F59EAEDF5} = {5B448FF6-EC42-491D-A22E-1DC8B618E6D5}
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Expand Down
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,50 @@ public class SayHelloTyped : TaskActivity<string, string>

You can find the full sample file, including detailed comments, at [samples/AzureFunctionsApp/HelloCitiesTyped.cs](samples/AzureFunctionsApp/HelloCitiesTyped.cs).

### Versioned class-based orchestrators (standalone worker)

Standalone worker projects can register multiple class-based orchestrators under the same durable task name when each class declares a unique `[DurableTaskVersion]`. Start a specific implementation by setting `StartOrchestrationOptions.Version`.

```csharp
[DurableTask("OrderWorkflow")]
[DurableTaskVersion("v1")]
public sealed class OrderWorkflowV1 : TaskOrchestrator<int, string>
{
public override Task<string> RunAsync(TaskOrchestrationContext context, int input)
=> Task.FromResult($"v1:{input}");
}

[DurableTask("OrderWorkflow")]
[DurableTaskVersion("v2")]
public sealed class OrderWorkflowV2 : TaskOrchestrator<int, string>
{
public override Task<string> RunAsync(TaskOrchestrationContext context, int input)
=> Task.FromResult($"v2:{input}");
}

string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
"OrderWorkflow",
input: 5,
new StartOrchestrationOptions { Version = new TaskVersion("v2") });
```

Use `ContinueAsNewOptions.NewVersion` to migrate long-running orchestrations at a replay-safe boundary.

> Do not combine per-orchestrator `[DurableTaskVersion]` routing with `DurableTaskWorkerOptions.Versioning` (or `UseVersioning(...)`). Both features use the orchestration instance version field, so worker-level version checks can reject per-orchestrator versions before class-based routing occurs.
>
> Azure Functions projects do not support same-name multi-version class-based orchestrators in v1. The source generator reports a diagnostic instead of generating colliding triggers.

### Compatibility with Durable Functions in-process

This SDK is *not* compatible with Durable Functions for the .NET *in-process* worker. It only works with the newer out-of-process .NET Isolated worker.

## Usage with the Durable Task Scheduler
## Usage with Durable Task Scheduler

Durable Task Scheduler provides durable execution in Azure. Durable execution is a fault-tolerant approach to running code that handles failures and interruptions through automatic retries and state persistence.

The Durable Task Scheduler for Azure Functions is a managed backend that is currently in preview. Durable Functions apps can use the Durable Task Scheduler as one of its [supported storage providers](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-storage-providers).
This SDK can also be used with the Durable Task Scheduler directly, without any Durable Functions dependency. For getting started, you can find documentation and samples [here](https://learn.microsoft.com/en-us/azure/azure-functions/durable/what-is-durable-task).

This SDK can also be used with the Durable Task Scheduler directly, without any Durable Functions dependency. To get started, sign up for the [Durable Task Scheduler private preview](https://techcommunity.microsoft.com/blog/appsonazureblog/announcing-limited-early-access-of-the-durable-task-scheduler-for-azure-durable-/4286526) and follow the instructions to create a new Durable Task Scheduler instance. Once granted access to the private preview GitHub repository, you can find samples and documentation for getting started [here](https://github.com/Azure/Azure-Functions-Durable-Task-Scheduler-Private-Preview/tree/main/samples/portable-sdk/dotnet/AspNetWebApp#readme).
For runnable DTS emulator examples that demonstrate versioning, see the [WorkerVersioningSample](samples/WorkerVersioningSample/README.md) (deployment-based versioning), the [PerOrchestratorVersioningSample](samples/PerOrchestratorVersioningSample/README.md) (multi-version routing with `[DurableTaskVersion]`), and the [ActivityVersioningSample](samples/ActivityVersioningSample/README.md) (activity versioning with inherited defaults and explicit override support).

## Obtaining the Protobuf definitions

Expand Down
23 changes: 23 additions & 0 deletions samples/ActivityVersioningSample/ActivityVersioningSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

<ItemGroup>
<!-- Using p2p references so we can show latest changes in samples. -->
<ProjectReference Include="$(SrcRoot)Client/AzureManaged/Client.AzureManaged.csproj" />
<ProjectReference Include="$(SrcRoot)Worker/AzureManaged/Worker.AzureManaged.csproj" />
<ProjectReference Include="$(SrcRoot)Analyzers/Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(SrcRoot)Generators/Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
154 changes: 154 additions & 0 deletions samples/ActivityVersioningSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// This sample demonstrates activity versioning with [DurableTaskVersion].
// Versioned orchestrators and versioned activities can share the same logical
// durable task names in one worker process. Plain activity calls inherit the
// orchestration instance version by default, while version-qualified helpers
// can explicitly override that routing when needed.

using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Read the DTS connection string from configuration.
string connectionString = builder.Configuration.GetValue<string>("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? throw new InvalidOperationException(
"Set DURABLE_TASK_SCHEDULER_CONNECTION_STRING. " +
"For the local emulator: Endpoint=http://localhost:8080;TaskHub=default;Authentication=None");

// AddAllGeneratedTasks() registers every [DurableTask]-annotated class in this
// project, including both versions of the orchestration and activity classes.
builder.Services.AddDurableTaskWorker(wb =>
{
wb.AddTasks(tasks => tasks.AddAllGeneratedTasks());
wb.UseDurableTaskScheduler(connectionString);
});

// Configure the client. Unlike worker-level versioning, the client does not
// stamp a single default version for every instance.
builder.Services.AddDurableTaskClient(cb => cb.UseDurableTaskScheduler(connectionString));

IHost host = builder.Build();
await host.StartAsync();

await using DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>();

Console.WriteLine("=== Activity versioning ([DurableTaskVersion]) ===");
Console.WriteLine();

Console.WriteLine("Scheduling CheckoutWorkflow v1 ...");
string v1Id = await client.ScheduleNewCheckoutWorkflow_1InstanceAsync(5);
OrchestrationMetadata v1 = await client.WaitForInstanceCompletionAsync(v1Id, getInputsAndOutputs: true);
Console.WriteLine($" Result: {v1.ReadOutputAs<string>()}");
Console.WriteLine();

Console.WriteLine("Scheduling CheckoutWorkflow v2 ...");
string v2Id = await client.ScheduleNewCheckoutWorkflow_2InstanceAsync(5);
OrchestrationMetadata v2 = await client.WaitForInstanceCompletionAsync(v2Id, getInputsAndOutputs: true);
Console.WriteLine($" Result: {v2.ReadOutputAs<string>()}");
Console.WriteLine();

Console.WriteLine("Scheduling CheckoutWorkflow v2 with explicit ShippingQuote v1 override ...");
string overrideId = await client.ScheduleNewOrchestrationInstanceAsync(
"ExplicitOverrideCheckoutWorkflow",
input: 5,
new StartOrchestrationOptions
{
Version = new TaskVersion("2"),
});
OrchestrationMetadata overrideResult = await client.WaitForInstanceCompletionAsync(overrideId, getInputsAndOutputs: true);
Console.WriteLine($" Result: {overrideResult.ReadOutputAs<string>()}");
Console.WriteLine();

Console.WriteLine("Done! Both versions ran in the same worker process.");
Console.WriteLine("Default activity calls inherit the orchestration version, but versioned helpers can explicitly override it.");

await host.StopAsync();

/// <summary>
/// CheckoutWorkflow v1 - default activity calls inherit orchestration version "1".
/// </summary>
[DurableTask("CheckoutWorkflow")]
[DurableTaskVersion("1")]
public sealed class CheckoutWorkflowV1 : TaskOrchestrator<int, string>
{
/// <inheritdoc />
public override async Task<string> RunAsync(TaskOrchestrationContext context, int itemCount)
{
string quote = await context.CallActivityAsync<string>("ShippingQuote", itemCount);
return $"Workflow v1 -> {quote}";
}
}

/// <summary>
/// CheckoutWorkflow v2 - default activity calls inherit orchestration version "2".
/// </summary>
[DurableTask("CheckoutWorkflow")]
[DurableTaskVersion("2")]
public sealed class CheckoutWorkflowV2 : TaskOrchestrator<int, string>
{
/// <inheritdoc />
public override async Task<string> RunAsync(TaskOrchestrationContext context, int itemCount)
{
string quote = await context.CallActivityAsync<string>("ShippingQuote", itemCount);
return $"Workflow v2 -> {quote}";
}
}

/// <summary>
/// CheckoutWorkflow v2 - explicitly overrides the inherited activity version.
/// </summary>
[DurableTask("ExplicitOverrideCheckoutWorkflow")]
[DurableTaskVersion("2")]
public sealed class ExplicitOverrideCheckoutWorkflowV2 : TaskOrchestrator<int, string>
{
/// <inheritdoc />
public override async Task<string> RunAsync(TaskOrchestrationContext context, int itemCount)
{
string quote = await context.CallShippingQuote_1Async(itemCount);
return $"Workflow v2 explicit override -> {quote}";
}
}

/// <summary>
/// ShippingQuote v1 - uses a flat shipping charge.
/// </summary>
[DurableTask("ShippingQuote")]
[DurableTaskVersion("1")]
public sealed class ShippingQuoteV1 : TaskActivity<int, string>
{
/// <inheritdoc />
public override Task<string> RunAsync(TaskActivityContext context, int itemCount)
{
int total = (itemCount * 10) + 7;
return Task.FromResult($"activity v1 quote: ${total} (flat $7 shipping)");
}
}

/// <summary>
/// ShippingQuote v2 - applies a bulk discount and cheaper shipping.
/// </summary>
[DurableTask("ShippingQuote")]
[DurableTaskVersion("2")]
public sealed class ShippingQuoteV2 : TaskActivity<int, string>
{
/// <inheritdoc />
public override Task<string> RunAsync(TaskActivityContext context, int itemCount)
{
int total = (itemCount * 10) + 5;
if (itemCount >= 5)
{
total -= 10;
}

return Task.FromResult($"activity v2 quote: ${total} ($10 bulk discount + $5 shipping)");
}
}
71 changes: 71 additions & 0 deletions samples/ActivityVersioningSample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Activity Versioning Sample

This sample demonstrates activity versioning with `[DurableTaskVersion]`, where multiple implementations of the same logical activity name coexist in one worker process and can be selected either by the orchestration instance version or by an explicit version-qualified helper.

## What it shows

- Two classes share the same `[DurableTask("ShippingQuote")]` name but have different `[DurableTaskVersion]` values
- Two versions of `CheckoutWorkflow` call the same logical activity name in one worker process using the default inherited-routing behavior
- The orchestration instance version is still the default for activity scheduling, so `CheckoutWorkflow` v1 routes to `ShippingQuote` v1 and `CheckoutWorkflow` v2 routes to `ShippingQuote` v2
- Version-qualified activity helpers like `CallShippingQuote_1Async()` and `CallShippingQuote_2Async()` now explicitly select those versions when called from an orchestration
- A third orchestration demonstrates explicitly overriding a `v2` orchestration to call the `ShippingQuote` v1 helper
- `AddAllGeneratedTasks()` registers both orchestration and activity versions automatically

## Prerequisites

- .NET 8.0 or 10.0 SDK
- [Docker](https://www.docker.com/get-started)

## Running the Sample

### 1. Start the DTS emulator

```bash
docker run --name durabletask-emulator -d -p 8080:8080 -e ASPNETCORE_URLS=http://+:8080 mcr.microsoft.com/dts/dts-emulator:latest
```

### 2. Set the connection string

```bash
export DURABLE_TASK_SCHEDULER_CONNECTION_STRING="Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
```

### 3. Run the sample

```bash
dotnet run
```

Expected output:

```text
=== Activity versioning ([DurableTaskVersion]) ===

Scheduling CheckoutWorkflow v1 ...
Result: Workflow v1 -> activity v1 quote: $57 (flat $7 shipping)

Scheduling CheckoutWorkflow v2 ...
Result: Workflow v2 -> activity v2 quote: $45 ($10 bulk discount + $5 shipping)

Scheduling CheckoutWorkflow v2 with explicit ShippingQuote v1 override ...
Result: Workflow v2 explicit override -> activity v1 quote: $57 (flat $7 shipping)

Done! Both versions ran in the same worker process.
Default activity calls inherit the orchestration version, but versioned helpers can explicitly override it.
```

### 4. Clean up

```bash
docker rm -f durabletask-emulator
```

## When to use this approach

Activity versioning is useful when:

- You need orchestration and activity behavior to evolve together across versions
- You want multiple versions of the same logical activity active simultaneously in one worker
- You want activity routing to follow the orchestration instance version by default, with explicit opt-in overrides when needed

For deployment-based versioning, see the [WorkerVersioningSample](../WorkerVersioningSample/README.md). For the orchestration-focused version of this pattern, see the [PerOrchestratorVersioningSample](../PerOrchestratorVersioningSample/README.md).
Loading
Loading