If you've been deploying AI agents in production on Azure, you've probably faced the same dilemma I've seen across dozens of enterprise engagements in Southeast Asia: containers feel like overkill for most agent workloads, but you still need reliability, event-driven triggers, and proper tool orchestration. Standing up an AKS cluster just to host a few dozen simple tool-calling agents is like renting a warehouse to store a bicycle.
Microsoft's answer is the Azure Functions Agents Runtime — currently in public preview, published on PyPI as azurefunctions-agents-runtime, and open-sourced on GitHub under Azure/azure-functions-agents-runtime. It is a markdown-first programming model for building AI agents on Azure Functions, powered by the Microsoft Agent Framework (MAF). You define agents in .agent.md files, deploy them as event-driven serverless functions, and let the runtime wire up the LLM, tools, and connectors for you.
I've been experimenting with it since it appeared, and I want to walk you through what it actually means for enterprise teams — what's real, what's still experimental, and the patterns that make sense in production.
The Problem: Agents Don't Fit Existing Hosting Models Cleanly
Most enterprise agents fall into one of two buckets. The first is the heavyweight, long-running agent — autonomous research agents, multi-step pipelines, orchestrators coordinating dozens of tools. Those need persistent state, retries, and durable execution, and Container Apps, AKS, or hand-rolled Durable Functions remain reasonable homes for them.
The second bucket — where most enterprise agents actually land — is the event-driven agent: it reacts to a trigger, executes a bounded task, and produces a result. An agent that watches a document library, extracts structured data from new files, and writes results to a database. An agent that consumes messages from a queue, classifies intent, and routes each one downstream. An agent that answers a webhook with a retrieval-backed response.
These agents don't need a persistent container or a cluster. They need an event source, a runtime, a tool interface, and an LLM endpoint. Until now, you either forced them into Functions as plain code (losing the agent loop, session state, and tool wiring you'd then rebuild by hand) or you over-provisioned compute. The agents runtime closes that gap: the function is your agent, the trigger is its perception layer, and the tools are declared in markdown and config files.
The .agent.md Programming Model
The distinctive piece is the agent file. Each agent is a markdown file with YAML frontmatter — trigger configuration, model overrides, tool bindings — followed by the system prompt as the markdown body. Here is a real, minimal agent from the runtime's own documentation:
---
name: Queue Message Processor
description: Processes messages from an Azure Storage queue.
trigger:
type: queue_trigger
args:
queue_name: agent-input
connection: AzureWebJobsStorage
---
You process one Azure Storage Queue message at a time. The runtime provides
the message as JSON, including `body`, `body_encoding`, and queue metadata
such as `id` and `dequeue_count`. When the message body is valid JSON, use
`body_json` when it is present.
Produce a concise structured summary with the message contents, any implied
action, and the relevant queue metadata.
That single file is the agent's identity, trigger binding, and behavioral contract. The runtime discovers it, registers it as an Azure Function, and hands every invocation to the LLM with the configured tools. The function app entry point is two lines:
# function_app.py
from azure_functions_agents import create_function_app
app = create_function_app()
Two-tier configuration keeps things clean: agents.config.yaml holds app-wide defaults (model, timeout, system tools), while each agent's frontmatter overrides them and filters the capabilities it sees. MCP servers live in mcp.json, custom Python tools in tools/ (decorate functions with @tool and they become callable), and progressive-disclosure prompt modules in skills/. Model providers are pluggable — Microsoft Foundry, Azure OpenAI, or OpenAI — selected via the AZURE_FUNCTIONS_AGENTS_PROVIDER setting, with sensible auto-detection.
What I appreciate most is that this makes agents reviewable. In enterprise environments, a human-readable agent definition that security teams, compliance officers, and domain experts can read and approve beats opaque code-only configuration. I've sat through incident reviews in Malaysian financial services projects where the agent's actual behavior was unclear even to the team that shipped it. A markdown contract plus declarative triggers eliminates most of that ambiguity before it ships.
The Trigger Ecosystem
Because each .agent.md file registers as a real Azure Function, the agent can run on the Functions trigger surface. The supported list includes HTTP, timer, queue, blob, Event Grid, Event Hub, Service Bus (queue and topic), Cosmos DB, Azure SQL, MySQL, Kafka, Dapr, and a generic escape hatch — plus connector_trigger, which plugs into the Azure Functions Connector Extension for connector-triggered apps. One rule to note: one trigger per agent file.
The 1,400+ connector story works on both sides of the agent loop. Connector actions (Office 365, Teams, SQL, Salesforce, SAP, and hundreds more) are exposed to agents as connector-backed MCP servers declared in mcp.json, while connector_trigger lets agents fire on connector events. Triggers you'd normally hand-wire become a few lines of YAML in frontmatter.
Your First Agent, End to End
The getting-started flow is refreshingly short. With Python 3.13+ and Azure Functions Core Tools installed:
pip install "azurefunctions-agents-runtime[monitor]"
Create the project layout:
my-agent-app/
├── function_app.py # create_function_app() entry point
├── agents.config.yaml # app-wide defaults (model, timeout)
├── main.agent.md # the agent definition
├── host.json # extension bundle [4.*, 5.0.0)
├── local.settings.json # provider + model env vars
└── requirements.txt
agents.config.yaml sets shared defaults:
model: $FOUNDRY_MODEL
timeout: 900
For local development against Microsoft Foundry, az login and then configure local.settings.json:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AZURE_FUNCTIONS_AGENTS_PROVIDER": "foundry",
"FOUNDRY_PROJECT_ENDPOINT": "https://<project>.<region>.services.ai.azure.com/api/projects/<project>",
"FOUNDRY_MODEL": "gpt-5.4"
}
}
Non-HTTP triggers and the MCP endpoint need storage, so run Azurite locally, then func start. Your agent is immediately reachable: a built-in chat UI at /agents/main/, a chat API at /agents/main/chat (plus an SSE chatstream variant), and an MCP tool endpoint at /runtime/webhooks/mcp — all with zero extra code. That built-in surface alone changes how you demo agents to stakeholders.
For deployment, the repo's samples use the Azure Developer CLI with Bicep: azd up provisions the resource group, a Flex Consumption function app (FC1), a Microsoft Foundry project with the model deployment, storage, and managed identity in one shot. The sample Bicep pins the region list because Flex Consumption and the default Foundry model deployment must both be available — a realistic constraint worth checking for your own region, especially here in Southeast Asia.
Durable Agent Workflows — The Part I'd Been Waiting For
This is where it gets genuinely interesting for enterprise workloads. The runtime ships an experimental feature called Dynamic Workflows: flip workflows.enabled: true in an agent's frontmatter and the agent gains five built-in tools — start_workflow, get_workflow_status, list_workflows, cancel_workflow, terminate_workflow — that author and run durable DAGs on Azure Durable Functions without you writing orchestration code.
The LLM authors a plan of tasks (tool calls, wait durable timers, and sub_agent invocations) with depends_on edges; the runtime validates the DAG, schedules it as a Durable orchestration, and returns a workflow_id immediately. Intermediate results stay inside the orchestration — the agent only ever ingests the final result envelope. That's the token, latency, and context-window discipline of programmatic tool calling, plus durability that survives worker restarts and long sleeps.
Workflow-capable tools are ordinary Python with an explicit opt-in marker:
# tools/incident_tools.py
from typing import Any
from azure_functions_agents import workflow_tool
@workflow_tool(description="Fetch recent log lines for a service.")
def fetch_logs(args: dict[str, Any]) -> dict[str, Any]:
return {"service": args["service"], "lines": ["..."]}
And a plan the agent might author looks like this:
{
"tasks": [
{ "id": "fetch_logs", "type": "tool", "tool": "fetch_logs",
"args": {"service": "payments"} },
{ "id": "fetch_metrics", "type": "tool", "tool": "fetch_metrics",
"args": {"service": "payments"} },
{ "id": "cool_down", "type": "wait", "duration": "PT30S",
"depends_on": ["fetch_logs", "fetch_metrics"] },
{ "id": "summarize", "type": "tool", "tool": "summarize",
"args": {"sources": ["${fetch_logs.result}", "${fetch_metrics.result}"]},
"depends_on": ["cool_down"] }
]
}
For multi-agent setups, any runnable agent can declare subagents: in its frontmatter — each reference becomes a delegate_<slug> tool on the coordinator, with no handoff builders and no new dependencies. Delegation is single-level by design, and specialists only receive the self-contained task string the coordinator passes them.
Decision Framework: Functions vs Containers vs Hand-Rolled Durable
After deploying agents on Functions, Container Apps, and AKS, here's how I'd frame it:
- Choose the agents runtime on Functions when: the agent is triggered by discrete events (queue, blob, timer, webhook), execution is bounded, you want scale-to-zero economics, and the workflow shape is "reason, act, respond" — optionally with a durable workflow for the heavy lifting.
- Choose Container Apps or AKS when: you need persistent in-memory state between invocations, GPU access, long-lived WebSocket connections, custom networking boundaries, or a microservices mesh.
- Choose hand-written Durable Functions when: you need deterministic, code-authored orchestrations with fine-grained control over retries, entities, and external events — and you're prepared to own that code.
One important clarification I'd offer: Dynamic Workflows are not a replacement for hand-written orchestrations. Plans are authored by the LLM, not hand-coded in YAML, and v1 deliberately omits per-task timeout/retry fields (tracked for v2). If your compliance posture demands a deterministic, reviewable orchestration, keep it in code. If you want a durable, observable execution layer that an agent can drive, workflows are the point.
For most enterprise agent use cases I encounter in Malaysia and ASEAN — document processing, email triage, scheduled reporting, webhook integrations — the agents runtime is now my default starting point.
Pitfalls I've Hit and Ones I'd Flag
It's public preview, moving fast. The package went from 0.0.0 dev releases to 0.1.0 beta in a few months, requires Python 3.13+, and the README explicitly warns features may change before GA. Pin versions, read the changelog before upgrading, and don't bake preview APIs into compliance commitments.
One trigger per agent file. If you want the same logic on two event sources, you have two agent files (or one agent plus delegation). Structure your agents/ folder for that from day one.
Workflows are fire-and-forget. start_workflow returns immediately with an ID; the agent does not block on completion. Design for the completion envelope coming back later — via the chat UI's auto-notification, or by polling get_workflow_status from an external operator. Trigger-started workflows on non-HTTP triggers get a fresh session ID with no app-wide index, so publish the terminal result somewhere operators can find it.
Secure the endpoints deliberately. The chat API defaults to function-key auth (http_auth: function), which is fine for a demo but not for production. Set builtin_endpoints.http_auth: entra for Entra ID enforcement, or put the app behind API Management or private endpoints. "Obscurity through URL complexity" is not a control.
Mind the hosting constraints. Flex Consumption works well with queue, timer, and Service Bus triggers, but blob triggers have known Flex limitations (the sample docs steer you to Event Grid-based wiring for blobs). Check trigger support on your chosen SKU before you commit to an architecture.
Watch token consumption, not just invocations. Standard Functions metrics don't capture LLM cost. Install the runtime with the [monitor] extra so OpenTelemetry spans and metrics flow to Application Insights automatically, and build your cost dashboards around token usage.
Conclusion
The Azure Functions Agents Runtime is the first time Azure has treated an AI agent as a genuine first-class serverless workload: markdown definitions, event triggers, 1,400+ connector-backed tools, built-in chat and MCP endpoints, session persistence, and durable workflows — all on the hosting platform most enterprises already run. It won't replace containers for heavyweight agents or hand-written Durable Functions for deterministic orchestrations, but for the broad middle of enterprise agent patterns, it removes almost everything that used to be glue code.
For Southeast Asian teams scaling agent programs in 2026, this meaningfully lowers the barrier: start with a markdown file, a trigger, and a Foundry model. Ship the simple version first. Add complexity only when requirements genuinely demand it — because the simplest architecture is usually the one that actually ships.
Key Takeaways
- Agents become markdown. .agent.md files combine trigger configuration and system prompt in one reviewable artifact, deployed as ordinary Azure Functions — your existing CI/CD, monitoring, and security tooling apply unchanged.
- The trigger surface is the perception layer. HTTP, timer, queue, blob, Event Grid, Service Bus, Cosmos DB, SQL, Kafka, Dapr, and connector triggers are declared in frontmatter; 1,400+ connector actions are reachable through connector-backed MCP servers.
- Dynamic Workflows bring durable execution to agents — one frontmatter flag gives the agent LLM-authored DAGs on Durable Functions that fan out, wait, survive restarts, and keep intermediate results out of the context window. It's experimental; use it with eyes open.
- Built-in endpoints are the hidden productivity win. Chat UI, chat API, SSE streaming, MCP tool registration, and Blob-backed session persistence come with a flag — no extra code.
- Start serverless, treat preview pragmatically. Pin versions, verify regional availability of Flex Consumption and your model deployment, use Entra auth for production endpoints, and reserve containers and hand-written orchestrations for cases that truly need them.