diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 98c7376aaf..556d0a61d8 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -113,14 +113,14 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 20552473b7..6c564d81b8 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -52,6 +52,13 @@
+
+
+
+
+
+
+
@@ -403,7 +410,6 @@
-
@@ -411,6 +417,7 @@
+
@@ -422,8 +429,8 @@
-
+
@@ -433,8 +440,8 @@
-
+
@@ -448,13 +455,13 @@
-
+
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj
new file mode 100644
index 0000000000..1c3ef2504a
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj
@@ -0,0 +1,42 @@
+
+
+ net10.0
+ v4
+ Exe
+ enable
+ enable
+
+ SequentialWorkflowFunctionApp
+ SequentialWorkflowFunctionApp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/Function.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/Function.cs
new file mode 100644
index 0000000000..dd8de6e553
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/Function.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Logging;
+
+namespace SequentialWorkflowFunctionApp;
+
+public class Function
+{
+ private readonly ILogger _logger;
+
+ public Function(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ [Function("Function")]
+ public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
+ {
+ _logger.LogInformation("C# HTTP trigger function processed a request.");
+ return new OkObjectResult("Welcome to Azure Functions!");
+ }
+}
\ No newline at end of file
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/OrderCancelExecutors.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/OrderCancelExecutors.cs
new file mode 100644
index 0000000000..39f5051a8c
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/OrderCancelExecutors.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace SequentialWorkflow;
+
+///
+/// Parses an Order ID from a string input and returns an Order object populated.
+///
+internal sealed class OrderLookup() : Executor("OrderLookup")
+{
+ public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ // Populate Order information from OrderId.
+ return new Order(message, 100.0m);
+ }
+}
+
+///
+/// Enriches an Order object with additional information.
+///
+internal sealed class OrderEnrich() : Executor("EnrichOrder")
+{
+ public override async ValueTask HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ if (message.Customer is null)
+ {
+ // populate customer information for the order from database.
+ message.Customer = new Customer(1, "Jerry");
+ }
+
+ return message;
+ }
+}
+
+internal sealed class PaymentProcessor() : Executor("ProcessPayment")
+{
+ public override async ValueTask HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ message.PaymentReferenceNumber = Guid.NewGuid().ToString()[^4..];
+
+ return message;
+ }
+}
+
+internal sealed class Order
+{
+ public Order(string id, decimal amount)
+ {
+ this.Id = id;
+ this.Amount = amount;
+ }
+ public string Id { get; }
+ public decimal Amount { get; }
+ public Customer? Customer { get; set; }
+ public string? PaymentReferenceNumber { get; set; }
+}
+
+public sealed record Customer(int Id, string Name);
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/Program.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/Program.cs
new file mode 100644
index 0000000000..8b6b457da5
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/Program.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Agents.AI.Hosting.AzureFunctions;
+using Microsoft.Extensions.Hosting;
+using SequentialWorkflow;
+
+OrderLookup orderLookupExecutor = new();
+OrderEnrich orderEnricherExeecutor = new();
+PaymentProcessor paymentProcessorExecutor = new();
+
+Workflow fulfillOrder = new WorkflowBuilder(orderLookupExecutor)
+ .WithName("FulfillOrder")
+ .WithDescription("Looks up an order by ID and run payment processing")
+ .AddEdge(orderLookupExecutor, orderEnricherExeecutor)
+ .AddEdge(orderEnricherExeecutor, paymentProcessorExecutor)
+ .Build();
+
+// Configure the function app to host the AI agent.
+// This will automatically generate HTTP API endpoints for the agent.
+using IHost app = FunctionsApplication
+ .CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableOptions(durableOption =>
+ {
+ // Add a workflow.
+ durableOption.Workflows.AddWorkflow(fulfillOrder);
+ })
+ .Build();
+app.Run();
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/demo.http b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/demo.http
new file mode 100644
index 0000000000..ec607e0f8d
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/demo.http
@@ -0,0 +1,8 @@
+# Default endpoint address for local testing
+@authority=http://localhost:7071
+
+### Prompt the agent
+POST {{authority}}/api/workflows/FulfillOrder/run
+Content-Type: text/plain
+
+987
\ No newline at end of file
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/host.json b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/01_SequentialWorkflow/host.json
@@ -0,0 +1,20 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Microsoft.Agents.AI.DurableTask": "Information",
+ "Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
+ "DurableTask": "Information",
+ "Microsoft.DurableTask": "Information"
+ }
+ },
+ "extensions": {
+ "durableTask": {
+ "hubName": "default",
+ "storageProvider": {
+ "type": "AzureManaged",
+ "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj
new file mode 100644
index 0000000000..1c3ef2504a
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj
@@ -0,0 +1,42 @@
+
+
+ net10.0
+ v4
+ Exe
+ enable
+ enable
+
+ SequentialWorkflowFunctionApp
+ SequentialWorkflowFunctionApp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/ExpertExecutors.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/ExpertExecutors.cs
new file mode 100644
index 0000000000..40674126f6
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/ExpertExecutors.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace WorkflowConcurrency;
+
+///
+/// Parses and validates the incoming question before sending to AI agents.
+///
+internal sealed class ParseQuestionExecutor() : Executor("ParseQuestion")
+{
+ public override ValueTask HandleAsync(
+ string message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Magenta;
+ Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
+ Console.WriteLine("│ [ParseQuestion] Preparing question for AI agents...");
+
+ string formattedQuestion = message.Trim();
+ if (!formattedQuestion.EndsWith('?'))
+ {
+ formattedQuestion += "?";
+ }
+
+ Console.WriteLine($"│ [ParseQuestion] Question: \"{formattedQuestion}\"");
+ Console.WriteLine("│ [ParseQuestion] → Sending to Physicist and Chemist in PARALLEL...");
+ Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
+ Console.ResetColor();
+
+ return ValueTask.FromResult(formattedQuestion);
+ }
+}
+
+///
+/// Aggregates responses from all AI agents into a comprehensive answer.
+/// This is the Fan-in point where parallel results are collected.
+///
+internal sealed class AggregatorExecutor() : Executor("Aggregator")
+{
+ public override ValueTask HandleAsync(
+ string[] message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
+ Console.WriteLine($"│ [Aggregator] 📋 Received {message.Length} AI agent responses");
+ Console.WriteLine("│ [Aggregator] Combining into comprehensive answer...");
+ Console.WriteLine("│ [Aggregator] ✓ Aggregation complete!");
+ Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
+ Console.ResetColor();
+
+ string aggregatedResult = "═══════════════════════════════════════════════════════════════\n" +
+ " AI EXPERT PANEL RESPONSES\n" +
+ "═══════════════════════════════════════════════════════════════\n\n";
+
+ for (int i = 0; i < message.Length; i++)
+ {
+ string expertLabel = i == 0 ? "⚛️ PHYSICIST" : "🧪 CHEMIST";
+ aggregatedResult += $"{expertLabel}:\n{message[i]}\n\n";
+ }
+
+ aggregatedResult += "═══════════════════════════════════════════════════════════════\n" +
+ $"Summary: Received perspectives from {message.Length} AI experts.\n" +
+ "═══════════════════════════════════════════════════════════════";
+
+ return ValueTask.FromResult(aggregatedResult);
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/Program.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/Program.cs
new file mode 100644
index 0000000000..734ba1b46a
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/Program.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hosting.AzureFunctions;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+using OpenAI.Chat;
+using WorkflowConcurrency;
+
+// Configuration
+string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
+ ?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
+string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
+ ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")
+ ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT is not set.");
+string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
+
+// Create Azure OpenAI client
+AzureOpenAIClient openAiClient = !string.IsNullOrEmpty(azureOpenAiKey)
+ ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
+ : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
+ChatClient chatClient = openAiClient.GetChatClient(deploymentName);
+
+// Define the 4 executors for the workflow
+ParseQuestionExecutor parseQuestion = new();
+AIAgent physicist = chatClient.AsAIAgent("You are a physics expert. Be concise (2-3 sentences).", "Physicist");
+AIAgent chemist = chatClient.AsAIAgent("You are a chemistry expert. Be concise (2-3 sentences).", "Chemist");
+AggregatorExecutor aggregator = new();
+
+// Build workflow: ParseQuestion -> [Physicist, Chemist] (parallel) -> Aggregator
+Workflow workflow = new WorkflowBuilder(parseQuestion)
+ .WithName("ExpertReview")
+ .AddFanOutEdge(parseQuestion, [physicist, chemist])
+ .AddFanInEdge([physicist, chemist], aggregator)
+ .Build();
+
+// Configure the function app to host the AI agent.
+// This will automatically generate HTTP API endpoints for the agent.
+using IHost app = FunctionsApplication
+ .CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableOptions(durableOption =>
+ {
+ // Add a workflow.
+ durableOption.Workflows.AddWorkflow(workflow);
+ })
+ .Build();
+app.Run();
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/demo.http b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/demo.http
new file mode 100644
index 0000000000..3129388a27
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/demo.http
@@ -0,0 +1,8 @@
+# Default endpoint address for local testing
+@authority=http://localhost:7071
+
+### Prompt the agent
+POST {{authority}}/api/workflows/ExpertReview/run
+Content-Type: text/plain
+
+What is saturation?
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/host.json b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/02_ConcurrentWorkflow/host.json
@@ -0,0 +1,20 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Microsoft.Agents.AI.DurableTask": "Information",
+ "Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
+ "DurableTask": "Information",
+ "Microsoft.DurableTask": "Information"
+ }
+ },
+ "extensions": {
+ "durableTask": {
+ "hubName": "default",
+ "storageProvider": {
+ "type": "AzureManaged",
+ "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/03_ConditionalEdges.csproj b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/03_ConditionalEdges.csproj
new file mode 100644
index 0000000000..1add0fdc50
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/03_ConditionalEdges.csproj
@@ -0,0 +1,39 @@
+
+
+ net10.0
+ v4
+ Exe
+ enable
+ enable
+
+ ConditionalEdgesFunctionApp
+ ConditionalEdgesFunctionApp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/OrderExecutors.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/OrderExecutors.cs
new file mode 100644
index 0000000000..4d0518955a
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/OrderExecutors.cs
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace ConditionalEdgesFunctionApp;
+
+///
+/// Represents an order with customer and payment details.
+///
+internal sealed class Order
+{
+ public Order(string id, decimal amount)
+ {
+ this.Id = id;
+ this.Amount = amount;
+ }
+
+ public string Id { get; }
+ public decimal Amount { get; }
+ public Customer? Customer { get; set; }
+ public string? PaymentReferenceNumber { get; set; }
+}
+
+///
+/// Represents a customer associated with an order.
+///
+public sealed record Customer(int Id, string Name, bool IsBlocked);
+
+///
+/// Parses the order ID and retrieves order details.
+///
+internal sealed class OrderIdParser() : Executor("OrderIdParser")
+{
+ public override ValueTask HandleAsync(
+ string message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[OrderIdParser] Parsing order ID: {message}");
+ Order order = new(message, 100.0m);
+ return ValueTask.FromResult(order);
+ }
+}
+
+///
+/// Enriches the order with customer information.
+/// Orders with IDs containing 'B' are associated with blocked customers.
+///
+internal sealed class OrderEnrich() : Executor("EnrichOrder")
+{
+ public override ValueTask HandleAsync(
+ Order message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ message.Customer = GetCustomerForOrder(message.Id);
+ Console.WriteLine($"[EnrichOrder] Customer: {message.Customer.Name}, IsBlocked: {message.Customer.IsBlocked}");
+ return ValueTask.FromResult(message);
+ }
+
+ private static Customer GetCustomerForOrder(string orderId)
+ {
+ if (orderId.Contains('B'))
+ {
+ return new Customer(101, "George", true);
+ }
+
+ return new Customer(201, "Jerry", false);
+ }
+}
+
+///
+/// Processes payment for valid (non-blocked) orders.
+///
+internal sealed class PaymentProcessor() : Executor("PaymentProcessor")
+{
+ public override ValueTask HandleAsync(
+ Order message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ message.PaymentReferenceNumber = Guid.NewGuid().ToString()[..4];
+ Console.WriteLine($"[PaymentProcessor] Payment processed for order {message.Id}. Reference: {message.PaymentReferenceNumber}");
+ return ValueTask.FromResult(message);
+ }
+}
+
+///
+/// Notifies the fraud team when a blocked customer places an order.
+///
+internal sealed class NotifyFraud() : Executor("NotifyFraud")
+{
+ public override ValueTask HandleAsync(
+ Order message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ string result = $"Order {message.Id} flagged as fraudulent for customer {message.Customer?.Name}.";
+ Console.WriteLine($"[NotifyFraud] {result}");
+ return ValueTask.FromResult(result);
+ }
+}
+
+///
+/// Defines condition functions for routing orders based on customer status.
+///
+internal static class OrderRouteConditions
+{
+ ///
+ /// Returns a condition that evaluates to true when the customer is blocked.
+ ///
+ internal static Func WhenBlocked() => order => order?.Customer?.IsBlocked == true;
+
+ ///
+ /// Returns a condition that evaluates to true when the customer is not blocked.
+ ///
+ internal static Func WhenNotBlocked() => order => order?.Customer?.IsBlocked == false;
+}
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/Program.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/Program.cs
new file mode 100644
index 0000000000..20bbe262a5
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/Program.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates conditional edges in a workflow hosted as an Azure Function.
+// Orders are routed to different executors based on customer status:
+// - Blocked customers → NotifyFraud
+// - Valid customers → PaymentProcessor
+
+using ConditionalEdgesFunctionApp;
+using Microsoft.Agents.AI.Hosting.AzureFunctions;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+
+// Create executor instances
+OrderIdParser orderParser = new();
+OrderEnrich orderEnrich = new();
+PaymentProcessor paymentProcessor = new();
+NotifyFraud notifyFraud = new();
+
+// Build workflow with conditional edges.
+// The condition functions evaluate the Order output from OrderEnrich
+// to determine whether to route to NotifyFraud or PaymentProcessor.
+Workflow auditOrder = new WorkflowBuilder(orderParser)
+ .WithName("AuditOrder")
+ .WithDescription("Audits an order and routes based on customer status")
+ .AddEdge(orderParser, orderEnrich)
+ .AddEdge(orderEnrich, notifyFraud, condition: OrderRouteConditions.WhenBlocked())
+ .AddEdge(orderEnrich, paymentProcessor, condition: OrderRouteConditions.WhenNotBlocked())
+ .Build();
+
+// Configure the function app to host the workflow.
+// This will automatically generate HTTP API endpoints for the workflow.
+using IHost app = FunctionsApplication
+ .CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableOptions(durableOption =>
+ {
+ // Add the workflow.
+ durableOption.Workflows.AddWorkflow(auditOrder);
+ })
+ .Build();
+app.Run();
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/README.md b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/README.md
new file mode 100644
index 0000000000..41eabf7b05
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/README.md
@@ -0,0 +1,84 @@
+# Conditional Edges Workflow - Azure Functions Sample
+
+This sample demonstrates how to build a workflow with **conditional edges** hosted as an Azure Function. Orders are routed to different executors based on customer status.
+
+## Key Concepts Demonstrated
+
+- Building workflows with **conditional edges** using `AddEdge` with a `condition` parameter
+- Hosting a conditional workflow as an Azure Function using `ConfigureDurableOptions`
+- Defining reusable condition functions for routing logic
+- Branching workflow execution based on data-driven decisions
+
+## Overview
+
+The workflow implements an order audit that routes orders differently based on whether the customer is blocked (flagged for fraud):
+
+```
+OrderIdParser --> OrderEnrich --[IsBlocked]--> NotifyFraud
+ |
+ +--[NotBlocked]--> PaymentProcessor
+```
+
+| Executor | Description |
+|----------|-------------|
+| OrderIdParser | Parses the order ID and retrieves order details |
+| OrderEnrich | Enriches the order with customer information |
+| PaymentProcessor | Processes payment for valid orders |
+| NotifyFraud | Notifies the fraud team for blocked customers |
+
+## How Conditional Edges Work
+
+Conditional edges allow you to specify a condition function that determines whether the edge should be traversed:
+
+```csharp
+builder
+ .AddEdge(orderParser, orderEnrich)
+ .AddEdge(orderEnrich, notifyFraud, condition: OrderRouteConditions.WhenBlocked())
+ .AddEdge(orderEnrich, paymentProcessor, condition: OrderRouteConditions.WhenNotBlocked());
+```
+
+The condition functions receive the output of the source executor and return a boolean:
+
+```csharp
+internal static class OrderRouteConditions
+{
+ internal static Func WhenBlocked() =>
+ order => order?.Customer?.IsBlocked == true;
+
+ internal static Func WhenNotBlocked() =>
+ order => order?.Customer?.IsBlocked == false;
+}
+```
+
+### Routing Logic
+
+- Order IDs containing the letter **'B'** are associated with blocked customers → routed to `NotifyFraud`
+- All other order IDs are associated with valid customers → routed to `PaymentProcessor`
+
+## Environment Setup
+
+This sample requires:
+
+- [.NET 10 SDK](https://dotnet.microsoft.com/download)
+- [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local)
+- [Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-azure-managed-storage) running locally (default: `http://localhost:8080`)
+- [Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite) for local Azure Storage emulation
+
+## Running the Sample
+
+```bash
+cd dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges
+func start
+```
+
+### Testing
+
+**Valid order (routes to PaymentProcessor):**
+```bash
+curl -X POST http://localhost:7071/api/workflows/AuditOrder/run -H "Content-Type: text/plain" -d "12345"
+```
+
+**Blocked order (routes to NotifyFraud):**
+```bash
+curl -X POST http://localhost:7071/api/workflows/AuditOrder/run -H "Content-Type: text/plain" -d "12345B"
+```
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/demo.http b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/demo.http
new file mode 100644
index 0000000000..2516be9224
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/demo.http
@@ -0,0 +1,14 @@
+# Default endpoint address for local testing
+@authority=http://localhost:7071
+
+### Valid order (routes to PaymentProcessor)
+POST {{authority}}/api/workflows/AuditOrder/run
+Content-Type: text/plain
+
+12345
+
+### Blocked order (routes to NotifyFraud) - Order IDs containing 'B' are flagged
+POST {{authority}}/api/workflows/AuditOrder/run
+Content-Type: text/plain
+
+12345B
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/host.json b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/03_ConditionalEdges/host.json
@@ -0,0 +1,20 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Microsoft.Agents.AI.DurableTask": "Information",
+ "Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
+ "DurableTask": "Information",
+ "Microsoft.DurableTask": "Information"
+ }
+ },
+ "extensions": {
+ "durableTask": {
+ "hubName": "default",
+ "storageProvider": {
+ "type": "AzureManaged",
+ "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/04_NestedWorkflows.csproj b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/04_NestedWorkflows.csproj
new file mode 100644
index 0000000000..22903f3fe2
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/04_NestedWorkflows.csproj
@@ -0,0 +1,39 @@
+
+
+ net10.0
+ v4
+ Exe
+ enable
+ enable
+
+ NestedWorkflowsFunctionApp
+ NestedWorkflowsFunctionApp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/OrderExecutors.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/OrderExecutors.cs
new file mode 100644
index 0000000000..1fd697b434
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/OrderExecutors.cs
@@ -0,0 +1,220 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace NestedWorkflowsFunctionApp;
+
+// ============================================
+// Order Processing Models
+// ============================================
+
+///
+/// Represents an order being processed through the workflow.
+///
+internal sealed class OrderInfo
+{
+ public required string OrderId { get; set; }
+ public decimal Amount { get; set; }
+ public string? PaymentTransactionId { get; set; }
+ public string? InventoryReservationId { get; set; }
+ public string? TrackingNumber { get; set; }
+ public string? Carrier { get; set; }
+}
+
+// ============================================
+// Main Workflow Executors
+// ============================================
+
+///
+/// Entry point executor that receives the order ID and creates an OrderInfo object.
+///
+internal sealed class OrderReceived() : Executor("OrderReceived")
+{
+ public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[OrderReceived] Processing order '{message}'");
+
+ OrderInfo order = new()
+ {
+ OrderId = message,
+ Amount = 99.99m
+ };
+
+ return ValueTask.FromResult(order);
+ }
+}
+
+///
+/// Final executor that outputs the completed order summary.
+///
+internal sealed class OrderCompleted() : Executor("OrderCompleted")
+{
+ public override ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[OrderCompleted] Order '{message.OrderId}' processed. Payment: {message.PaymentTransactionId}, Inventory: {message.InventoryReservationId}, Shipping: {message.Carrier} - {message.TrackingNumber}");
+
+ return ValueTask.FromResult($"Order {message.OrderId} completed. Tracking: {message.TrackingNumber}");
+ }
+}
+
+// ============================================
+// Payment Sub-Workflow Executors
+// ============================================
+
+///
+/// Validates payment information for an order.
+///
+internal sealed class ValidatePayment() : Executor("ValidatePayment")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Payment/ValidatePayment] Validating payment for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ Console.WriteLine($"[Payment/ValidatePayment] Payment validated for ${message.Amount}");
+
+ return message;
+ }
+}
+
+// ============================================
+// Fraud Check Sub-Sub-Workflow Executors (Level 2 nesting)
+// ============================================
+
+///
+/// Analyzes transaction patterns for potential fraud.
+///
+internal sealed class AnalyzePatterns() : Executor("AnalyzePatterns")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Payment/FraudCheck/AnalyzePatterns] Analyzing patterns for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ Console.WriteLine("[Payment/FraudCheck/AnalyzePatterns] Pattern analysis complete");
+
+ return message;
+ }
+}
+
+///
+/// Calculates a risk score for the transaction.
+///
+internal sealed class CalculateRiskScore() : Executor("CalculateRiskScore")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Payment/FraudCheck/CalculateRiskScore] Calculating risk score for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ int riskScore = new Random().Next(1, 100);
+
+ Console.WriteLine($"[Payment/FraudCheck/CalculateRiskScore] Risk score: {riskScore}/100 (Low risk)");
+
+ return message;
+ }
+}
+
+///
+/// Charges the payment for an order.
+///
+internal sealed class ChargePayment() : Executor("ChargePayment")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Payment/ChargePayment] Charging ${message.Amount} for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
+
+ message.PaymentTransactionId = $"TXN-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
+
+ Console.WriteLine($"[Payment/ChargePayment] Payment processed: {message.PaymentTransactionId}");
+
+ return message;
+ }
+}
+
+// ============================================
+// Inventory Sub-Workflow Executors
+// ============================================
+
+///
+/// Checks inventory availability for an order.
+///
+internal sealed class CheckInventory() : Executor("CheckInventory")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Inventory/CheckInventory] Checking inventory for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ Console.WriteLine("[Inventory/CheckInventory] Items available in stock");
+
+ return message;
+ }
+}
+
+///
+/// Reserves inventory for an order.
+///
+internal sealed class ReserveInventory() : Executor("ReserveInventory")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Inventory/ReserveInventory] Reserving items for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
+
+ message.InventoryReservationId = $"RES-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
+
+ Console.WriteLine($"[Inventory/ReserveInventory] Reserved: {message.InventoryReservationId}");
+
+ return message;
+ }
+}
+
+// ============================================
+// Shipping Sub-Workflow Executors
+// ============================================
+
+///
+/// Selects a shipping carrier for an order.
+///
+internal sealed class SelectCarrier() : Executor("SelectCarrier")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Shipping/SelectCarrier] Selecting carrier for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ message.Carrier = message.Amount > 50 ? "Express" : "Standard";
+
+ Console.WriteLine($"[Shipping/SelectCarrier] Selected carrier: {message.Carrier}");
+
+ return message;
+ }
+}
+
+///
+/// Creates shipment and generates tracking number.
+///
+internal sealed class CreateShipment() : Executor("CreateShipment")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Shipping/CreateShipment] Creating shipment for order '{message.OrderId}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
+
+ message.TrackingNumber = $"TRACK-{Guid.NewGuid().ToString("N")[..10].ToUpperInvariant()}";
+
+ Console.WriteLine($"[Shipping/CreateShipment] Shipment created: {message.TrackingNumber}");
+
+ return message;
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/Program.cs b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/Program.cs
new file mode 100644
index 0000000000..4817023868
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/Program.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates nested workflows (sub-workflows) hosted as an Azure Function.
+// One workflow can be used as an executor within another workflow, enabling modular,
+// reusable workflow components with independent checkpointing and replay.
+//
+// Workflow structure:
+//
+// ┌─────────────────────────────────────────────────────────────────────────┐
+// │ OrderProcessing (Main Workflow) │
+// │ │
+// │ OrderReceived │
+// │ │ │
+// │ ▼ │
+// │ ┌─────────────────────────────────────────────────────────┐ │
+// │ │ Payment (Sub-Workflow) │ │
+// │ │ │ │
+// │ │ ValidatePayment │ │
+// │ │ │ │ │
+// │ │ ▼ │ │
+// │ │ ┌─────────────────────────────────────────┐ │ │
+// │ │ │ FraudCheck (Sub-Sub-Workflow) │ │ │
+// │ │ │ │ │ │
+// │ │ │ AnalyzePatterns ──► CalculateRiskScore │ │ │
+// │ │ └─────────────────────────────────────────┘ │ │
+// │ │ │ │ │
+// │ │ ▼ │ │
+// │ │ ChargePayment │ │
+// │ └─────────────────────────────────────────────────────────┘ │
+// │ │ │
+// │ ▼ │
+// │ ┌─────────────────────────────────────────────────────────┐ │
+// │ │ Inventory (Sub-Workflow) │ │
+// │ │ │ │
+// │ │ CheckInventory ──► ReserveInventory │ │
+// │ └─────────────────────────────────────────────────────────┘ │
+// │ │ │
+// │ ▼ │
+// │ ┌─────────────────────────────────────────────────────────┐ │
+// │ │ Shipping (Sub-Workflow) │ │
+// │ │ │ │
+// │ │ SelectCarrier ──► CreateShipment │ │
+// │ └─────────────────────────────────────────────────────────┘ │
+// │ │ │
+// │ ▼ │
+// │ OrderCompleted │
+// └─────────────────────────────────────────────────────────────────────────┘
+
+using Microsoft.Agents.AI.Hosting.AzureFunctions;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+using NestedWorkflowsFunctionApp;
+
+// Create executor instances for the Fraud Check sub-sub-workflow (Level 2 nesting)
+AnalyzePatterns analyzePatterns = new();
+CalculateRiskScore calculateRiskScore = new();
+
+Workflow fraudCheckWorkflow = new WorkflowBuilder(analyzePatterns)
+ .WithName("SubFraudCheck")
+ .WithDescription("Analyzes transaction patterns and calculates risk score")
+ .AddEdge(analyzePatterns, calculateRiskScore)
+ .Build();
+
+// Create executor instances for the Payment Processing sub-workflow
+ValidatePayment validatePayment = new();
+ExecutorBinding fraudCheckExecutor = fraudCheckWorkflow.BindAsExecutor("FraudCheck");
+ChargePayment chargePayment = new();
+
+Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
+ .WithName("SubPaymentProcessing")
+ .WithDescription("Validates and processes payment for an order")
+ .AddEdge(validatePayment, fraudCheckExecutor)
+ .AddEdge(fraudCheckExecutor, chargePayment)
+ .Build();
+
+// Create executor instances for the Inventory Management sub-workflow
+CheckInventory checkInventory = new();
+ReserveInventory reserveInventory = new();
+
+Workflow inventoryWorkflow = new WorkflowBuilder(checkInventory)
+ .WithName("SubInventoryManagement")
+ .WithDescription("Checks availability and reserves inventory")
+ .AddEdge(checkInventory, reserveInventory)
+ .Build();
+
+// Create executor instances for the Shipping Arrangement sub-workflow
+SelectCarrier selectCarrier = new();
+CreateShipment createShipment = new();
+
+Workflow shippingWorkflow = new WorkflowBuilder(selectCarrier)
+ .WithName("SubShippingArrangement")
+ .WithDescription("Selects carrier and creates shipment")
+ .AddEdge(selectCarrier, createShipment)
+ .Build();
+
+// Build the main Order Processing workflow using sub-workflows as executors
+ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
+ExecutorBinding inventoryExecutor = inventoryWorkflow.BindAsExecutor("Inventory");
+ExecutorBinding shippingExecutor = shippingWorkflow.BindAsExecutor("Shipping");
+
+OrderReceived orderReceived = new();
+OrderCompleted orderCompleted = new();
+
+Workflow orderProcessingWorkflow = new WorkflowBuilder(orderReceived)
+ .WithName("OrderProcessing")
+ .WithDescription("Processes an order through payment, inventory, and shipping")
+ .AddEdge(orderReceived, paymentExecutor)
+ .AddEdge(paymentExecutor, inventoryExecutor)
+ .AddEdge(inventoryExecutor, shippingExecutor)
+ .AddEdge(shippingExecutor, orderCompleted)
+ .Build();
+
+// Configure the function app to host the workflow.
+// Sub-workflows are discovered and registered automatically.
+using IHost app = FunctionsApplication
+ .CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableOptions(durableOption =>
+ durableOption.Workflows.AddWorkflow(orderProcessingWorkflow))
+ .Build();
+app.Run();
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/README.md b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/README.md
new file mode 100644
index 0000000000..04973bd98f
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/README.md
@@ -0,0 +1,112 @@
+# Nested Workflows - Azure Functions Sample
+
+This sample demonstrates how to build **nested workflows** (sub-workflows) hosted as an Azure Function. One workflow can be used as an executor within another workflow, enabling modular, reusable workflow components.
+
+## Key Concepts Demonstrated
+
+- Building workflows with **sub-workflow executors** using `BindAsExecutor`
+- **Multi-level nesting** — a sub-workflow contains its own sub-workflow (FraudCheck inside Payment)
+- Hosting nested workflows as an Azure Function using `ConfigureDurableOptions`
+- Automatic discovery and registration of sub-workflows
+
+## Overview
+
+The workflow implements an order processing pipeline where each stage is a separate sub-workflow:
+
+```
+OrderReceived
+ │
+ ▼
+┌─────────────────────────────────┐
+│ Payment (Sub-Workflow) │
+│ ValidatePayment │
+│ ▼ │
+│ ┌────────────────────────────┐ │
+│ │ FraudCheck (Sub-Sub) │ │
+│ │ AnalyzePatterns │ │
+│ │ ▼ │ │
+│ │ CalculateRiskScore │ │
+│ └────────────────────────────┘ │
+│ ▼ │
+│ ChargePayment │
+└─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────┐
+│ Inventory (Sub-Workflow) │
+│ CheckInventory ──► Reserve │
+└─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────┐
+│ Shipping (Sub-Workflow) │
+│ SelectCarrier ──► CreateShip. │
+└─────────────────────────────────┘
+ │
+ ▼
+OrderCompleted
+```
+
+| Executor | Sub-Workflow | Description |
+|----------|-------------|-------------|
+| OrderReceived | Main | Receives order ID and creates OrderInfo |
+| ValidatePayment | Payment | Validates payment information |
+| AnalyzePatterns | FraudCheck (nested in Payment) | Analyzes transaction patterns |
+| CalculateRiskScore | FraudCheck (nested in Payment) | Calculates fraud risk score |
+| ChargePayment | Payment | Charges the payment |
+| CheckInventory | Inventory | Checks item availability |
+| ReserveInventory | Inventory | Reserves inventory items |
+| SelectCarrier | Shipping | Selects shipping carrier |
+| CreateShipment | Shipping | Creates shipment with tracking |
+| OrderCompleted | Main | Outputs completed order summary |
+
+## How Nested Workflows Work
+
+Sub-workflows are created by binding a workflow as an executor:
+
+```csharp
+// Build a sub-workflow
+Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
+ .WithName("SubPaymentProcessing")
+ .AddEdge(validatePayment, fraudCheckExecutor)
+ .AddEdge(fraudCheckExecutor, chargePayment)
+ .Build();
+
+// Bind it as an executor in the parent workflow
+ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
+
+// Use it like any other executor
+Workflow mainWorkflow = new WorkflowBuilder(orderReceived)
+ .AddEdge(orderReceived, paymentExecutor)
+ .Build();
+```
+
+Each sub-workflow runs as a separate orchestration instance, providing:
+- **Modularity** — workflows can be composed from reusable sub-workflows
+- **Independent checkpointing** — each sub-workflow has its own replay history
+- **Hierarchical visualization** — view parent-child relationships in the DTS dashboard
+- **Failure isolation** — sub-workflow failures don't corrupt parent state
+
+## Environment Setup
+
+This sample requires:
+
+- [.NET 10 SDK](https://dotnet.microsoft.com/download)
+- [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local)
+- [Durable Task Scheduler](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-azure-managed-storage) running locally (default: `http://localhost:8080`)
+- [Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite) for local Azure Storage emulation
+
+## Running the Sample
+
+```bash
+cd dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows
+func start
+```
+
+### Testing
+
+```bash
+curl -X POST http://localhost:7071/api/workflows/OrderProcessing/run -H "Content-Type: text/plain" -d "ORD-2026-001"
+```
+
+Open the DTS dashboard at `http://localhost:8080` to see the parent-child orchestration hierarchy in the Timeline view.
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/demo.http b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/demo.http
new file mode 100644
index 0000000000..6cb4223406
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/demo.http
@@ -0,0 +1,14 @@
+# Default endpoint address for local testing
+@authority=http://localhost:7071
+
+### Process an order through nested workflows (Payment → Inventory → Shipping)
+POST {{authority}}/api/workflows/OrderProcessing/run
+Content-Type: text/plain
+
+ORD-2026-001
+
+### Process another order
+POST {{authority}}/api/workflows/OrderProcessing/run
+Content-Type: text/plain
+
+ORD-2026-002
diff --git a/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/host.json b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/AzureFunctions/04_NestedWorkflows/host.json
@@ -0,0 +1,20 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Microsoft.Agents.AI.DurableTask": "Information",
+ "Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
+ "DurableTask": "Information",
+ "Microsoft.DurableTask": "Information"
+ }
+ },
+ "extensions": {
+ "durableTask": {
+ "hubName": "default",
+ "storageProvider": {
+ "type": "AzureManaged",
+ "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/05_NestedWorkflows.csproj b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/05_NestedWorkflows.csproj
new file mode 100644
index 0000000000..218b4dfe9a
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/05_NestedWorkflows.csproj
@@ -0,0 +1,29 @@
+
+
+ net10.0
+ Exe
+ enable
+ enable
+ NestedWorkflows
+ NestedWorkflows
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/Executors.cs b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/Executors.cs
new file mode 100644
index 0000000000..e2c142a9dd
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/Executors.cs
@@ -0,0 +1,250 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace NestedWorkflows;
+
+// ============================================
+// Order Processing Models
+// ============================================
+
+///
+/// Represents an order being processed through the workflow.
+///
+internal sealed class OrderInfo
+{
+ public required string OrderId { get; set; }
+ public decimal Amount { get; set; }
+ public string? PaymentTransactionId { get; set; }
+ public string? InventoryReservationId { get; set; }
+ public string? TrackingNumber { get; set; }
+ public string? Carrier { get; set; }
+}
+
+// ============================================
+// Main Workflow Executors
+// ============================================
+
+///
+/// Entry point executor that receives the order ID and creates an OrderInfo object.
+///
+internal sealed class OrderReceived() : Executor("OrderReceived")
+{
+ public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Cyan;
+ Console.WriteLine($"[OrderReceived] Processing order '{message}'");
+ Console.ResetColor();
+
+ OrderInfo order = new()
+ {
+ OrderId = message,
+ Amount = 99.99m
+ };
+
+ return ValueTask.FromResult(order);
+ }
+}
+
+///
+/// Final executor that outputs the completed order summary.
+///
+internal sealed class OrderCompleted() : Executor("OrderCompleted")
+{
+ public override ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
+ Console.WriteLine($"│ [OrderCompleted] Order '{message.OrderId}' successfully processed!");
+ Console.WriteLine($"│ Payment: {message.PaymentTransactionId}");
+ Console.WriteLine($"│ Inventory: {message.InventoryReservationId}");
+ Console.WriteLine($"│ Shipping: {message.Carrier} - {message.TrackingNumber}");
+ Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
+ Console.ResetColor();
+
+ return ValueTask.FromResult($"Order {message.OrderId} completed. Tracking: {message.TrackingNumber}");
+ }
+}
+
+// ============================================
+// Payment Sub-Workflow Executors
+// ============================================
+
+///
+/// Validates payment information for an order.
+///
+internal sealed class ValidatePayment() : Executor("ValidatePayment")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine($" [Payment/ValidatePayment] Validating payment for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ Console.WriteLine($" [Payment/ValidatePayment] Payment validated for ${message.Amount}");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+// ============================================
+// Fraud Check Sub-Sub-Workflow Executors (Level 2 nesting)
+// ============================================
+
+///
+/// Analyzes transaction patterns for potential fraud.
+///
+internal sealed class AnalyzePatterns() : Executor("AnalyzePatterns")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkYellow;
+ Console.WriteLine($" [Payment/FraudCheck/AnalyzePatterns] Analyzing patterns for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ Console.WriteLine(" [Payment/FraudCheck/AnalyzePatterns] ✓ Pattern analysis complete");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+///
+/// Calculates a risk score for the transaction.
+///
+internal sealed class CalculateRiskScore() : Executor("CalculateRiskScore")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.ForegroundColor = ConsoleColor.DarkYellow;
+ Console.WriteLine($" [Payment/FraudCheck/CalculateRiskScore] Calculating risk score for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ int riskScore = new Random().Next(1, 100);
+
+ Console.WriteLine($" [Payment/FraudCheck/CalculateRiskScore] ✓ Risk score: {riskScore}/100 (Low risk)");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+///
+/// Charges the payment for an order.
+///
+internal sealed class ChargePayment() : Executor("ChargePayment")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine($" [Payment/ChargePayment] Charging ${message.Amount} for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
+
+ message.PaymentTransactionId = $"TXN-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
+
+ Console.WriteLine($" [Payment/ChargePayment] ✓ Payment processed: {message.PaymentTransactionId}");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+// ============================================
+// Inventory Sub-Workflow Executors
+// ============================================
+
+///
+/// Checks inventory availability for an order.
+///
+internal sealed class CheckInventory() : Executor("CheckInventory")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Magenta;
+ Console.WriteLine($" [Inventory/CheckInventory] Checking inventory for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ Console.WriteLine(" [Inventory/CheckInventory] ✓ Items available in stock");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+///
+/// Reserves inventory for an order.
+///
+internal sealed class ReserveInventory() : Executor("ReserveInventory")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.ForegroundColor = ConsoleColor.Magenta;
+ Console.WriteLine($" [Inventory/ReserveInventory] Reserving items for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
+
+ message.InventoryReservationId = $"RES-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
+
+ Console.WriteLine($" [Inventory/ReserveInventory] ✓ Reserved: {message.InventoryReservationId}");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+// ============================================
+// Shipping Sub-Workflow Executors
+// ============================================
+
+///
+/// Selects a shipping carrier for an order.
+///
+internal sealed class SelectCarrier() : Executor("SelectCarrier")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = ConsoleColor.Blue;
+ Console.WriteLine($" [Shipping/SelectCarrier] Selecting carrier for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
+
+ message.Carrier = message.Amount > 50 ? "Express" : "Standard";
+
+ Console.WriteLine($" [Shipping/SelectCarrier] ✓ Selected carrier: {message.Carrier}");
+ Console.ResetColor();
+
+ return message;
+ }
+}
+
+///
+/// Creates shipment and generates tracking number.
+///
+internal sealed class CreateShipment() : Executor("CreateShipment")
+{
+ public override async ValueTask HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ Console.ForegroundColor = ConsoleColor.Blue;
+ Console.WriteLine($" [Shipping/CreateShipment] Creating shipment for order '{message.OrderId}'...");
+
+ await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
+
+ message.TrackingNumber = $"TRACK-{Guid.NewGuid().ToString("N")[..10].ToUpperInvariant()}";
+
+ Console.WriteLine($" [Shipping/CreateShipment] ✓ Shipment created: {message.TrackingNumber}");
+ Console.ResetColor();
+
+ return message;
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/Program.cs b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/Program.cs
new file mode 100644
index 0000000000..14561a5bc5
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/Program.cs
@@ -0,0 +1,228 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates nested workflows (sub-workflows) where one workflow
+// can be used as an executor within another workflow. This enables modular,
+// reusable workflow components with independent checkpointing and replay.
+//
+// Workflow structure:
+//
+// ┌─────────────────────────────────────────────────────────────────────────┐
+// │ OrderProcessing (Main Workflow) │
+// │ │
+// │ OrderReceived │
+// │ │ │
+// │ ▼ │
+// │ ┌─────────────────────────────────────────────────────────┐ │
+// │ │ Payment (Sub-Workflow) │ │
+// │ │ │ │
+// │ │ ValidatePayment │ │
+// │ │ │ │ │
+// │ │ ▼ │ │
+// │ │ ┌─────────────────────────────────────────┐ │ │
+// │ │ │ FraudCheck (Sub-Sub-Workflow) │ │ │
+// │ │ │ │ │ │
+// │ │ │ AnalyzePatterns ──► CalculateRiskScore │ │ │
+// │ │ └─────────────────────────────────────────┘ │ │
+// │ │ │ │ │
+// │ │ ▼ │ │
+// │ │ ChargePayment │ │
+// │ └─────────────────────────────────────────────────────────┘ │
+// │ │ │
+// │ ▼ │
+// │ ┌─────────────────────────────────────────────────────────┐ │
+// │ │ Inventory (Sub-Workflow) │ │
+// │ │ │ │
+// │ │ CheckInventory ──► ReserveInventory │ │
+// │ └─────────────────────────────────────────────────────────┘ │
+// │ │ │
+// │ ▼ │
+// │ ┌─────────────────────────────────────────────────────────┐ │
+// │ │ Shipping (Sub-Workflow) │ │
+// │ │ │ │
+// │ │ SelectCarrier ──► CreateShipment │ │
+// │ └─────────────────────────────────────────────────────────┘ │
+// │ │ │
+// │ ▼ │
+// │ OrderCompleted │
+// └─────────────────────────────────────────────────────────────────────────┘
+//
+// Each sub-workflow runs as a separate orchestration instance, providing:
+// - Modular, reusable workflow components
+// - Independent checkpointing and replay
+// - Hierarchical visualization in the DTS dashboard
+// - Failure isolation between parent and child workflows
+
+using Microsoft.Agents.AI.DurableTask;
+using Microsoft.Agents.AI.DurableTask.Workflows;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.DurableTask.Client.AzureManaged;
+using Microsoft.DurableTask.Worker.AzureManaged;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using NestedWorkflows;
+
+string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
+ ?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
+
+// ============================================
+// Step 1: Build the Fraud Check sub-sub-workflow (Level 2 nesting)
+// ============================================
+AnalyzePatterns analyzePatterns = new();
+CalculateRiskScore calculateRiskScore = new();
+
+Workflow fraudCheckWorkflow = new WorkflowBuilder(analyzePatterns)
+ .WithName("SubFraudCheck")
+ .WithDescription("Analyzes transaction patterns and calculates risk score")
+ .AddEdge(analyzePatterns, calculateRiskScore)
+ .Build();
+
+// ============================================
+// Step 2: Build the Payment Processing sub-workflow (with nested FraudCheck)
+// ============================================
+ValidatePayment validatePayment = new();
+ExecutorBinding fraudCheckExecutor = fraudCheckWorkflow.BindAsExecutor("FraudCheck");
+ChargePayment chargePayment = new();
+
+Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
+ .WithName("SubPaymentProcessing")
+ .WithDescription("Validates and processes payment for an order")
+ .AddEdge(validatePayment, fraudCheckExecutor)
+ .AddEdge(fraudCheckExecutor, chargePayment)
+ .Build();
+
+// ============================================
+// Step 3: Build the Inventory Management sub-workflow
+// ============================================
+CheckInventory checkInventory = new();
+ReserveInventory reserveInventory = new();
+
+Workflow inventoryWorkflow = new WorkflowBuilder(checkInventory)
+ .WithName("SubInventoryManagement")
+ .WithDescription("Checks availability and reserves inventory")
+ .AddEdge(checkInventory, reserveInventory)
+ .Build();
+
+// ============================================
+// Step 4: Build the Shipping Arrangement sub-workflow
+// ============================================
+SelectCarrier selectCarrier = new();
+CreateShipment createShipment = new();
+
+Workflow shippingWorkflow = new WorkflowBuilder(selectCarrier)
+ .WithName("SubShippingArrangement")
+ .WithDescription("Selects carrier and creates shipment")
+ .AddEdge(selectCarrier, createShipment)
+ .Build();
+
+// ============================================
+// Step 5: Build the Main Order Processing workflow using sub-workflows
+// ============================================
+ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
+ExecutorBinding inventoryExecutor = inventoryWorkflow.BindAsExecutor("Inventory");
+ExecutorBinding shippingExecutor = shippingWorkflow.BindAsExecutor("Shipping");
+
+OrderReceived orderReceived = new();
+OrderCompleted orderCompleted = new();
+
+// Main workflow: OrderReceived -> Payment -> Inventory -> Shipping -> OrderCompleted
+Workflow orderProcessingWorkflow = new WorkflowBuilder(orderReceived)
+ .WithName("OrderProcessing")
+ .WithDescription("Processes an order through payment, inventory, and shipping")
+ .AddEdge(orderReceived, paymentExecutor)
+ .AddEdge(paymentExecutor, inventoryExecutor)
+ .AddEdge(inventoryExecutor, shippingExecutor)
+ .AddEdge(shippingExecutor, orderCompleted)
+ .Build();
+
+// ============================================
+// Step 6: Configure and start the host
+// ============================================
+IHost host = Host.CreateDefaultBuilder(args)
+ .ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
+ .ConfigureServices(services =>
+ {
+ // Register only the main workflow - sub-workflows are discovered automatically!
+ services.ConfigureDurableWorkflows(
+ workflowOptions => workflowOptions.AddWorkflow(orderProcessingWorkflow),
+ workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
+ clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
+ })
+ .Build();
+
+await host.StartAsync();
+
+IWorkflowClient workflowClient = host.Services.GetRequiredService();
+
+Console.WriteLine("╔══════════════════════════════════════════════════════════════════╗");
+Console.WriteLine("║ Nested Workflows Sample ║");
+Console.WriteLine("╠══════════════════════════════════════════════════════════════════╣");
+Console.WriteLine("║ Main Workflow: OrderProcessing ║");
+Console.WriteLine("║ ├── Payment (sub-workflow) ║");
+Console.WriteLine("║ │ ├── ValidatePayment ║");
+Console.WriteLine("║ │ ├── FraudCheck (sub-sub-workflow) ← Level 2 nesting! ║");
+Console.WriteLine("║ │ │ ├── AnalyzePatterns ║");
+Console.WriteLine("║ │ │ └── CalculateRiskScore ║");
+Console.WriteLine("║ │ └── ChargePayment ║");
+Console.WriteLine("║ ├── Inventory (sub-workflow) ║");
+Console.WriteLine("║ │ ├── CheckInventory ║");
+Console.WriteLine("║ │ └── ReserveInventory ║");
+Console.WriteLine("║ └── Shipping (sub-workflow) ║");
+Console.WriteLine("║ ├── SelectCarrier ║");
+Console.WriteLine("║ └── CreateShipment ║");
+Console.WriteLine("╚══════════════════════════════════════════════════════════════════╝");
+Console.WriteLine();
+Console.WriteLine("Open the DTS dashboard at http://localhost:8080 to see the");
+Console.WriteLine("parent-child orchestration hierarchy in the Timeline view!");
+Console.WriteLine();
+Console.WriteLine("Enter an order ID (or 'exit'):");
+
+while (true)
+{
+ Console.Write("> ");
+ string? input = Console.ReadLine();
+ if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
+ {
+ break;
+ }
+
+ try
+ {
+ await StartNewWorkflowAsync(input, orderProcessingWorkflow, workflowClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ }
+
+ Console.WriteLine();
+}
+
+await host.StopAsync();
+
+// Start a new workflow and wait for completion
+static async Task StartNewWorkflowAsync(string orderId, Workflow workflow, IWorkflowClient client)
+{
+ Console.WriteLine($"\nStarting order processing for '{orderId}'...");
+
+ IAwaitableWorkflowRun run = (IAwaitableWorkflowRun)await client.RunAsync(workflow, orderId);
+ Console.WriteLine($"Run ID: {run.RunId}");
+ Console.WriteLine("Check the DTS dashboard Timeline tab to see sub-orchestrations!");
+ Console.WriteLine();
+
+ try
+ {
+ Console.WriteLine("Waiting for workflow to complete...");
+ string? result = await run.WaitForCompletionAsync();
+
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine($"✓ Order completed: {result}");
+ Console.ResetColor();
+ }
+ catch (InvalidOperationException ex)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"✗ Failed: {ex.Message}");
+ Console.ResetColor();
+ }
+}
diff --git a/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/README.md b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/README.md
new file mode 100644
index 0000000000..dda2e351d4
--- /dev/null
+++ b/dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows/README.md
@@ -0,0 +1,95 @@
+# Nested Workflows Sample
+
+This sample demonstrates how to use **nested workflows** (sub-workflows) where one workflow can be used as an executor within another workflow. This enables modular, reusable workflow components with independent checkpointing and replay.
+
+## Key Concepts Demonstrated
+
+- Using `Workflow.BindAsExecutor()` to embed a workflow as an executor in another workflow
+- Multi-level nesting (sub-workflow within a sub-workflow)
+- Automatic discovery of sub-workflows when registering the main workflow
+- Independent orchestration instances for each sub-workflow
+
+## Overview
+
+The sample implements an order processing workflow with three sub-workflows and one sub-sub-workflow:
+
+```
+OrderProcessing (Main Workflow)
+├── OrderReceived
+├── Payment (sub-workflow)
+│ ├── ValidatePayment
+│ ├── FraudCheck (sub-sub-workflow) ← Level 2 nesting!
+│ │ ├── AnalyzePatterns
+│ │ └── CalculateRiskScore
+│ └── ChargePayment
+├── Inventory (sub-workflow)
+│ ├── CheckInventory
+│ └── ReserveInventory
+├── Shipping (sub-workflow)
+│ ├── SelectCarrier
+│ └── CreateShipment
+└── OrderCompleted
+```
+
+## How Nested Workflows Work
+
+Sub-workflows are created using `BindAsExecutor()`, which converts a `Workflow` into an `ExecutorBinding` that can be used in another workflow's graph:
+
+```csharp
+// Build a sub-workflow
+Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
+ .WithName("SubPaymentProcessing")
+ .AddEdge(validatePayment, fraudCheckExecutor)
+ .AddEdge(fraudCheckExecutor, chargePayment)
+ .Build();
+
+// Bind as executor for use in the parent workflow
+ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
+
+// Use in the main workflow
+Workflow mainWorkflow = new WorkflowBuilder(orderReceived)
+ .AddEdge(orderReceived, paymentExecutor)
+ .Build();
+```
+
+Each sub-workflow runs as a separate orchestration instance in the Durable Task Scheduler, visible in the DTS dashboard Timeline view.
+
+## Environment Setup
+
+See the [README.md](../README.md) file in the parent directory for information on configuring the environment, including how to install and run the Durable Task Scheduler.
+
+## Running the Sample
+
+```bash
+cd dotnet/samples/Durable/Workflow/ConsoleApps/05_NestedWorkflows
+dotnet run --framework net10.0
+```
+
+### Sample Output
+
+```text
+╔══════════════════════════════════════════════════════════════════╗
+║ Nested Workflows Sample ║
+╠══════════════════════════════════════════════════════════════════╣
+║ Main Workflow: OrderProcessing ║
+║ ├── Payment (sub-workflow) ║
+║ │ ├── ValidatePayment ║
+║ │ ├── FraudCheck (sub-sub-workflow) ← Level 2 nesting! ║
+║ │ │ ├── AnalyzePatterns ║
+║ │ │ └── CalculateRiskScore ║
+║ │ └── ChargePayment ║
+║ ├── Inventory (sub-workflow) ║
+║ │ ├── CheckInventory ║
+║ │ └── ReserveInventory ║
+║ └── Shipping (sub-workflow) ║
+║ ├── SelectCarrier ║
+║ └── CreateShipment ║
+╚══════════════════════════════════════════════════════════════════╝
+
+Enter an order ID (or 'exit'):
+> ORD-001
+Starting order processing for 'ORD-001'...
+Run ID: abc123...
+Waiting for workflow to complete...
+✓ Order completed: Order ORD-001 completed. Tracking: TRACK-1A2B3C4D5E
+```
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/ServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/ServiceCollectionExtensions.cs
index da763488c5..c7d8cc5a85 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/ServiceCollectionExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/ServiceCollectionExtensions.cs
@@ -197,24 +197,33 @@ public static class ServiceCollectionExtensions
// Configure Durable Task Worker - capture sharedOptions reference in closure.
// The options object is populated by all Configure* calls before the worker starts.
- services.AddDurableTaskWorker(builder =>
+
+ if (workerBuilder is not null)
{
- workerBuilder?.Invoke(builder);
-
- builder.AddTasks(registry => RegisterTasksFromOptions(registry, sharedOptions));
- });
+ services.AddDurableTaskWorker(builder =>
+ {
+ workerBuilder?.Invoke(builder);
+ builder.AddTasks(registry => RegisterTasksFromOptions(registry, sharedOptions));
+ });
+ }
// Configure Durable Task Client
+ // Only register a client if explicitly configured. For Azure Functions,
+ // the Functions extension provides the DurableTaskClient automatically via bindings.
if (clientBuilder is not null)
{
services.AddDurableTaskClient(clientBuilder);
+
+ // These services depend on DurableTaskClient from DI, so only register them
+ // when we're registering a client. For Azure Functions, the client comes from
+ // bindings, not DI, so these won't work there.
+ services.TryAddSingleton();
+ services.TryAddSingleton(sp => sp.GetRequiredService());
+ services.TryAddSingleton();
}
- // Register workflow and agent services
- services.TryAddSingleton();
- services.TryAddSingleton(sp => sp.GetRequiredService());
+ // Register workflow and agent services that don't depend on DurableTaskClient
services.TryAddSingleton();
- services.TryAddSingleton();
// Register agent factories resolver - returns factories from the shared options
services.TryAddSingleton(
@@ -257,7 +266,11 @@ public static class ServiceCollectionExtensions
ExecutorBinding binding = activity.Binding;
registry.AddActivityFunc(
activity.ActivityName,
- (context, input) => DurableActivityExecutor.ExecuteAsync(binding, input));
+ (context, input) =>
+ {
+ // to do:a
+ return DurableActivityExecutor.ExecuteAsync(binding, input);
+ });
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs
index 903b2fb127..bd8e398ec3 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableExecutorDispatcher.cs
@@ -14,19 +14,20 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.Agents.AI.DurableTask.Workflows;
///
-/// Dispatches workflow executors to either activities or AI agents.
+/// Dispatches workflow executors to activities, AI agents, or sub-workflow orchestrations.
///
///
/// Called during the dispatch phase of each superstep by
/// DurableWorkflowRunner.DispatchExecutorsInParallelAsync. For each executor that has
/// pending input, this dispatcher determines whether the executor is an AI agent (stateful,
-/// backed by Durable Entities) or a regular activity, and invokes the appropriate Durable Task API.
+/// backed by Durable Entities), a sub-workflow (child orchestration), or a regular activity,
+/// and invokes the appropriate Durable Task API.
/// The serialised string result is returned to the runner for the routing phase.
///
internal static class DurableExecutorDispatcher
{
///
- /// Dispatches an executor based on its type (activity or AI agent).
+ /// Dispatches an executor based on its type (activity, AI agent, or sub-workflow).
///
/// The task orchestration context.
/// Information about the executor to dispatch.
@@ -46,6 +47,11 @@ internal static class DurableExecutorDispatcher
return await ExecuteAgentAsync(context, executorInfo, logger, envelope.Message).ConfigureAwait(true);
}
+ if (executorInfo.IsSubworkflowExecutor)
+ {
+ return await ExecuteSubWorkflowAsync(context, executorInfo, envelope.Message).ConfigureAwait(true);
+ }
+
return await ExecuteActivityAsync(context, executorInfo, envelope.Message, envelope.InputTypeName).ConfigureAwait(true);
}
@@ -96,4 +102,29 @@ internal static class DurableExecutorDispatcher
return response.Text;
}
+
+ ///
+ /// Executes a sub-workflow as a child orchestration.
+ ///
+ ///
+ /// The input is passed as a raw JSON object (not a string) to avoid double-encoding.
+ ///
+ private static async Task ExecuteSubWorkflowAsync(
+ TaskOrchestrationContext context,
+ WorkflowExecutorInfo executorInfo,
+ string input)
+ {
+ string orchestrationName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorInfo.SubWorkflow!.Name!);
+
+ // Parse the input JSON to pass as an object, preventing double-encoding.
+ // The sub-workflow orchestrator receives DurableWorkflowInput