This directory contains samples for durable agent hosting using the Durable Task Scheduler. These samples demonstrate the worker-client architecture pattern, enabling distributed agent execution with persistent conversation state.
Warning
Breaking change on this branch. The unreleased schema-v2 runtime requires
DURABLE_AGENTS_DEPLOYMENT_MODE=isolated_v2 before a sample host starts. Set it only for a
new, empty, uniquely named task hub with upgraded clients and no old or unrelated workers.
Do not use default, an old shared hub, or upgrade a live hub in place. Start fresh workflow
instances after this update, even when upgrading from an earlier v2 build. Protocol 2 is
unchanged, but older v2 in-flight instances and recorded histories are unsupported and may fail
under the revised HITL checkpoints and mixed parent/child scheduling. The marker checks start
admission, not feature or replay compatibility. Keep old runs on their original deployment if
they must finish.
This is an operator acknowledgement, not proof of isolation or production readiness. There
is no automatic isolation check, compatibility fallback, or history migration. Follow
Environment Configuration for standalone and Azure Functions setup.
These samples import the durable hosting types directly from the extension packages,
agent_framework_durabletask and agent_framework_azurefunctions.
from agent_framework_durabletask import DurableAIAgentWorker, DurableWorkflowClient
from agent_framework_azurefunctions import AgentFunctionAppFor backward compatibility these entry-point types are also re-exported from
agent_framework.azure in the core agent-framework package, so existing
from agent_framework.azure import ... code keeps working. New and updated samples should use
the direct package imports shown above rather than the agent_framework.azure shim.
Install and verify these tools before Running the Samples:
- Docker – run the Durable Task Scheduler emulator locally
- uv – manage Python dependencies (optional but recommended)
- Azure CLI – authenticate with
az loginforAzureCliCredential
Windows (PowerShell):
winget install Docker.DockerDesktop
irm https://astral.sh/uv/install.ps1 | iex
winget install Microsoft.AzureCLImacOS / Linux:
# Docker: https://docs.docker.com/get-docker/
curl -LsSf https://astral.sh/uv/install.sh | sh
# Azure CLI: https://learn.microsoft.com/cli/azure/install-azure-cliVerify:
docker --version
uv --version
az account show- 01_single_agent: Host a single conversational agent and interact with it via a client. Demonstrates basic worker-client architecture and agent state management.
- 02_multi_agent: Host multiple domain-specific agents (physicist and chemist) and route requests to the appropriate agent based on the question topic.
- 03_single_agent_streaming: Enable reliable, resumable streaming using Redis Streams with agent response callbacks. Demonstrates non-blocking agent execution and cursor-based resumption for disconnected clients.
- 04_single_agent_orchestration_chaining: Chain multiple invocations of the same agent using durable orchestration, preserving conversation context across sequential runs.
- 05_multi_agent_orchestration_concurrency: Run multiple agents concurrently within an orchestration, aggregating their responses in parallel.
- 06_multi_agent_orchestration_conditionals: Implement conditional branching in orchestrations with spam detection and email assistant agents. Demonstrates structured outputs with Pydantic models and activity functions for side effects.
- 07_single_agent_orchestration_hitl: Human-in-the-loop pattern with external event handling, timeouts, and iterative refinement based on human feedback. Shows long-running workflows with external interactions.
- 08_workflow: Host a MAF
Workflowas a durable orchestration on a standalone worker viaDurableAIAgentWorker.configure_workflow. Demonstrates conditional routing and mixing AI agents with non-agent executors. - 09_workflow_hitl: A workflow that pauses for human approval using
ctx.request_info/@response_handler, with the client discovering and answering the pending request. - 10_workflow_streaming: Stream a hosted workflow's events as typed
WorkflowEventobjects by polling the orchestration's custom status. - 11_subworkflow: Compose workflows by embedding an inner
Workflowas a node viaWorkflowExecutor. On the durable host the inner workflow runs as its own child orchestration, and a singleconfigure_workflowcall registers both. - 12_subworkflow_hitl: A human-in-the-loop pause that lives inside a sub-workflow. The nested request surfaces to the client with a qualified request id (
{executor}~{ordinal}~{requestId}) behind a single top-level addressing surface.
- 13_conversation_compaction shows durable history with compaction and independent eager-pruning and pressure-budget settings.
- 14_external_history_redis keeps Redis as the primary history store, separate from durable response delivery and local retention.
These samples host workflows and agents on Azure Durable Functions (func start) instead of the worker-client model above. Each has its own setup steps in its README, and shared environment setup lives in azure_functions/README.md.
- azure_functions/01_single_agent: Host a single AI agent on Azure Functions with direct HTTP API access for interactive conversations.
- azure_functions/02_multi_agent: Host multiple AI agents on Azure Functions, each reachable via its own HTTP endpoint.
- azure_functions/03_reliable_streaming: Reliable, resumable streaming for durable agents using Redis Streams with cursor-based reconnection.
- azure_functions/04_single_agent_orchestration_chaining: Chain two invocations of the same agent inside a Durable Functions orchestration, preserving conversation state between runs.
- azure_functions/05_multi_agent_orchestration_concurrency: Run two agents in parallel inside a Durable Functions orchestration and merge their responses.
- azure_functions/06_multi_agent_orchestration_conditionals: Conditional orchestration that screens emails with a spam-detector agent and drafts replies with an email assistant agent.
- azure_functions/07_single_agent_orchestration_hitl: Human-in-the-loop orchestration where a writer agent iterates until a reviewer approves or the attempt limit is reached.
- azure_functions/08_mcp_server: Expose agents as both HTTP endpoints and Model Context Protocol (MCP) tools.
- azure_functions/09_workflow_shared_state: Run a MAF
WorkflowwithSharedStateon Azure Durable Functions. - azure_functions/10_workflow_no_shared_state: Run a MAF
Workflowon Azure Durable Functions without SharedState. - azure_functions/11_workflow_parallel: Parallel execution of executors and agents in an Azure Durable Functions workflow.
- azure_functions/12_workflow_hitl: The workflow human-in-the-loop pattern on Azure Durable Functions, with the reviewer notified from inside the workflow via
WorkflowHitlContext. - azure_functions/13_subworkflow_hitl: A human-in-the-loop pause inside a sub-workflow on Azure Durable Functions, exposed through a single top-level
respondendpoint. - azure_functions/14_conversation_compaction shows durable compaction on Functions with independent eager pruning and explicit local byte budgets.
The retention default is retention="keep_all". Standalone DurableTaskSchedulerWorker hosts
use a 1 MiB pressure budget when max_state_bytes is omitted. Explicit None opts out. Generic
workers and Azure Functions remain disabled by default. Compaction does not itself delete stored
messages unless follow_compaction is enabled. Pressure eviction independently triggers at the
0.85 high watermark toward 0.70, subject to protected state. The whole-entity ASCII-escaped
JSON estimate is not a backend acceptance guarantee. "backend_limit" resolves to 1 MiB only
with DurableTaskSchedulerWorker. Azure Functions rejects it. Samples that explicitly pass
max_state_bytes=None continue to disable pressure eviction.
Completion and ingestion receipts, live results and session/control state survive transcript
eviction. An unreachable protected floor raises StateCapacityError. There is no bounded receipt
cleanup, and idle response expiry needs application-owned maintenance. Retention metrics
describe staged changes, never confirmed commits. Per-agent and workflow budget overrides use
INHERIT to inherit and None to disable pressure eviction. See the package's
retention contract and
metric semantics.
These samples are designed to be run locally in a cloned repository.
The following prerequisites are required for the standalone samples. For Functions, use the Functions prerequisites.
- Python 3.10 or later
- Azure CLI installed and authenticated (
az login) - Microsoft Foundry project with a deployed model, configured through
FOUNDRY_PROJECT_ENDPOINTandFOUNDRY_MODEL(gpt-4o-mini or better is recommended) - Durable Task Scheduler (local emulator or Azure-hosted)
- Docker installed if running the Durable Task Scheduler emulator locally
These samples are configured to use the Azure OpenAI service with RBAC permissions to access the model. You'll need to configure the RBAC permissions for the Azure OpenAI service to allow the Python app to access the model.
Below is an example of how to configure the RBAC permissions for the Azure OpenAI service to allow the current user to access the model.
Bash (Linux/macOS/WSL):
az role assignment create \
--assignee "[email protected]" \
--role "Cognitive Services OpenAI User" \
--scope /subscriptions/<your-subscription-id>/resourceGroups/<your-resource-group-name>/providers/Microsoft.CognitiveServices/accounts/<your-openai-resource-name>PowerShell:
az role assignment create `
--assignee "[email protected]" `
--role "Cognitive Services OpenAI User" `
--scope /subscriptions/<your-subscription-id>/resourceGroups/<your-resource-group-name>/providers/Microsoft.CognitiveServices/accounts/<your-openai-resource-name>More information on how to configure RBAC permissions for Azure OpenAI can be found in the Azure OpenAI documentation.
The standalone samples use the Durable Task Scheduler (DTS) to support hosted agents and durable orchestrations. DTS also provides a dashboard for their state. The Azure Functions samples ship with the default Azure Storage backend, using Azurite locally. A DTS connection string alone does not change that backend. See the optional Functions DTS setup.
To run the Durable Task Scheduler locally, you can use the following docker command:
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 -e DTS_USE_DYNAMIC_TASK_HUBS=true mcr.microsoft.com/dts/dts-emulator:latestDynamic task hubs allow the emulator to create the fresh named hubs used below, matching the CI
setup. This does not verify isolation. The DTS dashboard will be available at http://localhost:8082.
Choose a new, unique alphanumeric hub name starting with a letter. The examples use
durablesamplev2UNIQUE as a placeholder. Replace UNIQUE with your own unique alphanumeric
suffix and use the resulting name consistently. Verify the hub is empty and reserved for this
sample deployment. Provision the hub first when using an Azure-hosted scheduler. Do not reuse
default or a hub containing old state, even if another sample guide or template uses it.
Upgrade all clients that will access the new hub to match this branch's runtime. Keep old workers
and clients off the new hub and start new workflow instances. If old runs must finish, retain their
original deployment. Setting the flag, retaining protocol 2, or rewrapping an old start input does
not migrate history. Do not hard-code deployment_mode in sample workers or add an automatic
fallback to bypass this deployment decision.
Set these variables in both the worker and client terminals, using the same chosen hub name and endpoint. For combined samples, set them in the terminal running the sample. The endpoint below is for the local emulator.
Standalone workers and clients reject a missing, blank, or case-insensitive default
TASKHUB value before they construct a scheduler connection. This keeps a sample from
silently attaching to a shared live hub when the environment is incomplete.
POSIX shell (Linux/macOS/WSL):
export FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project"
export FOUNDRY_MODEL="your-deployment-name"
export ENDPOINT="http://localhost:8080"
export TASKHUB="durablesamplev2UNIQUE"
export DURABLE_AGENTS_DEPLOYMENT_MODE="isolated_v2"PowerShell:
$env:FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project"
$env:FOUNDRY_MODEL="your-deployment-name"
$env:ENDPOINT="http://localhost:8080"
$env:TASKHUB="durablesamplev2UNIQUE"
$env:DURABLE_AGENTS_DEPLOYMENT_MODE="isolated_v2"For host-generated workflows, use the upgraded DurableWorkflowClient.start_workflow with
the application input and workflow name (or a constructor default). It supplies the v2 start
envelope. Azure Functions generated workflow start routes do the same. Application-owned
native orchestrators keep their original raw input contracts. Do not wrap their payloads.
Copy the sample's local-settings template as described in
azure_functions/README.md, then merge these entries into its
Values object before func start. Keep the existing storage, model, and other sample settings.
Replace every durablesamplev2UNIQUE with the same new, empty hub name chosen for that function app.
These settings supersede older instructions to leave the hub as default.
{
"Values": {
"DURABLE_AGENTS_DEPLOYMENT_MODE": "isolated_v2",
"TASKHUB_NAME": "durablesamplev2UNIQUE",
"AzureFunctionsJobHost__extensions__durableTask__hubName": "durablesamplev2UNIQUE"
}
}The host setting explicitly selects the hub even for samples without a TASKHUB_NAME binding.
Keep it and TASKHUB_NAME identical. As shipped, Functions uses Azure Storage through
AzureWebJobsStorage, so verify the new hub is isolated in that storage account or local Azurite
instance. Setting DURABLE_TASK_SCHEDULER_CONNECTION_STRING does not select DTS. To opt in on a
new deployment, configure storageProvider.type="azureManaged", a supporting host extension, and
a connection string whose TaskHub matches the host hub. Follow the
optional Functions DTS setup.
Do not switch an existing hub's backend as a migration. See also the Azure Functions
host configuration override guidance.
Navigate to the sample directory and install dependencies. For example:
cd samples/01_single_agent
pip install -r requirements.txtIf you're using uv for package management:
uv pip install -r requirements.txtEach sample follows a worker-client architecture. Most samples provide separate worker.py and client.py files, though some include a combined sample.py for convenience.
Running with separate worker and client:
In one terminal, start the worker:
python worker.pyIn another terminal, run the client:
python client.pyRunning with combined sample:
python sample.pyThe sample output is displayed directly in the terminal where you ran the Python script. Agent responses are printed to stdout with log formatting for better readability.
For standalone samples or Functions deployments explicitly using DTS, you can also see the state
of agents and orchestrations in its dashboard at http://localhost:8082. Functions deployments
using the default Azure Storage backend do not appear there.