Microsoft Agent Framework Agents Created: 10 May 2026 Updated: 10 May 2026

Durable Agent Pipelines with Microsoft.Agents.AI.DurableTask

A multi-agent pipeline is only as reliable as the infrastructure running it. If the process crashes between two agent steps, all intermediate output is lost and the whole run must restart from scratch. Durable workflows fix this by persisting each step's result to an external store before advancing to the next step.

The Microsoft Agent Framework exposes this capability through the Microsoft.Agents.AI.DurableTask package, which integrates with the open-source Durable Task Framework and the Azure Durable Task Scheduler (DTS).

Scenario — Travel Planning Pipeline

Three specialized agents run sequentially. Each agent's output becomes the next agent's input, and every step is checkpointed by DTS:

TravelAdvisor → ItineraryPlanner → BudgetEstimator
  1. TravelAdvisor — recommends one destination based on the user's preferences.
  2. ItineraryPlanner — creates a short 3-day itinerary for that destination.
  3. BudgetEstimator — estimates the total trip cost with a brief breakdown.

Key Concepts

1. WorkflowBuilder

The agent graph is built with WorkflowBuilder. Call .WithName() and .WithDescription() so DTS can register the workflow under a stable identity:

Workflow travelPipeline = new WorkflowBuilder(advisor)
.WithName("TravelPlanning")
.WithDescription("Plan a trip: recommend destination, build itinerary, estimate budget")
.AddEdge(advisor, planner)
.AddEdge(planner, budget)
.Build();

2. IHost + ConfigureDurableWorkflows

Durable workflows need a worker process (which polls DTS for orchestration tasks) and a client (which submits new runs). Both are wired up through the standard Microsoft.Extensions.Hosting pipeline using the ConfigureDurableWorkflows extension method from Microsoft.Agents.AI.DurableTask:

IHost host = Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
workflowOptions => workflowOptions.AddWorkflow(travelPipeline),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();

UseDurableTaskScheduler is an extension from Microsoft.DurableTask.Client.AzureManaged (client side) and Microsoft.DurableTask.Worker.AzureManaged (worker side). It accepts a connection string in the format:

Endpoint=http://localhost:8080;TaskHub=default;Authentication=None

For local development you can run DTS in Docker. For production, substitute the Azure DTS endpoint and use Authentication=ManagedIdentity or Authentication=DefaultAzureCredential.

3. Submitting a Run

After starting the host, resolve IWorkflowClient from DI and call RunAsync with the workflow definition and a typed input record:

IWorkflowClient workflowClient =
host.Services.GetRequiredService<IWorkflowClient>();

TravelRequest request = new("warm climate, beach, 3 days, budget traveler");

IAwaitableWorkflowRun run =
(IAwaitableWorkflowRun)await workflowClient.RunAsync(travelPipeline, request);

TravelRequest is a plain C# record that carries the user's preferences. It is serialized to JSON by DTS and passed as the workflow's initial input.

public record TravelRequest(string Preferences);

4. Awaiting Completion

RunAsync returns an IWorkflowRun. Cast it to IAwaitableWorkflowRun to get access to WaitForCompletionAsync<T>, which blocks until all agents have finished and returns the final agent's output:

Console.WriteLine($"Workflow run id: {run.RunId}");

string? result = await run.WaitForCompletionAsync<string>();
Console.WriteLine($"\nWorkflow completed:\n{result}");

5. Graceful Shutdown

Always stop the host in a finally block so that the worker's background polling loop is cleanly terminated:

await host.StartAsync();
try
{
// ... submit and await the run ...
}
finally
{
await host.StopAsync();
}

Required NuGet Packages

<PackageReference Include="Microsoft.Agents.AI" Version="1.5.0" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" Version="1.5.0" />
<PackageReference Include="Microsoft.Agents.AI.DurableTask" Version="1.5.0-preview.*" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" Version="*-*" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" Version="*-*" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="*" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.5.2" />

Running the Demo Locally

  1. Start DTS locally with Docker:
docker run -p 8080:8080 mcr.microsoft.com/durable-task-scheduler:latest
  1. Set the required environment variable:
$env:OPEN_AI_KEY = "sk-..."
  1. Optionally override the DTS endpoint:
$env:DURABLE_TASK_SCHEDULER_CONNECTION_STRING = "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
  1. Run the project:
dotnet run

Expected console output (truncated):

--- Durable Travel Pipeline ---
Starting durable workflow for: 'warm climate, beach, 3 days, budget traveler'...

Workflow run id: abc123-...

Workflow completed:
Flight: ~$350, Hotel (3 nights): ~$240, Food & activities: ~$120
Total estimated cost: ~$710

Full Example

// AgentsInWorkflowsDemo.cs
// Demonstrates Microsoft Agent Framework — Agents in Workflows.
//
// Scenario: Travel Planning Pipeline
// 1. TravelAdvisorAgent — suggests a destination based on user preferences.
// 2. ItineraryAgent — creates a 3-day itinerary for that destination.
// 3. BudgetEstimatorAgent — estimates the total trip cost.
//
// Demo 1: In-process streaming execution.
// Demo 2: Durable execution via Microsoft.Agents.AI.DurableTask + DTS.

using Microsoft.Agents.AI;
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.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenAI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Agents;

/// <summary>Travel preferences submitted to the durable workflow.</summary>
public record TravelRequest(string Preferences);

