Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e8d0bd9
.NET: [Feature Branch] Add basic durable workflow support (#3648)
kshyju Feb 7, 2026
b62b1f2
.NET: [Feature Branch] Add Azure Functions hosting support for durabl…
kshyju Feb 15, 2026
3256baa
.NET: [Feature Branch] Adding support for events & shared state in du…
kshyju Feb 21, 2026
2988568
.NET: [Feature Branch] Add nested sub-workflow support for durable w…
kshyju Feb 25, 2026
ad51aee
.NET: [Feature Branch] Add Human In the Loop support for durable work…
kshyju Mar 3, 2026
bb96bf6
Add changelog entries for durable workflow support (#4436)
kshyju Mar 3, 2026
154c44a
Merge main into feat/durable_task and resolve conflicts
kshyju Mar 3, 2026
f8eebfe
Bump Microsoft.DurableTask.Worker to 1.19.1 to fix version downgrade
kshyju Mar 3, 2026
cd3929c
Fix broken markdown links in durable workflow sample READMEs
kshyju Mar 3, 2026
123cd74
Fix build errors from main merge: Throw conflict, ExecuteAsync rename…
kshyju Mar 3, 2026
cd8344d
Move durable workflow samples to 04-hosting/DurableWorkflows
kshyju Mar 3, 2026
3c586b2
Fix build errors: remove duplicate base class members, update renamed…
kshyju Mar 3, 2026
9213645
Merge branch 'main' into feat/durable_task
kshyju Mar 4, 2026
ae6b23c
Fix dotnet format issues: add UTF-8 BOM and remove unused using
kshyju Mar 4, 2026
41d5c6e
Fix typo PaymentProcesser -> PaymentProcessor and garbled arrows in R…
kshyju Mar 4, 2026
f59eba4
Fix GetExecutorName to handle agent names with underscores
kshyju Mar 4, 2026
931ec6b
Align DurableTask.Client.AzureManaged to 1.19.1
kshyju Mar 4, 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
10 changes: 5 additions & 5 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@
<PackageVersion Include="Microsoft.Agents.ObjectModel.PowerFx" Version="2026.2.4.1" />
<PackageVersion Include="Microsoft.PowerFx.Interpreter" Version="1.8.1" />
<!-- Durable Task -->
<PackageVersion Include="Microsoft.DurableTask.Client" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Client" Version="1.19.1" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.19.1" />
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.19.1" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.19.1" />
<!-- Azure Functions -->
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.50.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.50.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.11.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.13.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
Expand Down
21 changes: 20 additions & 1 deletion dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@
<Folder Name="/Samples/02-agents/DeclarativeAgents/">
<Project Path="samples/02-agents/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/DurableWorkflows/" />
<Folder Name="/Samples/04-hosting/DurableWorkflows/ConsoleApps/">
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/03_ConditionalEdges/03_ConditionalEdges.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/04_WorkflowAndAgents/04_WorkflowAndAgents.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/05_WorkflowEvents/05_WorkflowEvents.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/06_WorkflowSharedState/06_WorkflowSharedState.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/07_SubWorkflows/07_SubWorkflows.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/ConsoleApps/08_WorkflowHITL/08_WorkflowHITL.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/DurableWorkflows/AzureFunctions/">
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/01_SequentialWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/02_ConcurrentWorkflow/02_ConcurrentWorkflow.csproj" />
<Project Path="samples/04-hosting/DurableWorkflows/AzureFunctions/03_WorkflowHITL/03_WorkflowHITL.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/">
<File Path="samples/GettingStarted/README.md" />
</Folder>
<Folder Name="/Samples/02-agents/AGUI/">
<File Path="samples/02-agents/AGUI/README.md" />
</Folder>
Expand Down Expand Up @@ -506,4 +525,4 @@
<Project Path="tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
</Folder>
</Solution>
</Solution>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- The Functions build tools don't like namespaces that start with a number -->
<AssemblyName>SingleAgent</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<!-- Azure Functions packages -->
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>

<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Agents.AI.Workflows;

namespace SequentialWorkflow;

/// <summary>
/// Looks up an order by its ID and return an Order object.
/// </summary>
internal sealed class OrderLookup() : Executor<string, Order>("OrderLookup")
{
public override async ValueTask<Order> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] OrderLookup: Starting lookup for order '{message}'");
Console.ResetColor();

// Simulate database lookup with delay
await Task.Delay(TimeSpan.FromMicroseconds(100), cancellationToken);

Order order = new(
Id: message,
OrderDate: DateTime.UtcNow.AddDays(-1),
IsCancelled: false,
Customer: new Customer(Name: "Jerry", Email: "jerry@example.com"));

Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($"│ [Activity] OrderLookup: Found order '{message}' for customer '{order.Customer.Name}'");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();

return order;
}
}

/// <summary>
/// Cancels an order.
/// </summary>
internal sealed class OrderCancel() : Executor<Order, Order>("OrderCancel")
{
public override async ValueTask<Order> HandleAsync(
Order message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] OrderCancel: Starting cancellation for order '{message.Id}'");
Console.ResetColor();

// Simulate a slow cancellation process (e.g., calling external payment system)
for (int i = 1; i <= 3; i++)
{
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine("│ [Activity] OrderCancel: Processing...");
Console.ResetColor();
}

Order cancelledOrder = message with { IsCancelled = true };

Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"│ [Activity] OrderCancel: ✓ Order '{cancelledOrder.Id}' has been cancelled");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();

return cancelledOrder;
}
}

