This sample demonstrates how to build an agent-directed workflow (agent loop) using durable entities in Azure Durable Functions for Python backed by the Durable Task Scheduler.
It corresponds to the "Build your own using orchestrations and entities" approach described in the Durable Task for AI Agents comparison table.
Note: A Durable Task SDK version of this sample is also available. That version uses FastAPI with SSE streaming and gives you full control over the web host. This version uses the standard Azure Functions HTTP model (non-streaming responses).
In a typical deterministic workflow, your code controls the execution path. In an agent-directed workflow, the LLM drives the control flow — it decides which tools to call, in what order, and when the task is complete. The execution path isn't known until runtime.
Each chat session is a durable entity that holds the full conversation history. When you send a message, the entity runs the agent loop (call LLM → execute tools → repeat) and streams response chunks to Redis pub/sub. The HTTP function subscribes to the Redis channel and collects the full response before returning it as JSON.
agent-directed-workflows/
├── function_app.py # Entity, HTTP functions, and agent loop logic
├── tools.py # Tool definitions and execution logic
├── host.json # Functions host configuration (Durable Task Scheduler)
├── requirements.txt # Python dependencies
└── README.md
- Python 3.10+
- Azure Functions Core Tools v4+
- Docker (for the Durable Task Scheduler emulator and Redis)
# Durable Task Scheduler emulator
docker run --name dtsemulator -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
# Redis
docker run --name redis -d -p 6379:6379 redis:latestpip install -r requirements.txtCreate a local.settings.json file:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "python",
"DTS_CONNECTION_STRING": "Endpoint=http://localhost:8080;Authentication=None",
"TASKHUB_NAME": "default",
"REDIS_CONNECTION_STRING": "localhost:6379"
}
}Add these to your local.settings.json Values to use a real LLM instead of the echo fallback:
{
"AZURE_OPENAI_ENDPOINT": "https://<your-resource>.openai.azure.com",
"AZURE_OPENAI_DEPLOYMENT": "<your-deployment-name>"
}The sample uses DefaultAzureCredential — make sure you're signed in via az login.
func start# Send a message (returns JSON with full response)
curl -X POST http://localhost:7071/api/chat/session1 \
-H "Content-Type: application/json" \
-d '{"message":"What is the weather in Seattle?"}'
# View conversation history
curl http://localhost:7071/api/chat/session1/history
# Reset the conversation
curl -X POST http://localhost:7071/api/chat/session1/reset| Method | Endpoint | Description |
|---|---|---|
| POST | /api/chat/{sessionId} |
Send a message and get the full response as JSON. Body: {"message": "..."} |
| GET | /api/chat/{sessionId}/history |
Get the full conversation history for a session |
| POST | /api/chat/{sessionId}/reset |
Clear a session's conversation history |
| Aspect | Durable Task SDK | Durable Functions |
|---|---|---|
| Web host | FastAPI (full control) | Azure Functions HTTP triggers |
| Streaming | SSE via StreamingResponse |
Non-streaming (JSON response) |
| Entity registration | worker.add_entity() |
@bp.entity_trigger decorator |
| Client | DurableTaskSchedulerClient |
DurableOrchestrationClient |
| Configuration | Environment variables | host.json + local.settings.json |