Azure Durable Agents in Microsoft Agent Framework with Docker Durable Task Scheduler (DTS) Emulator

Azure Durable Functions are an extension of Azure Functions that makes it easy to build long-running, stateful workflows in serverless environments.
Instead of managing state, retries, checkpoints and recovery custom built logic, Azure Durable Functions automatically handles them behind the scenes.
You can think of it as a workflow coordinator that orchestrates multiple tasks, waits for external events, schedules timers, and resumes execution even after application restarts or failures. Such implementation make it ideal for business processes that requires Human-in-the-loop (HITL) that may run for minutes, hours, or even days.
Azure Durable Functions uses an event-sourcing model where every action is recorded as an event. When the workflow needs to resume, the orchestrator rebuilds its state by replaying these events without requiring developers to manage state and infrastructure concerns.
In this article I will demonstrate a very basic setup of an Azure Durable Agent in MAF through Docker Emulator for Durable Task Scheduler (DTS) that can run durable and stateful agents.
SetUp
Create a new Azure Function project and add the following references.
dotnet add package Azure;
dotnet add package Azure.AI.OpenAI;
dotnet add package Microsoft.Agents.AI;
dotnet add package Microsoft.Extensions.AI;
dotnet add package Microsoft.Extensions.Configuration;
dotnet add package Microsoft.Extensions.DependencyInjection;
dotnet add package Microsoft.Extensions.Hosting;
dotnet add package Microsoft.Agents.AI.Hosting.AzureFunctions--prerelase;
dotnet add package Microsoft.Azure.Functions.Worker.Builder;
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore;
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged;
Of the above ,ensure that you don't miss to reference the following two libraries in the project.
Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged;
Otherwise you will face issue that is highlighted in the following GitHub post.
https://github.com/microsoft/agent-framework/issues/5927
Not referencing the above two libraries, will result in you having to declare a dummy orchestrator.
public static class MyDummyOrchestrator
{
[Function(nameof(MyDummyOrchestrator))]
public static Task RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
return Task.CompletedTask;
}
}
This is because the function worker fails to find an entry point of execution.
These are the major packages and version numbers that I have referenced in the project
Docker DTS Emulator >>
Install Docker Desktop if you don't have on your system and then pull the Docker image containing the DTS emulator
docker pull mcr.microsoft.com/dts/dts-emulator:latest
Now run the emulator in the Docker desktop terminal
docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
You can change the port numbers if you want to.
Ensure that the Emulator is up and running.
Navigate to http://localhost:8082/ and you should see the DTS endpoints running on port number 8082.
Add appsetting.json to the project
"AppSettings":
{
"Chat_DeploymentName": "Deployment Name",
"EndPoint": "Azure OpenAI endpoint",
"ApiKey": "Azure OpenAI API key"
}
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
"AZURE_OPENAI_ENDPOINT": "Azure OpenAI endpoint",
"AZURE_OPENAI_DEPLOYMENT_NAME": "Deployment Name"
}
}
In DURABLE_TASK_SCHEDULER_CONNECTION_STRING settings above, the Endpoint value should match the Endpoint that was set while configuring the Docker DTS Emulator.
launchSettings.json
{
"profiles": {
"Azure_Durable_SingleAgent": {
"commandName": "Project",
"commandLineArgs": "--port 7001",
"launchBrowser": false
}
}
}
--port 7001 value will be used to send HttpRequest to the running Agent.
host.json
{
"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"
}
}
}
}
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING" is the keyword above.
If you are using Azure storage, then there is no need for connectionStringName and type settings but in case you are using Durable Task Scheduler (DTS) which is the recommended approach , then setting type and connectionStringName property is required.
serviceDependencies.json
{
"dependencies": {
"appInsights1": {
"type": "appInsights"
},
"storage1": {
"type": "storage",
"connectionId": "AzureWebJobsStorage"
}
}
}
Code
Implementation is pretty straightforward.
After the above artifacts in place, add the following code to read the settings from appsettings.json in Program.cs of the project.
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.Build();
Read the credentials and register a Chatclient
var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);
builder.Services.AddKeyedChatClient(
"ChatClient",
(
sp =>
new AzureOpenAIClient(new Uri(configuration["AppSettings:EndPoint"]), credential)
.GetChatClient(configuration["AppSettings:Chat_DeploymentName"])
.AsIChatClient()
)
);
In the next step, register the AIAgent in the hosted DI container.
servicecollection.AddSingleton<ChatClientAgent>(sp =>
{
Func<ChatClientAgentOptions> func = () =>
{
return new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
Instructions = "You are good at explaining topics on history",
},
Name = "History",
Id = "1"
};
};
return new ChatClientAgent(sp.GetKeyedService<IChatClient>("ChatClient"), options: func());
});
Build the service and get the agent from the ServiceProvider as ChatClientAgent.
ServiceProvider serviceProvider = servicecollection.BuildServiceProvider();
var agent = serviceProvider.GetServices<ChatClientAgent>();
List<ChatClientAgent> chatclientagent = new(agent);
Then add this agent as DurableAgent to the Azure Function worker.
using IHost app = FunctionsApplication
.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableAgents(options => options.AddAIAgent(chatclientagent[0], timeToLive: TimeSpan.FromHours(1)))
.Build();
app.Run();
The time to live of agent is configured to to an hour.
The agent name History set during defining the agent will act as the Durable Agent endpoint.
In our example the HttpRequest endpoint and the call will be as follows
Invoke-RestMethod -Method Post `
-Uri http://localhost:7001/api/agents/History/run `
-ContentType text/plain `
-Body "Tell me more about World war 2"
Port number 7001 comes from the set value in launchSettings.json.
That's all. Go ahead and invoke the call.
I invoked it through PowerShell
You should see the Agent chat history and other details in the DTS Emulator Dashboard.
Execution >>
Conclusion
Leveraging Azure Durable Function for state management of Agents is the most optimal approach and effective approach to build reliable long running workflows that can recover through restarts and process failures without the over head of implementation and maintaining custom processes.
Though the above set up is an example for a very simple use case, in a few upcoming articles I will touch base on more advance use cases.
Till then stay tuned !!!