/// <summary>
/// Demonstrates ChatClientAgent instances wired together in a WorkflowBuilder pipeline.
/// Uses a travel-planning domain to show sequential agent topologies,
/// both in-process (Demo 1) and durable (Demo 2).
/// </summary>
public static class AgentsInWorkflowsDemo
{
public static async Task RunAsync()
{
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY")
?? throw new InvalidOperationException(
"Set the OPEN_AI_KEY environment variable before running.");

IChatClient chatClient = new OpenAIClient(apiKey)
.GetChatClient("gpt-4o-mini")
.AsIChatClient();

Console.WriteLine("====== Agents in Workflows — Travel Planning Pipeline ======\n");

// await Demo1_LinearPipelineAsync(chatClient);
await Demo2_DurablePipelineAsync(chatClient);
}

// ──────────────────────────────────────────────────────────
// Demo 1 — Linear Agent Pipeline
// TravelAdvisor → Itinerary → BudgetEstimator
// ──────────────────────────────────────────────────────────
private static async Task Demo1_LinearPipelineAsync(IChatClient chatClient)
{
Console.WriteLine("--- Demo 1: Linear Travel Pipeline ---");
Console.WriteLine("Input: warm climate, beach, 3 days, budget traveler\n");

var advisor = chatClient.AsAIAgent(
name: "TravelAdvisor",
instructions: "You are a travel advisor. Given travel preferences, recommend "
+ "one destination and explain it in 1-2 sentences. Only output the recommendation.");

var planner = chatClient.AsAIAgent(
name: "ItineraryPlanner",
instructions: "You are a travel planner. Given a destination recommendation, "
+ "create a short 3-day itinerary (one activity per day). Keep it concise.");

var budget = chatClient.AsAIAgent(
name: "BudgetEstimator",
instructions: "You are a budget analyst. Given a 3-day travel itinerary, "
+ "estimate the total cost in USD with a short breakdown (flight, hotel, food). "
+ "Output 2-3 lines only.");

var workflow = new WorkflowBuilder(advisor)
.AddEdge(advisor, planner)
.AddEdge(planner, budget)
.Build();

await using var run = await InProcessExecution.RunStreamingAsync(
workflow,
new ChatMessage(ChatRole.User, "warm climate, beach, 3 days, budget traveler"));

await run.TrySendMessageAsync(new TurnToken(emitEvents: true));

string currentAgent = "";
await foreach (var evt in run.WatchStreamAsync())
{
if (evt is AgentResponseUpdateEvent update)
{
if (update.ExecutorId != currentAgent)
{
if (currentAgent != "") Console.WriteLine("\n");
currentAgent = update.ExecutorId;
Console.Write($"[{currentAgent}] ");
}
Console.Write(update.Data);
}
}

Console.WriteLine("\n");
}

// ──────────────────────────────────────────────────────────
// Demo 2 — Durable Travel Pipeline
// Same graph hosted via Microsoft.Agents.AI.DurableTask.
// Requires a running Durable Task Scheduler (DTS) reachable
// at DURABLE_TASK_SCHEDULER_CONNECTION_STRING (or localhost:8080).
// ──────────────────────────────────────────────────────────
private static async Task Demo2_DurablePipelineAsync(IChatClient chatClient)
{
Console.WriteLine("--- Demo 2: Durable Travel Pipeline ---");

string dtsConnectionString =
Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";

var advisor = chatClient.AsAIAgent(
name: "TravelAdvisor",
instructions: "You are a travel advisor. Given travel preferences, recommend "
+ "one destination and explain it in 1-2 sentences. Only output the recommendation.");

var planner = chatClient.AsAIAgent(
name: "ItineraryPlanner",
instructions: "You are a travel planner. Given a destination recommendation, "
+ "create a short 3-day itinerary (one activity per day). Keep it concise.");

var budget = chatClient.AsAIAgent(
name: "BudgetEstimator",
instructions: "You are a budget analyst. Given a 3-day travel itinerary, "
+ "estimate the total cost in USD with a short breakdown (flight, hotel, food). "
+ "Output 2-3 lines only.");

Workflow travelPipeline = new WorkflowBuilder(advisor)
.WithName("TravelPlanning")
.WithDescription("Plan a trip: recommend destination, build itinerary, estimate budget")
.AddEdge(advisor, planner)
.AddEdge(planner, budget)
.Build();

IHost host = Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
workflowOptions => workflowOptions.AddWorkflow(travelPipeline),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();

await host.StartAsync();
try
{
IWorkflowClient workflowClient =
host.Services.GetRequiredService<IWorkflowClient>();

TravelRequest request = new("warm climate, beach, 3 days, budget traveler");
Console.WriteLine($"Starting durable workflow for: '{request.Preferences}'...\n");

IAwaitableWorkflowRun run =
(IAwaitableWorkflowRun)await workflowClient.RunAsync(travelPipeline, request);

Console.WriteLine($"Workflow run id: {run.RunId}");

string? result = await run.WaitForCompletionAsync<string>();
Console.WriteLine($"\nWorkflow completed:\n{result}");
}
finally
{
await host.StopAsync();
}

Console.WriteLine("\n");
}
}

References

  1. Azure Durable Task Scheduler documentation
  2. IHost — Microsoft.Extensions.Hosting
  3. Durable Task Framework for .NET (GitHub)


Share this lesson: