diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..66023c649a
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,4 @@
+# Code ownership assignments
+# https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+
+python/packages/azurefunctions/ @microsoft/agentframework-durabletask-developers
diff --git a/.github/actions/azure-functions-integration-setup/action.yml b/.github/actions/azure-functions-integration-setup/action.yml
new file mode 100644
index 0000000000..6be5afb814
--- /dev/null
+++ b/.github/actions/azure-functions-integration-setup/action.yml
@@ -0,0 +1,36 @@
+name: Azure Functions Integration Test Setup
+description: Prepare local emulators and tools for Azure Functions integration tests
+
+runs:
+ using: "composite"
+ steps:
+ - name: Start Durable Task Scheduler Emulator
+ shell: bash
+ run: |
+ if [ "$(docker ps -aq -f name=dts-emulator)" ]; then
+ echo "Stopping and removing existing Durable Task Scheduler Emulator"
+ docker rm -f dts-emulator
+ fi
+ echo "Starting Durable Task Scheduler Emulator"
+ docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
+ echo "Waiting for Durable Task Scheduler Emulator to be ready"
+ timeout 30 bash -c 'until curl --silent http://localhost:8080/healthz; do sleep 1; done'
+ echo "Durable Task Scheduler Emulator is ready"
+ - name: Start Azurite (Azure Storage emulator)
+ shell: bash
+ run: |
+ if [ "$(docker ps -aq -f name=azurite)" ]; then
+ echo "Stopping and removing existing Azurite (Azure Storage emulator)"
+ docker rm -f azurite
+ fi
+ echo "Starting Azurite (Azure Storage emulator)"
+ docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
+ echo "Waiting for Azurite (Azure Storage emulator) to be ready"
+ timeout 30 bash -c 'until curl --silent http://localhost:10000/devstoreaccount1; do sleep 1; done'
+ echo "Azurite (Azure Storage emulator) is ready"
+ - name: Install Azure Functions Core Tools
+ shell: bash
+ run: |
+ echo "Installing Azure Functions Core Tools"
+ npm install -g azure-functions-core-tools@4 --unsafe-perm true
+ func --version
diff --git a/.github/workflows/dotnet-build-and-test.yml b/.github/workflows/dotnet-build-and-test.yml
index 8c9fe22ffc..ecf093a3e9 100644
--- a/.github/workflows/dotnet-build-and-test.yml
+++ b/.github/workflows/dotnet-build-and-test.yml
@@ -151,6 +151,14 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ # This setup action is required for both Durable Task and Azure Functions integration tests.
+ # We only run it on Ubuntu since the Durable Task and Azure Functions features are not available
+ # on .NET Framework (net472) which is what we use the Windows runner for.
+ - name: Set up Durable Task and Azure Functions Integration Test Emulators
+ if: github.event_name != 'pull_request' && matrix.integration-tests && matrix.os == 'ubuntu-latest'
+ uses: ./.github/actions/azure-functions-integration-setup
+ id: azure-functions-setup
+
- name: Run Integration Tests
shell: bash
if: github.event_name != 'pull_request' && matrix.integration-tests
@@ -172,6 +180,9 @@ jobs:
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
OpenAI__ChatReasoningModelId: ${{ vars.OPENAI__CHATREASONINGMODELID }}
+ # Azure OpenAI Models
+ AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
+ AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
# Azure AI Foundry
AzureAI__Endpoint: ${{ secrets.AZUREAI__ENDPOINT }}
AzureAI__DeploymentName: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
diff --git a/.github/workflows/python-code-quality.yml b/.github/workflows/python-code-quality.yml
index 871436509c..dd4c0b57cf 100644
--- a/.github/workflows/python-code-quality.yml
+++ b/.github/workflows/python-code-quality.yml
@@ -28,6 +28,8 @@ jobs:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -46,4 +48,6 @@ jobs:
with:
extra_args: --config python/.pre-commit-config.yaml --all-files
- name: Run Mypy
- run: uv run poe mypy
+ env:
+ GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}
+ run: uv run poe ci-mypy
diff --git a/.github/workflows/python-merge-tests.yml b/.github/workflows/python-merge-tests.yml
index bd5768b968..a30b3c4ac3 100644
--- a/.github/workflows/python-merge-tests.yml
+++ b/.github/workflows/python-merge-tests.yml
@@ -66,6 +66,11 @@ jobs:
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
+ # For Azure Functions integration tests
+ FUNCTIONS_WORKER_RUNTIME: "python"
+ DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
+ AzureWebJobsStorage: "UseDevelopmentStorage=true"
+
defaults:
run:
working-directory: python
@@ -87,6 +92,9 @@ jobs:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
+ - name: Set up Azure Functions Integration Test Emulators
+ uses: ./.github/actions/azure-functions-integration-setup
+ id: azure-functions-setup
- name: Test with pytest
timeout-minutes: 10
run: uv run poe all-tests -n logical --dist loadfile --dist worksteal --timeout 300 --retries 3 --retry-delay 10
diff --git a/.gitignore b/.gitignore
index 70c1563f5a..162eec9ba7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -205,10 +205,22 @@ agents.md
.claude/
WARP.md
+# Azurite storage emulator files
+*/__azurite_db_blob__.json
+*/__azurite_db_blob_extent__.json
+*/__azurite_db_queue__.json
+*/__azurite_db_queue_extent__.json
+*/__azurite_db_table__.json
+*/__blobstorage__/
+*/__queuestorage__/
+
+# Azure Functions local settings
+local.settings.json
+
# Frontend
**/frontend/node_modules/
**/frontend/.vite/
**/frontend/dist/
# Database files
-*.db
\ No newline at end of file
+*.db
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 3a85c615b7..fd7e69fd52 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -97,6 +97,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 6009e00f00..588dff618a 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -23,6 +23,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -80,6 +91,9 @@
+
+
+
@@ -162,6 +176,7 @@
+
@@ -289,13 +304,16 @@
+
+
+
@@ -305,7 +323,9 @@
+
+
@@ -317,12 +337,15 @@
+
+
+
diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props
index cbcc78bb9d..df707d9031 100644
--- a/dotnet/nuget/nuget-package.props
+++ b/dotnet/nuget/nuget-package.props
@@ -2,9 +2,9 @@
1.0.0
- $(VersionPrefix)-$(VersionSuffix).251110.2
- $(VersionPrefix)-preview.251110.2
- 1.0.0-preview.251110.2
+ $(VersionPrefix)-$(VersionSuffix).251113.1
+ $(VersionPrefix)-preview.251113.1
+ 1.0.0-preview.251113.1
Debug;Release;Publish
true
diff --git a/dotnet/samples/AzureFunctions/.editorconfig b/dotnet/samples/AzureFunctions/.editorconfig
new file mode 100644
index 0000000000..b43bf5ebd0
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/.editorconfig
@@ -0,0 +1,10 @@
+# .editorconfig
+[*.cs]
+
+# See https://github.com/Azure/azure-functions-durable-extension/issues/3173
+dotnet_diagnostic.DURABLE0001.severity = none
+dotnet_diagnostic.DURABLE0002.severity = none
+dotnet_diagnostic.DURABLE0003.severity = none
+dotnet_diagnostic.DURABLE0004.severity = none
+dotnet_diagnostic.DURABLE0005.severity = none
+dotnet_diagnostic.DURABLE0006.severity = none
diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj b/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj
new file mode 100644
index 0000000000..bce1b96f7b
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj
@@ -0,0 +1,42 @@
+
+
+ net9.0
+ v4
+ Exe
+ enable
+ enable
+
+ SingleAgent
+ SingleAgent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/Program.cs b/dotnet/samples/AzureFunctions/01_SingleAgent/Program.cs
new file mode 100644
index 0000000000..60b3103adc
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/Program.cs
@@ -0,0 +1,37 @@
+// 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.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+using OpenAI;
+
+// Get the Azure OpenAI endpoint and deployment name from environment variables.
+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.");
+
+// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
+string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
+AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
+ ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
+ : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
+
+// Set up an AI agent following the standard Microsoft Agent Framework pattern.
+const string JokerName = "Joker";
+const string JokerInstructions = "You are good at telling jokes.";
+
+AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName);
+
+// 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()
+ .ConfigureDurableAgents(options => options.AddAIAgent(agent))
+ .Build();
+app.Run();
diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/README.md b/dotnet/samples/AzureFunctions/01_SingleAgent/README.md
new file mode 100644
index 0000000000..5727f037c2
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/README.md
@@ -0,0 +1,89 @@
+# Single Agent Sample
+
+This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
+
+## Key Concepts Demonstrated
+
+- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
+- Registering agents with the Function app and running them using HTTP.
+- Conversation management (via session IDs) for isolated interactions.
+
+## Environment Setup
+
+See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
+
+## Running the Sample
+
+With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
+
+You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
+
+Bash (Linux/macOS/WSL):
+
+```bash
+curl -X POST http://localhost:7071/api/agents/Joker/run \
+ -H "Content-Type: text/plain" \
+ -d "Tell me a joke about a pirate."
+```
+
+PowerShell:
+
+```powershell
+Invoke-RestMethod -Method Post `
+ -Uri http://localhost:7071/api/agents/Joker/run `
+ -ContentType text/plain `
+ -Body "Tell me a joke about a pirate."
+```
+
+You can also send JSON requests:
+
+```bash
+curl -X POST http://localhost:7071/api/agents/Joker/run \
+ -H "Content-Type: application/json" \
+ -H "Accept: application/json" \
+ -d '{"message": "Tell me a joke about a pirate."}'
+```
+
+To continue a conversation, include the `thread_id` in the query string or JSON body:
+
+```bash
+curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=@dafx-joker@your-thread-id" \
+ -H "Content-Type: application/json" \
+ -H "Accept: application/json" \
+ -d '{"message": "Tell me another one."}'
+```
+
+The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
+
+```text
+Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
+```
+
+The expected `application/json` output will look something like:
+
+```json
+{
+ "status": 200,
+ "thread_id": "@dafx-joker@your-thread-id",
+ "response": {
+ "Messages": [
+ {
+ "AuthorName": "Joker",
+ "CreatedAt": "2025-11-11T12:00:00.0000000Z",
+ "Role": "assistant",
+ "Contents": [
+ {
+ "Type": "text",
+ "Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
+ }
+ ]
+ }
+ ],
+ "Usage": {
+ "InputTokenCount": 78,
+ "OutputTokenCount": 36,
+ "TotalTokenCount": 114
+ }
+ }
+}
+```
diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/demo.http b/dotnet/samples/AzureFunctions/01_SingleAgent/demo.http
new file mode 100644
index 0000000000..3b741adf31
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/demo.http
@@ -0,0 +1,8 @@
+# Default endpoint address for local testing
+@authority=http://localhost:7071
+
+### Prompt the agent
+POST {{authority}}/api/agents/Joker/run
+Content-Type: text/plain
+
+Tell me a joke about a pirate.
diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/host.json b/dotnet/samples/AzureFunctions/01_SingleAgent/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/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/AzureFunctions/01_SingleAgent/local.settings.json b/dotnet/samples/AzureFunctions/01_SingleAgent/local.settings.json
new file mode 100644
index 0000000000..3411463ac4
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/local.settings.json
@@ -0,0 +1,10 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
+ "AZURE_OPENAI_ENDPOINT": "",
+ "AZURE_OPENAI_DEPLOYMENT": ""
+ }
+}
\ No newline at end of file
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj
new file mode 100644
index 0000000000..4ec460450a
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj
@@ -0,0 +1,42 @@
+
+
+ net9.0
+ v4
+ Exe
+ enable
+ enable
+
+ AgentOrchestration_Chaining
+ AgentOrchestration_Chaining
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs
new file mode 100644
index 0000000000..a631e7715c
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Net;
+using System.Text.Json;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.DurableTask;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+
+namespace AgentOrchestration_Chaining;
+
+public static class FunctionTriggers
+{
+ public sealed record TextResponse(string Text);
+
+ [Function(nameof(RunOrchestrationAsync))]
+ public static async Task RunOrchestrationAsync([OrchestrationTrigger] TaskOrchestrationContext context)
+ {
+ DurableAIAgent writer = context.GetAgent("WriterAgent");
+ AgentThread writerThread = writer.GetNewThread();
+
+ AgentRunResponse initial = await writer.RunAsync(
+ message: "Write a concise inspirational sentence about learning.",
+ thread: writerThread);
+
+ AgentRunResponse refined = await writer.RunAsync(
+ message: $"Improve this further while keeping it under 25 words: {initial.Result.Text}",
+ thread: writerThread);
+
+ return refined.Result.Text;
+ }
+
+ // POST /singleagent/run
+ [Function(nameof(StartOrchestrationAsync))]
+ public static async Task StartOrchestrationAsync(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "singleagent/run")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client)
+ {
+ string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
+ orchestratorName: nameof(RunOrchestrationAsync));
+
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
+ await response.WriteAsJsonAsync(new
+ {
+ message = "Single-agent orchestration started.",
+ instanceId,
+ statusQueryGetUri = GetStatusQueryGetUri(req, instanceId),
+ });
+ return response;
+ }
+
+ // GET /singleagent/status/{instanceId}
+ [Function(nameof(GetOrchestrationStatusAsync))]
+ public static async Task GetOrchestrationStatusAsync(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "singleagent/status/{instanceId}")] HttpRequestData req,
+ string instanceId,
+ [DurableClient] DurableTaskClient client)
+ {
+ OrchestrationMetadata? status = await client.GetInstanceAsync(
+ instanceId,
+ getInputsAndOutputs: true,
+ req.FunctionContext.CancellationToken);
+
+ if (status is null)
+ {
+ HttpResponseData notFound = req.CreateResponse(HttpStatusCode.NotFound);
+ await notFound.WriteAsJsonAsync(new { error = "Instance not found" });
+ return notFound;
+ }
+
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(new
+ {
+ instanceId = status.InstanceId,
+ runtimeStatus = status.RuntimeStatus.ToString(),
+ input = status.SerializedInput is not null ? (object)status.ReadInputAs() : null,
+ output = status.SerializedOutput is not null ? (object)status.ReadOutputAs() : null,
+ failureDetails = status.FailureDetails
+ });
+ return response;
+ }
+
+ private static string GetStatusQueryGetUri(HttpRequestData req, string instanceId)
+ {
+ // NOTE: This can be made more robust by considering the value of
+ // request headers like "X-Forwarded-Host" and "X-Forwarded-Proto".
+ string authority = $"{req.Url.Scheme}://{req.Url.Authority}";
+ return $"{authority}/api/singleagent/status/{instanceId}";
+ }
+}
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs
new file mode 100644
index 0000000000..41f643a763
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs
@@ -0,0 +1,40 @@
+// 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.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+using OpenAI;
+
+// Get the Azure OpenAI endpoint and deployment name from environment variables.
+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.");
+
+// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
+string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
+AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
+ ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
+ : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
+
+// Single agent used by the orchestration to demonstrate sequential calls on the same thread.
+const string WriterName = "WriterAgent";
+const string WriterInstructions =
+ """
+ You refine short pieces of text. When given an initial sentence you enhance it;
+ when given an improved sentence you polish it further.
+ """;
+
+AIAgent writerAgent = client.GetChatClient(deploymentName).CreateAIAgent(WriterInstructions, WriterName);
+
+using IHost app = FunctionsApplication
+ .CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableAgents(options => options.AddAIAgent(writerAgent))
+ .Build();
+
+app.Run();
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/README.md b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/README.md
new file mode 100644
index 0000000000..e98885eced
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/README.md
@@ -0,0 +1,59 @@
+# Single Agent Orchestration Sample
+
+This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that orchestrates sequential calls to a single AI agent using the same conversation thread for context continuity.
+
+## Key Concepts Demonstrated
+
+- Orchestrating multiple interactions with the same agent in a deterministic order
+- Using the same `AgentThread` across multiple calls to maintain conversational context
+- Durable orchestration with automatic checkpointing and resumption from failures
+- HTTP API integration for starting and monitoring orchestrations
+
+## Environment Setup
+
+See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
+
+## Running the Sample
+
+With the environment setup and function app running, you can test the sample by sending an HTTP request to start the orchestration.
+
+You can use the `demo.http` file to start the orchestration, or a command line tool like `curl` as shown below:
+
+Bash (Linux/macOS/WSL):
+
+```bash
+curl -X POST http://localhost:7071/api/singleagent/run
+```
+
+PowerShell:
+
+```powershell
+Invoke-RestMethod -Method Post -Uri http://localhost:7071/api/singleagent/run
+```
+
+The response will be a JSON object that looks something like the following, which indicates that the orchestration has started.
+
+```json
+{
+ "message": "Single-agent orchestration started.",
+ "instanceId": "86313f1d45fb42eeb50b1852626bf3ff",
+ "statusQueryGetUri": "http://localhost:7071/api/singleagent/status/86313f1d45fb42eeb50b1852626bf3ff"
+}
+```
+
+The orchestration will proceed to run the WriterAgent twice in sequence:
+
+1. First, it writes an inspirational sentence about learning
+2. Then, it refines the initial output using the same conversation thread
+
+Once the orchestration has completed, you can get the status of the orchestration by sending a GET request to the `statusQueryGetUri` URL. The response will be a JSON object that looks something like the following:
+
+```json
+{
+ "failureDetails": null,
+ "input": null,
+ "instanceId": "86313f1d45fb42eeb50b1852626bf3ff",
+ "output": "Learning serves as the key, opening doors to boundless opportunities and a brighter future.",
+ "runtimeStatus": "Completed"
+}
+```
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/demo.http b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/demo.http
new file mode 100644
index 0000000000..aa4dcc4a16
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/demo.http
@@ -0,0 +1,3 @@
+### Start the single-agent orchestration
+POST http://localhost:7071/api/singleagent/run
+
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/host.json b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/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/AzureFunctions/02_AgentOrchestration_Chaining/local.settings.json b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/local.settings.json
new file mode 100644
index 0000000000..54dfbb5664
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/local.settings.json
@@ -0,0 +1,10 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
+ "AZURE_OPENAI_ENDPOINT": "",
+ "AZURE_OPENAI_DEPLOYMENT": ""
+ }
+}
diff --git a/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj
new file mode 100644
index 0000000000..8698b0a7b8
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj
@@ -0,0 +1,42 @@
+
+
+ net9.0
+ v4
+ Exe
+ enable
+ enable
+
+ AgentOrchestration_Concurrency
+ AgentOrchestration_Concurrency
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/FunctionTriggers.cs
new file mode 100644
index 0000000000..2d15dd585c
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/FunctionTriggers.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Net;
+using System.Text.Json;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.DurableTask;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+
+namespace AgentOrchestration_Concurrency;
+
+public static class FunctionsTriggers
+{
+ public sealed record TextResponse(string Text);
+
+ [Function(nameof(RunOrchestrationAsync))]
+ public static async Task