Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Durable Functions Agentic Workflows — Companion Code

Code samples for the blog post Durable Functions as the Orchestration Layer for Directed Agentic Workflows.

Five patterns, each implemented as a self-contained Durable Functions orchestrator in C# (.NET 8, isolated worker). All patterns share a single function app and deploy via azd.

Patterns

Orchestrator Pattern Agentic scenario Verified
DocumentPipelineOrchestrator Function chaining Sequential AI pipeline — extract, classify, call LLM, write result
ParallelRetrievalOrchestrator Fan-out / fan-in Parallel retrieval from 3 sources, aggregated LLM call
ApprovalOrchestrator Human interaction AI classification + human review with 24h timeout
MonitorOrchestrator Monitor Poll every 5 minutes until processing completes or 24h timeout
RiskRoutingOrchestrator Risk-class routing AI classification → LOW/MEDIUM/HIGH branch with human-in-the-loop

Prerequisites

Quick start (local)

# Start Azurite in a separate terminal
npx azurite --silent

cd src
cp local.settings.json.example local.settings.json
dotnet build
func start

Deploy to Azure

azd up

Select Central US when prompted — the Durable Task Scheduler preview resource is available there. After provisioning completes, assign the additional storage roles manually (see Known Issues below).

Testing each pattern

See TESTING.md for the full curl command sequence for each pattern including how to submit external events for the approval and risk-routing patterns.

Quick summary of verified results:

Pattern 1 — Sequential pipeline:

curl -X POST "<base-url>/api/pipeline/start?code=<key>" \
  -H "Content-Type: application/json" \
  -d '{"documentId": "doc-001", "content": "Insurance claim for water damage."}'
# Result: {"Pattern":"chaining","Succeeded":true,"Result":"[Stub] Answer to 'doc-001'..."}

Pattern 5 — Risk routing (LOW, instant):

curl -X POST "<base-url>/api/risk/start?code=<key>" \
  -H "Content-Type: application/json" \
  -d '{"documentId": "doc-001", "content": "Routine claim."}'
# Result: {"Pattern":"risk-routing-low","Succeeded":true,"Result":"[Automated result] Processed doc-001"}

Known issues and deployment notes

Storage role assignments required after provision

The Bicep template assigns Storage Blob Data Owner to the managed identity but Durable Functions also needs table and queue access. After azd up completes, run:

principalId=$(az functionapp identity show --name <func-name> --resource-group <rg> --query principalId -o tsv)
storageId=$(az storage account show --name <storage-name> --resource-group <rg> --query id -o tsv)

az role assignment create --assignee $principalId --role "Storage Table Data Contributor" --scope $storageId
az role assignment create --assignee $principalId --role "Storage Queue Data Contributor" --scope $storageId

Then restart the function app:

az functionapp restart --name <func-name> --resource-group <rg>

Durable Task Scheduler — azureManaged storage provider not available

The azureManaged storage provider requires Microsoft.Azure.Functions.ExtensionBundle.Preview but the preview bundle does not include the provider in the current release. This repo uses the standard Azure Storage backend (Microsoft.Azure.Functions.ExtensionBundle [4.*, 5.0.0)) which works correctly and demonstrates all five patterns without any functional difference for local or deployed testing.

WorkerExtensions net6.0 build error

Microsoft.Azure.Functions.Worker.Sdk (all versions to 2.0.7) generates a WorkerExtensions.csproj hardcoded to net6.0. Extension packages that require net8.0 cause build failures. The workaround already applied in this repo: mark the DurableTask.AzureManaged extension with PrivateAssets="all" and use the extension bundle for host-side loading. Do not add it back without also removing it from the extension bundle.

Durable Task Scheduler role ID varies by subscription

The role ID for Durable Task Data Contributor is not a well-known global GUID — it varies by subscription. The Bicep uses a var for the role ID. If deployment fails with RoleDefinitionDoesNotExist, find the correct ID for your subscription:

az role definition list --name "Durable Task Data Contributor" --query "[].name" -o tsv

Update infra/main.bicep with the returned GUID.

Key design decisions

Activity functions contain the work, orchestrators contain the coordination. No business logic lives in orchestrators. Each activity is independently testable.

Azure OpenAI calls are stubbed. Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT in local.settings.json to enable real LLM calls. The stubs are deterministic — doc-001 always routes LOW risk, doc-003 always routes HIGH.

Records not tuples for activity inputs. C# tuples do not serialize correctly through Durable Functions activity calls. All multi-field activity inputs use named records (ProcessWithDecisionInput, NotifySupervisorInput).

Series

This repo accompanies an ongoing series on AI and Azure Functions at sjwiggers.com.

About

Five Durable Functions agentic workflow patterns in C# (.NET 8 isolated worker) — function chaining, fan-out/fan-in, human interaction, monitor, and risk-class routing. Full azd deployment with Azure Storage backend.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages