Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions docs/features/durable-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ using IHost app = FunctionsApplication
app.Run();
```

To host durable workflows alongside agents, add a `ConfigureDurableWorkflows` call. The two methods compose, in any order and any number of times:

```csharp
using IHost app = FunctionsApplication
.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableAgents(agents => agents.AddAIAgent(agent))
.ConfigureDurableWorkflows(workflows => workflows.AddWorkflow(workflow))
.Build();
app.Run();
```

Alternatively, `ConfigureDurableOptions` configures both from a single delegate and can be freely mixed with the methods above.

**Python example:**

```python
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

// This sample demonstrates using ConfigureDurableOptions to register BOTH agents AND workflows
// in a single Azure Functions app. It uses a workflow to translate text and a standalone AI agent
// accessible via HTTP and MCP tool triggers.
// This sample demonstrates registering BOTH agents AND workflows in a single Azure Functions app.
// It uses a workflow to translate text and a standalone AI agent accessible via HTTP and MCP tool
// triggers.
//
// ConfigureDurableAgents and ConfigureDurableWorkflows compose: call them in any order, as many
// times as you like, and the configurations are additive. ConfigureDurableOptions is an equivalent
// alternative that configures both from a single delegate - see the README for that variant.

#pragma warning disable IDE0002 // Simplify Member Access

Expand Down Expand Up @@ -49,17 +53,15 @@
.AddEdge(translateText, formatOutput)
.Build();

// Use ConfigureDurableOptions to register both agents and workflows together
// Register agents and workflows through separate, composable calls.
using IHost app = FunctionsApplication
.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableOptions(options =>
{
// Register the standalone agent with HTTP and MCP tool triggers
options.Agents.AddAIAgent(assistant, enableHttpTrigger: true, enableMcpToolTrigger: true);

// Register the workflow with an HTTP endpoint and MCP tool trigger
options.Workflows.AddWorkflow(translateWorkflow, enableStatusEndpoint: false, enableMcpToolTrigger: true);
})
// Register the standalone agent with HTTP and MCP tool triggers
.ConfigureDurableAgents(agents => agents.AddAIAgent(assistant, enableHttpTrigger: true, enableMcpToolTrigger: true))

// Register the workflow with an HTTP endpoint and MCP tool trigger
.ConfigureDurableWorkflows(workflows => workflows.AddWorkflow(translateWorkflow, enableStatusEndpoint: false, enableMcpToolTrigger: true))
.Build();
app.Run();
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
# Workflow and Agents Sample

This sample demonstrates how to use `ConfigureDurableOptions` to register **both** AI agents **and** workflows in a single Azure Functions app. This is the recommended approach when your application needs both standalone agents and orchestrated workflows.
This sample demonstrates how to register **both** AI agents **and** workflows in a single Azure Functions app, using separate `ConfigureDurableAgents` and `ConfigureDurableWorkflows` calls.

These methods compose: call them in any order, as many times as you like, and the configurations are additive.

```csharp
using IHost app = FunctionsApplication
.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableAgents(agents => agents.AddAIAgent(assistant, enableHttpTrigger: true, enableMcpToolTrigger: true))
.ConfigureDurableWorkflows(workflows => workflows.AddWorkflow(translateWorkflow, enableMcpToolTrigger: true))
.Build();
app.Run();
```

If you prefer to configure everything from a single delegate, `ConfigureDurableOptions` is an equivalent alternative and can be freely mixed with the two methods above:

```csharp
.ConfigureDurableOptions(options =>
{
options.Agents.AddAIAgent(assistant, enableHttpTrigger: true, enableMcpToolTrigger: true);
options.Workflows.AddWorkflow(translateWorkflow, enableMcpToolTrigger: true);
})
```

## Key Concepts Demonstrated

- **Unified Configuration**: Use `ConfigureDurableOptions` to register agents and workflows together
- **Composable Configuration**: `ConfigureDurableAgents` and `ConfigureDurableWorkflows` combine in the same app
- **Standalone Agent**: An AI agent accessible via HTTP and MCP tool triggers
- **Workflow**: A simple text translation workflow also exposed as an MCP tool
- **Mixed Triggers**: Both agents and workflows coexist in the same Functions host
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

// This sample demonstrates the THREE ways to configure durable agents and workflows:
//
// 1. ConfigureDurableAgents() - For standalone agents only
// 2. ConfigureDurableWorkflows() - For workflows only
// 3. ConfigureDurableOptions() - For both agents AND workflows
// 1. ConfigureDurableAgents() - Configures agents
// 2. ConfigureDurableWorkflows() - Configures workflows
// 3. ConfigureDurableOptions() - Configures agents and workflows from a single delegate
//
// KEY: All methods can be called MULTIPLE times - configurations are ADDITIVE.
// KEY: All three methods compose. Call them in any order, as many times as you like - the
// configurations are ADDITIVE and share a single underlying options instance.

using Azure.AI.OpenAI;
using Azure.Identity;
Expand Down Expand Up @@ -67,16 +68,16 @@
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
// METHOD 1: ConfigureDurableAgents - for standalone agents only
// METHOD 1: ConfigureDurableAgents - configures agents
services.ConfigureDurableAgents(
options => options.AddAIAgent(biologist),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));

// METHOD 2: ConfigureDurableWorkflows - for workflows only
// METHOD 2: ConfigureDurableWorkflows - configures workflows
services.ConfigureDurableWorkflows(options => options.AddWorkflow(physicsWorkflow));

// METHOD 3: ConfigureDurableOptions - for both agents AND workflows
// METHOD 3: ConfigureDurableOptions - configures both from a single delegate
services.ConfigureDurableOptions(options =>
{
options.Agents.AddAIAgent(chemist);
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Fixed `ConfigureDurableAgents` and `ConfigureDurableWorkflows` silently dropping the `workerBuilder` or `clientBuilder` supplied to a later call, so the Durable Task worker and client are now registered regardless of which configuration call provides them ([#67](https://github.com/microsoft/agent-framework-durable-extension/pull/67))
- Fixed a `JsonTypeInfo metadata ... was not provided` failure when persisting agent state for function calls or results that carry values the state serializer has no metadata for, such as the `AIContent` results returned by MCP tools ([#57](https://github.com/microsoft/agent-framework-durable-extension/pull/57))
- [BREAKING] Added `IWorkflowClient` overloads that start a registered workflow by name, and made workflow result deserialization case-insensitive so results can be read back when hosted in Azure Functions. External implementations of `IWorkflowClient` must implement the new members, and an untyped `null` first argument is now ambiguous between the `Workflow` and workflow-name overloads ([#48](https://github.com/microsoft/agent-framework-durable-extension/pull/48))
- [BREAKING] Removed the `AddAIAgents` and `AddWorkflows` bulk registration APIs and changed `AddWorkflow` to return `DurableWorkflowOptions` so multiple workflows can be registered fluently ([#39](https://github.com/microsoft/agent-framework-durable-extension/pull/39))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ namespace Microsoft.Agents.AI.DurableTask;
/// </para>
/// </remarks>
internal sealed class DurableServicesMarker;

/// <summary>
/// Marker class used to track whether the Durable Task worker has been configured.
/// </summary>
/// <remarks>
/// The worker is tracked separately from <see cref="DurableServicesMarker"/> because a worker builder
/// may be supplied by any <c>Configure*</c> call, not just the first one:
/// <code>
/// services.ConfigureDurableAgents(...); // 1st call - no worker builder
/// services.ConfigureDurableWorkflows(..., workerBuilder: b => ...); // 2nd call - worker builder
/// </code>
/// A single shared marker would cause the second call's builder to be silently dropped.
/// </remarks>
internal sealed class DurableTaskWorkerMarker;

/// <summary>
/// Marker class used to track whether the Durable Task client has been configured.
/// </summary>
/// <remarks>
/// See <see cref="DurableTaskWorkerMarker"/> for why the client is tracked separately from
/// <see cref="DurableServicesMarker"/>.
/// </remarks>
internal sealed class DurableTaskClientMarker;
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ private static void EnsureDurableServicesRegistered(
DurableOptions sharedOptions,
Action<IDurableTaskWorkerBuilder>? workerBuilder,
Action<IDurableTaskClientBuilder>? clientBuilder)
{
EnsureSharedServicesRegistered(services);
EnsureWorkerRegistered(services, sharedOptions, workerBuilder);
EnsureClientRegistered(services, clientBuilder);
}

private static void EnsureSharedServicesRegistered(IServiceCollection services)
{
// Use a marker to ensure we only register core services once
if (services.Any(d => d.ServiceType == typeof(DurableServicesMarker)))
Expand All @@ -195,27 +202,6 @@ private static void EnsureDurableServicesRegistered(

services.TryAddSingleton<DurableWorkflowRunner>();

// Configure Durable Task Worker - capture sharedOptions reference in closure.
// The options object is populated by all Configure* calls before the worker starts.

if (workerBuilder is not null)
{
services.AddDurableTaskWorker(builder =>
{
workerBuilder?.Invoke(builder);

builder.AddTasks(registry => RegisterTasksFromOptions(registry, sharedOptions));
});
}

// Configure Durable Task Client
if (clientBuilder is not null)
{
services.AddDurableTaskClient(clientBuilder);
services.TryAddSingleton<IWorkflowClient, DurableWorkflowClient>();
services.TryAddSingleton<IDurableAgentClient, DefaultDurableAgentClient>();
}

// Register workflow and agent services
services.TryAddSingleton<DataConverter, DurableDataConverter>();

Expand All @@ -227,6 +213,54 @@ private static void EnsureDurableServicesRegistered(
services.TryAddSingleton(sp => sp.GetRequiredService<DurableOptions>().Agents);
}

/// <summary>
/// Configures the Durable Task worker the first time a <c>Configure*</c> call supplies a worker
/// builder. The builder is tracked separately from the other core services so that a builder
/// supplied to a later call is still honored.
/// </summary>
private static void EnsureWorkerRegistered(
IServiceCollection services,
DurableOptions sharedOptions,
Action<IDurableTaskWorkerBuilder>? workerBuilder)
{
if (workerBuilder is null || services.Any(d => d.ServiceType == typeof(DurableTaskWorkerMarker)))
{
return;
}

services.AddSingleton<DurableTaskWorkerMarker>();

// Capture the sharedOptions reference in the closure.
// The options object is populated by all Configure* calls before the worker starts.
services.AddDurableTaskWorker(builder =>
{
workerBuilder(builder);

builder.AddTasks(registry => RegisterTasksFromOptions(registry, sharedOptions));
});
}

/// <summary>
/// Configures the Durable Task client the first time a <c>Configure*</c> call supplies a client
/// builder. The builder is tracked separately from the other core services so that a builder
/// supplied to a later call is still honored.
/// </summary>
private static void EnsureClientRegistered(
IServiceCollection services,
Action<IDurableTaskClientBuilder>? clientBuilder)
{
if (clientBuilder is null || services.Any(d => d.ServiceType == typeof(DurableTaskClientMarker)))
{
return;
}

services.AddSingleton<DurableTaskClientMarker>();

services.AddDurableTaskClient(clientBuilder);
services.TryAddSingleton<IWorkflowClient, DurableWorkflowClient>();
services.TryAddSingleton<IDurableAgentClient, DefaultDurableAgentClient>();
}

private static void RegisterTasksFromOptions(DurableTaskRegistry registry, DurableOptions durableOptions)
{
// Build registrations for all workflows including sub-workflows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Fixed `ConfigureDurableAgents` and `ConfigureDurableWorkflows` not composing when both are called on the same application: calling `ConfigureDurableAgents` first left the workflow functions without an executor, and calling `ConfigureDurableWorkflows` first registered the built-in function execution middleware twice ([#67](https://github.com/microsoft/agent-framework-durable-extension/pull/67))
- [BREAKING] Support bounded synchronous workflow HTTP invocation through query parameters and default workflow run responses to JSON, with `Accept: text/plain` available for the legacy text format and the same negotiated asynchronous response returned on timeout ([#52](https://github.com/microsoft/agent-framework-durable-extension/pull/52))
- Added `DurableTaskClient.AsWorkflowClient` so functions can invoke durable workflows without constructing an `HttpClient` ([#48](https://github.com/microsoft/agent-framework-durable-extension/pull/48))
- [BREAKING] Consolidated the `AddWorkflow` extension overloads into a single method with optional `enableStatusEndpoint` and `enableMcpToolTrigger` parameters, and changed it to return `DurableWorkflowOptions` instead of `void` so multiple workflows can be registered fluently ([#39](https://github.com/microsoft/agent-framework-durable-extension/pull/39))
Expand Down
Loading
Loading