/// <summary>
/// Sends a cancellation confirmation email to the customer.
/// </summary>
internal sealed class SendEmail() : Executor<Order, string>("SendEmail")
{
public override ValueTask<string> HandleAsync(
Order message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] SendEmail: Sending email to '{message.Customer.Email}'...");
Console.ResetColor();

string result = $"Cancellation email sent for order {message.Id} to {message.Customer.Email}.";

Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("│ [Activity] SendEmail: ✓ Email sent successfully!");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();

return ValueTask.FromResult(result);
}
}

internal sealed record Order(string Id, DateTime OrderDate, bool IsCancelled, Customer Customer);

internal sealed record Customer(string Name, string Email);

/// <summary>
/// Represents a batch cancellation request with multiple order IDs and a reason.
/// This demonstrates using a complex typed object as workflow input.
/// </summary>
#pragma warning disable CA1812 // Instantiated via JSON deserialization at runtime
internal sealed record BatchCancelRequest(string[] OrderIds, string Reason, bool NotifyCustomers);
#pragma warning restore CA1812

/// <summary>
/// Represents the result of processing a batch cancellation.
/// </summary>
internal sealed record BatchCancelResult(int TotalOrders, int CancelledCount, string Reason);

/// <summary>
/// Generates a status report for an order.
/// </summary>
internal sealed class StatusReport() : Executor<Order, string>("StatusReport")
{
public override ValueTask<string> HandleAsync(
Order message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] StatusReport: Generating report for order '{message.Id}'");
Console.ResetColor();

string status = message.IsCancelled ? "Cancelled" : "Active";
string result = $"Order {message.Id} for {message.Customer.Name}: Status={status}, Date={message.OrderDate:yyyy-MM-dd}";

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"│ [Activity] StatusReport: ✓ {result}");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();

return ValueTask.FromResult(result);
}
}

/// <summary>
/// Processes a batch cancellation request. Accepts a complex <see cref="BatchCancelRequest"/> object
/// as input, demonstrating how workflows can receive structured JSON input.
/// </summary>
internal sealed class BatchCancelProcessor() : Executor<BatchCancelRequest, BatchCancelResult>("BatchCancelProcessor")
{
public override async ValueTask<BatchCancelResult> HandleAsync(
BatchCancelRequest message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] BatchCancelProcessor: Processing {message.OrderIds.Length} orders");
Console.WriteLine($"│ [Activity] BatchCancelProcessor: Reason: {message.Reason}");
Console.WriteLine($"│ [Activity] BatchCancelProcessor: Notify customers: {message.NotifyCustomers}");
Console.ResetColor();

// Simulate processing each order
int cancelledCount = 0;
foreach (string orderId in message.OrderIds)
{
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
cancelledCount++;
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"│ [Activity] BatchCancelProcessor: ✓ Cancelled order '{orderId}'");
Console.ResetColor();
}

BatchCancelResult result = new(message.OrderIds.Length, cancelledCount, message.Reason);

Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"│ [Activity] BatchCancelProcessor: ✓ Batch complete: {cancelledCount}/{message.OrderIds.Length} cancelled");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();

return result;
}
}

/// <summary>
/// Generates a summary of the batch cancellation.
/// </summary>
internal sealed class BatchCancelSummary() : Executor<BatchCancelResult, string>("BatchCancelSummary")
{
public override ValueTask<string> HandleAsync(
BatchCancelResult message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine("│ [Activity] BatchCancelSummary: Generating summary");
Console.ResetColor();

string result = $"Batch cancellation complete: {message.CancelledCount}/{message.TotalOrders} orders cancelled. Reason: {message.Reason}";

Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"│ [Activity] BatchCancelSummary: ✓ {result}");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();

return ValueTask.FromResult(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.

// This sample demonstrates three workflows that share executors.
// The CancelOrder workflow cancels an order and notifies the customer.
// The OrderStatus workflow looks up an order and generates a status report.
// The BatchCancelOrders workflow accepts a complex JSON input to cancel multiple orders.
// Both CancelOrder and OrderStatus reuse the same OrderLookup executor, demonstrating executor sharing.

using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using SequentialWorkflow;

// Define executors for all workflows
OrderLookup orderLookup = new();
OrderCancel orderCancel = new();
SendEmail sendEmail = new();
StatusReport statusReport = new();
BatchCancelProcessor batchCancelProcessor = new();
BatchCancelSummary batchCancelSummary = new();

// Build the CancelOrder workflow: OrderLookup -> OrderCancel -> SendEmail
Workflow cancelOrder = new WorkflowBuilder(orderLookup)
.WithName("CancelOrder")
.WithDescription("Cancel an order and notify the customer")
.AddEdge(orderLookup, orderCancel)
.AddEdge(orderCancel, sendEmail)
.Build();

// Build the OrderStatus workflow: OrderLookup -> StatusReport
// This workflow shares the OrderLookup executor with the CancelOrder workflow.
Workflow orderStatus = new WorkflowBuilder(orderLookup)
.WithName("OrderStatus")
.WithDescription("Look up an order and generate a status report")
.AddEdge(orderLookup, statusReport)
.Build();

// Build the BatchCancelOrders workflow: BatchCancelProcessor -> BatchCancelSummary
// This workflow demonstrates using a complex JSON object as the workflow input.
Workflow batchCancelOrders = new WorkflowBuilder(batchCancelProcessor)
.WithName("BatchCancelOrders")
.WithDescription("Cancel multiple orders in a batch using a complex JSON input")
.AddEdge(batchCancelProcessor, batchCancelSummary)
.Build();

using IHost app = FunctionsApplication
.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableWorkflows(workflows => workflows.AddWorkflows(cancelOrder, orderStatus, batchCancelOrders))
.Build();
app.Run();
Loading
Loading