Public preview. The features described here are available for preview use and may change before general availability.
A markdown-first programming model for building AI agents on Azure Functions, powered by the Microsoft Agent Framework (MAF).
📖 Full documentation: azure.github.io/azure-functions-agents-runtime
- Build agents with markdown — write instructions, configure triggers, and bind tools in
.agent.mdfiles - Run on any Azure Functions trigger — trigger agents on timer, queue, blob, HTTP, Event Hub, Service Bus, Cosmos DB, and more
- Connect to 1,400+ services — use connector-backed MCP servers to let agents act through Office 365, Teams, SQL, Salesforce, SAP, and hundreds of other connectors
- Extend with MCP servers — plug in remote HTTP MCP servers, including MCP servers backed by connectors
- Build custom tools in plain Python — drop a
.pyfile intools/, decorate functions with@tool, and pull in any package you need - Run agents on durable workflows (experimental, see
docs/workflows.md) — one frontmatter flag turns on a DAG-of-tools execution model that fans out, waits, and survives restarts, without burning tokens on intermediate results - Automatic HTTP and MCP endpoints — optionally expose your agent as an HTTP chat API and MCP server with no extra code
- Hosted skills in ordinary Functions — inject a markdown agent into your own trigger handler with
@app.hosted_skill - Evaluate authored behavior (preview) — run native Vally evaluations against the same chat surface under Core Tools or in staging
- Serverless with built-in session management — scales to zero, persists multi-turn conversations in Azure Blob Storage
- Pluggable model providers — bring OpenAI, Azure OpenAI, or Microsoft Foundry credentials and the runtime auto-detects the right client
- Execution controls — set model output limits while each harness keeps its native conversation-compaction behavior
The package is published on PyPI as azurefunctions-agents-runtime.
pip install azurefunctions-agents-runtimeAdd it to your function app's requirements.txt:
azurefunctions-agents-runtime
The runtime uses Microsoft Agent Framework, which supports Microsoft Foundry, Azure OpenAI, and OpenAI as inference back-ends. The public preview quickstart and samples use Microsoft Foundry as the primary path, pinned with AZURE_FUNCTIONS_AGENTS_PROVIDER=foundry.
| Provider | AZURE_FUNCTIONS_AGENTS_PROVIDER |
Required env vars | Notes |
|---|---|---|---|
| Microsoft Foundry | foundry |
FOUNDRY_PROJECT_ENDPOINT; optional FOUNDRY_MODEL |
Recommended quickstart/sample path. Uses DefaultAzureCredential; run az login locally and set AZURE_CLIENT_ID in multi-identity Function Apps. If no model is configured, the runtime uses gpt-4o-mini. |
| Azure OpenAI | azure_openai |
AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT, optional AZURE_OPENAI_API_VERSION |
Alternative Azure-hosted provider. AZURE_OPENAI_DEPLOYMENT takes precedence over AZURE_FUNCTIONS_AGENTS_MODEL. If AZURE_OPENAI_API_KEY is omitted the SDK uses DefaultAzureCredential (AAD). |
| OpenAI | openai |
OPENAI_API_KEY, optional AZURE_FUNCTIONS_AGENTS_MODEL (default gpt-4o-mini) |
Alternative non-Azure provider. AZURE_FUNCTIONS_AGENTS_MODEL applies directly for OpenAI. |
If AZURE_FUNCTIONS_AGENTS_PROVIDER is unset, auto-detection picks the first provider whose env vars are set, in this order: AZURE_OPENAI_ENDPOINT → FOUNDRY_PROJECT_ENDPOINT → OPENAI_API_KEY. Set AZURE_FUNCTIONS_AGENTS_PROVIDER to make the provider choice intentional.
Model resolution precedence is: explicit requested model > provider-specific env (FOUNDRY_MODEL for Foundry, AZURE_OPENAI_DEPLOYMENT for Azure OpenAI) > AZURE_FUNCTIONS_AGENTS_MODEL > runtime fallback (gpt-4o-mini).
Create main.agent.md:
---
name: My Agent
description: A helpful assistant
builtin_endpoints: true
---
You are a helpful assistant. Answer questions concisely.Create function_app.py:
from azure_functions_agents import create_function_app
app = create_function_app()The app root is auto-detected from
AzureWebJobsScriptRoot(set byfunc startand the Azure Functions host). You can override it withcreate_function_app(app_root=Path(__file__).parent)or theAZURE_FUNCTIONS_AGENTS_APP_ROOTenv var.
# Default runtime configuration
model: $FOUNDRY_MODEL
timeout: 900{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": ""
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}azurefunctions-agents-runtime
Connector-backed tools are exposed through MCP servers in mcp.json, and connector-triggered apps use the Azure Functions Connector Extension through the Functions extension bundle. No package extra is required.
For local development with Microsoft Foundry, sign in with az login, then create local.settings.json:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AZURE_FUNCTIONS_AGENTS_PROVIDER": "foundry",
"FOUNDRY_PROJECT_ENDPOINT": "https://<project-name>.<region>.services.ai.azure.com/api/projects/<project-name>",
"FOUNDRY_MODEL": "gpt-5.4"
}
}The MCP server endpoint and non-HTTP triggers (timer, queue, blob, etc.) require a storage account. Locally, use Azurite via Docker:
docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite \
azurite --skipApiVersionCheck --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0func startYour agent is now running at http://localhost:7071/agents/main/ with a built-in chat UI, HTTP API (/agents/main/chat, /agents/main/chatstream), and MCP tool exposed through the Functions MCP endpoint (/runtime/webhooks/mcp).
Architecture overview: see docs/architecture.md for the module map and data flow pipeline.
Any .agent.md file can opt into built-in endpoints with builtin_endpoints. The route slug comes from the .agent.md filename after sanitization, not from the display name: field.
- Debug chat UI — built-in single-page web interface at
/agents/{slug}/ - HTTP APIs —
POST /agents/{slug}/chat(JSON) andPOST /agents/{slug}/chatstream(SSE) - MCP tool — optional tool exposed through
/runtime/webhooks/mcpfor VS Code, Claude Desktop, etc. - Session persistence — multi-turn conversations stored in Azure Blob Storage via the runtime's
BlobHistoryProvider, reusing the function app'sAzureWebJobsStorageaccount
If any built-in endpoint is enabled, trigger is optional. This allows endpoint-only agents as well as triggered agents that also expose a chat UI or API. builtin_endpoints.debug_chat_ui: true automatically enables the backing chat APIs. builtin_endpoints: true is shorthand for enabling all built-in endpoints, including the MCP tool. See docs/front-matter-spec.md#builtin_endpoints.
Use @app.hosted_skill when application code owns the Azure Functions trigger
and needs an agent internally. The selected .agent.md may omit trigger and
builtin_endpoints, or define either to expose additional, independently
registered surfaces. Its identity slug comes from the filename.
from azurefunctions.extensions.http.fastapi import Request, Response
from azure_functions_agents import HostedSkill, HostedSkillFunctionApp
app = HostedSkillFunctionApp()
@app.route(route="summarize", methods=["POST"])
@app.hosted_skill(arg_name="skill", agent_name="summarizer")
async def summarize(req: Request, skill: HostedSkill) -> Response:
result = await skill.run((await req.body()).decode("utf-8"))
return Response(result.content, media_type="text/plain")skill.run() returns an AgentResult; skill.stream() yields structured
HostedSkillEvent values through the app-bound MAF or Copilot harness. The
facade is intentionally not callable and must be obtained through decorator
injection rather than constructed directly. See the
hybrid-hosted-skill sample and
HostedSkill injection.
The repository's Vally 0.16.0 custom executor tests an agent through its existing synchronous chat
API. A native eval.yaml can target a local Core Tools host or deployed staging Function App and
use Vally's deterministic or model-based graders, repeated trials, reports, and CI verdicts. The
Python runtime remains evaluation-framework-neutral. See the evaluation guide
and evaluation sample.
By default, agents execute through Microsoft Agent Framework's harness-agent mechanism. Optional global defaults and recursive per-agent overrides configure model output limits. Harness-specific conversation compaction stays on each SDK's native default behavior.
# agents.config.yaml
agent_configuration:
max_output_tokens: 4096Agents recursively inherit global agent_configuration values. Per-agent values override individual
leaves, while explicit null clears inherited values. Legacy
agent_framework.compaction.max_context_window_tokens configuration emits a warning and is ignored;
remove it to use the selected harness's native compaction default. On the Copilot
preview path, max_output_tokens is passed through as the SDK provider's
request-local native output cap. See
docs/front-matter-spec.md#agent_configuration.
By default the chat API requires a function/host key (http_auth: function). Configure builtin_endpoints.http_auth to change the policy — admin (system key), anonymous, or entra (Entra ID / Azure AD). In entra mode the runtime relies on platform App Service Authentication (Easy Auth): the platform validates the Entra token and the runtime enforces the injected x-ms-client-principal (with optional tenant/audience/client-id allowlists). Because the entra route is anonymous, the runtime trusts that header only with non-spoofable evidence Easy Auth is enforced (WEBSITE_AUTH_ENABLED or the AZURE_FUNCTIONS_AGENTS_ENTRA_EASY_AUTH app setting) and fails closed (401) otherwise. http_auth applies only to HTTP endpoints and does not affect the MCP endpoint (/runtime/webhooks/mcp), which is owned by the Functions host and always requires the MCP extension system key. See docs/front-matter-spec.md#http_auth--endpoint-authentication.
Define event-triggered agents with .agent.md files. Each file corresponds to a single Azure Function. Supported trigger types:
- Event triggers — timer, queue, blob, Event Hub, Service Bus, Cosmos DB, Teams, Office 365, etc.
- HTTP triggers — expose agents as REST API endpoints; add
response_exampleorresponse_schemafor validated JSON responses
Agent files can be placed at the app root or in an agents/ folder:
my-app/
├── agent.md # Bare single-agent alias → slug "main" (is_main=true)
│ # Alternatives: main.agent.md → slug "main", CLAUDE.md → slug "main"
│ # (agent.md, CLAUDE.md, and main.agent.md are all aliases;
│ # only one may be present in the same app)
├── agents/ # Optional folder for organization
│ ├── chat.agent.md
│ └── report.claude.md # *.claude.md is equivalent to *.agent.md — prefix becomes slug
├── tools/
└── skills/
- Markdown-first — agent instructions, trigger config, and tool bindings in
.agent.mdfiles - Skills — shared directory discovery under
skills/searches through two child levels and stops at eachSKILL.mdroot. Filter by directory name; the selected SDK validates metadata and loads content on demand. Nested documents remain resources of the containing skill, not independent skill roots. - Custom tools — drop a
.pyfile intools/, decorate functions with@tool, and they become callable - Connector-backed MCP tools — call Office 365, Teams, SQL, Salesforce, SAP, and other connectors through HTTP MCP servers
- MCP servers — connect to external remote HTTP MCP servers for additional tools
- Sandbox — Python code execution via Azure Container Apps dynamic sessions; if no explicit sandbox session id is supplied, each invocation gets a fresh GUID-backed session
- Web request — built-in, default-on
web_requesttool for outbound HTTP(S) calls to public hosts, guarded by an always-on SSRF security floor; no Azure resource required. Disable app-wide withsystem_tools.web_request: false, or per-agent withsystem_tools.web_request: falsein that agent's front matter
Any independently runnable agent can declare subagents: to call other agents as chat-time tools —
no HandoffBuilder, no HITL, no new dependency (FRD 0007):
---
name: Support Coordinator
subagents:
- agent: billing # references agents/billing.agent.md (or billing.agent.md) by slug
when: "Billing, invoices, refunds, or subscription questions"
- agent: tech_support # references agents/tech-support.agent.md — slug is the sanitized file stem
when: "Technical troubleshooting or product bugs"
---
Route the user's request to the right specialist, or answer directly for anything general.Each entry becomes a hand-written delegate_<slug> function tool on the
coordinator, built eagerly for each request but only run (building the specialist's own agent)
if the coordinator's model selects it. A few
rules to know before you reach for this:
- Trust boundary:
subagentsis an authoring-time capability grant. One app is one trust domain — a specialist's ownbuiltin_endpoints/auth level is never consulted when it is invoked as a sub-agent; if a coordinator lists it, its full capability set is reachable through that coordinator (Decision #10). - Write a self-contained
task: the specialist does not see the coordinator's conversation history, session, or any other request context (propagate_session=False) — it only receives thetaskstring the coordinator's model passes to the tool. Instruct the coordinator (viawhen:/ its own instructions) to pass every fact the specialist needs, not just a short pointer. - Single-level only: delegation does not chain. A specialist invoked via
delegate_<slug>runs with its own instructions/model/tools, but its ownsubagents:(if it declares any) are not wired up for that call — only a top-level coordinator getsdelegate_*tools (Decision #6). - Slugs must be globally unique: a
subagentsreference resolves by file-stem slug, the same identity used forbuiltin_endpointsroutes — see the breaking-change note under Multiple functions from markdown below. - An agent referenced only as a specialist may skip
triggerandbuiltin_endpointsentirely — seedocs/triggers.md#endpoint-less-internal-specialists.
See docs/front-matter-spec.md#subagents for the full field
reference, docs/architecture.md for how
this fits into the composition pipeline, and
samples/multi-agent-delegation/ for a runnable coordinator +
two-specialist example.
Agent files use YAML frontmatter + markdown body:
---
name: Agent Name
description: What this agent does
# Optional: system tools (code execution)
system_tools:
dynamic_sessions_code_interpreter:
endpoint: $ACA_SESSION_POOL_ENDPOINT
# Optional when builtin_endpoints is enabled; required otherwise:
trigger:
type: timer_trigger # or queue_trigger, connector_trigger, etc.
args:
schedule: "0 0 9 * * *" # trigger-specific params passed as kwargs
logger: true # optional, default true
substitute_variables: true # optional, default true — env-var replacement in frontmatter + body
# For HTTP-triggered agents: expected response format
response_example: | # optional — agent returns structured JSON matching this example
{
"summary": "A brief summary",
"keywords": ["keyword1", "keyword2"]
}
---
Agent instructions in markdown...Note: Earlier preview releases supported a
runtime: copilot|maffrontmatter field. As of 1.0.0 only Microsoft Agent Framework is used and the field is ignored (with a one-time warning per agent file). Remove it from your.agent.mdfiles.
*.agent.mdwithtrigger— creates an event-triggered Azure Function. Exactly one trigger per file.*.agent.mdwithbuiltin_endpoints— also serves/agents/{slug}/,/agents/{slug}/chat, and/agents/{slug}/chatstreamwhen chat endpoints are enabled, and can expose an MCP tool whenbuiltin_endpoints: trueorbuiltin_endpoints.mcp: true. The sanitized filename stem becomes the base Azure Function name, endpoint slug, and the agent's global identity (its slug — also used fordelegate_<slug>tool names, see Multi-agent delegation above). The frontmattername:field is display-only. Seedocs/front-matter-spec.md#function-name-resolutionanddocs/front-matter-spec.md#builtin_endpoints.
Flexible filename conventions: Beyond
*.agent.md, the runtime also supports:
agent.md(any casing:Agent.md,AGENT.MD) andCLAUDE.md(any casing:Claude.md,claude.md) — bare aliases formain.agent.mdthat produce slugmainandis_main=True.agent.md,CLAUDE.md, andmain.agent.mdall produce the same slug so at most one may be present in the same app.*.claude.md— e.g.summarizer.claude.mdis equivalent tosummarizer.agent.md; the prefix becomes the slug.- Case-insensitive suffix matching —
.agent.mdand.claude.mdsuffixes are matched case-insensitively. Files that derive the same slug collide even when their suffixes differ.
⚠️ Breaking change: Earlier releases silently auto-suffixed (_2,_3, ...) when two agent files sanitized to the same name. Slugs are now required to be globally unique across the whole app. A collision (for exampledaily-report.agent.mdanddaily_report.agent.md, which both sanitize todaily_report) now fails fast at startup with an actionable error telling you which files collided, instead of silently registering a_2-suffixed duplicate. Rename one of the colliding files to fix it. This also applies to.agent.mdfiles that only participate as delegation specialists and never register their own trigger or endpoints — every agent's slug is checked against the same app-wide index.
When a triggered function runs, the agent's markdown body is used as the system instructions. The prompt sent to the agent includes the trigger type and JSON-safe binding data:
Triggered by: service_bus_queue_trigger
Trigger data:
```json
{"body": "...", "message_id": "...", ...}
```
For non-HTTP Azure Functions bindings, the runtime serializes public binding fields instead of the
binding object's Python representation: Queue, Service Bus, Event Hubs, and Kafka bodies include
an encoding marker; Event Grid includes its parsed data; timers include past_due,
schedule_status, and schedule; and Cosmos DB/SQL batches become JSON arrays. Blob triggers
include blob name, URI, properties, and metadata only; they do not read blob content, so provide a
tool when an agent must fetch it. HTTP request-body handling remains separate.
For concrete payload examples and a complete reference of supported triggers and parameters, see docs/triggers.md.
| Format | Resolves to | Example |
|---|---|---|
http_trigger |
Runtime HTTP adapter over app.route(...) |
http_trigger |
| No dots | app.<type>(...) |
timer_trigger, queue_trigger |
connector_trigger |
app.connector_trigger(...) |
connector_trigger |
HTTP-triggered agents expose REST API endpoints that accept JSON input and return structured JSON output. Use response_example in the frontmatter to define the expected response format:
---
name: Summarize
trigger:
type: http_trigger
args:
route: summarize
methods: ["POST"]
auth_level: FUNCTION # ANONYMOUS | FUNCTION | ADMIN (default: FUNCTION)
response_example: |
{
"summary": "A brief summary of the content",
"keywords": ["keyword1", "keyword2"],
"sentiment": "positive"
}
---
Analyze the provided content and return a structured summary.The agent receives the HTTP request body as input and is instructed to return JSON matching the example. If response_example is omitted, the raw agent text is returned as text/plain.
response_schema (JSON Schema) is also supported as an alternative to response_example for advanced use cases.
docs/front-matter-spec.md#environment-variable-substitution is the authoritative reference. In short, the runtime resolves $VAR and %VAR% placeholders inline in every string value in agents.config.yaml, mcp.json, agent frontmatter values, and the markdown body (outside fenced code blocks). Missing variables are left as literal placeholders.
Variable references are resolved inline at load time anywhere string values are supported. Both $VAR_NAME and %VAR_NAME% syntaxes are supported, where the identifier must match [A-Za-z_][A-Za-z0-9_]*:
---
name: Notifier
description: Sends updates to $TEAM_NAME
system_tools:
dynamic_sessions_code_interpreter:
endpoint: "https://$HOST/api"
---
Send a daily summary email to $TO_EMAIL.
Post a message to the %TEAM_NAME% team's General channel.If HOST=contoso.internal, TO_EMAIL=alice@example.com, and TEAM_NAME=Engineering are set in the environment, those values resolve inline:
endpoint: "https://contoso.internal/api"Send a daily summary email to alice@example.com.
Post a message to the Engineering team's General channel.
If a referenced variable is not set, the original $VAR_NAME or %VAR_NAME% text is left unchanged.
The runtime does not substitute dictionary keys, ${FOO} brace syntax, identifiers starting with a digit such as $9PORT, or text inside fenced code blocks (```), so documentation examples in your instructions are preserved.
For the $IDENT syntax, identifiers that include characters outside [A-Za-z0-9_] (for example $VAR-NAME) are matched greedily up to the first invalid character — so $VAR-NAME resolves to <value-of-VAR>-NAME when VAR is set, and stays $VAR-NAME when VAR is unset. The %IDENT% syntax requires a closing % immediately after the identifier, so tokens like %VAR-NAME% remain fully literal. Quote or escape the surrounding text if you need a $IDENT token to remain literal.
To disable substitution for an agent's frontmatter values and markdown body, set substitute_variables: false in the frontmatter:
---
name: My Agent
substitute_variables: false
---
Instructions with literal $VAR references that should not be replaced.Note:
substitute_variablesitself is read before env-var substitution. It must be a literal boolean (trueorfalse). Settingsubstitute_variables: $MY_FLAGwill not be resolved and defaults totrue.
Drop a .py file in tools/ and decorate functions with @tool. The runtime auto-discovers them at import time and adds them to every agent.
# tools/my_tools.py
from azure_functions_agents import tool
@tool
def reverse_string(text: str) -> str:
"""Reverse the input string."""
return text[::-1]The runtime's ordinary @tool produces an SDK-free descriptor. Functions can be
sync or async; signature types and Pydantic schemas supply input validation, and
the selected harness adapts the descriptor to its SDK. Tools return ordinary
Python values. Discovery selects one tool per module: a runtime descriptor
first, then named callable objects with public name/description, then the
first local public function. Imported functions, classes, unrelated globals,
and workflow-only declarations are not automatically exposed.
The convenience @tool accepts only name, description, schema, and
approval_mode; unexpected keyword arguments raise the normal Python
TypeError. approval_mode still accepts the existing
"never_require"/"always_require" strings, and you can also use
ApprovalMode.NEVER_REQUIRE or ApprovalMode.ALWAYS_REQUIRE directly:
from azure_functions_agents import ApprovalMode, tool
@tool(approval_mode=ApprovalMode.NEVER_REQUIRE)
def reverse_string(text: str) -> str:
return text[::-1]Runtime capability filters, authentication, and schemas still apply.
Dynamic Workflow tools live in the same tools/ directory but must opt in
explicitly with @workflow_tool so they can run safely as Durable
Function activities:
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": ["..."]}Use both @tool and @workflow_tool when the same callable should be
available both directly in chat and inside workflows. A workflow tool can own
the retry policy for an operation that is safe to repeat and the maximum wait
for each attempt:
from azure_functions_agents import WorkflowRetryBackoff, WorkflowRetryPolicy
@workflow_tool(
retry=WorkflowRetryPolicy(
max_attempts=3,
backoff=WorkflowRetryBackoff(initial="PT1S", multiplier=2.0, max="PT4S"),
),
timeout="PT30S",
)
def reserve_inventory(args: dict[str, Any]) -> dict[str, Any]:
...Decorator precedence applies separately to plan-authored execution.retry and
execution.timeout. The tool raises WorkflowRetryableError when a failure is
safe to retry. An expired attempt uses workflow_task_timeout. A plan can set
execution.continue_on_error: true to give a bounded permitted failure result
to dependent tasks after the attempt budget is complete. See
docs/workflows.md for the Activity handler
contract, workflows.exclude, retry, timeout, continuation, and host limits.
Any agent can enable workflows; triggers and built-in endpoints independently
determine how that agent is invoked. See the
per-agent-workflows sample for two independent
non-main workflow-enabled agents sharing one Durable engine.
Built-in endpoints are explicit per agent. The filename stem determines {slug}; for example, main.agent.md uses main and daily_azure_report.agent.md uses daily_azure_report.
A built-in single-page chat interface served at /agents/{slug}/ when builtin_endpoints.debug_chat_ui: true (or builtin_endpoints: true). No frontend code needed — just open http://localhost:7071/agents/main/ locally for main.agent.md, or https://<your-app>.azurewebsites.net/agents/{slug}/ when deployed. See docs/front-matter-spec.md#function-name-resolution.
On first load, you'll be prompted for the base URL and a function key (for deployed apps). These are stored in browser local storage and can be changed via the gear icon.
The chat UI manages the session id for you. The active id is shown beneath the status line with a Copy button, and you can resume an existing conversation by pasting its id into the Session ID (optional) field in the settings dialog. Pasted ids are validated client-side against the same rule the server enforces (^[A-Za-z0-9._-]{1,128}$) and persist in browser local storage per base URL and agent (each /agents/{slug}/), so a resumed conversation survives page reloads and new tabs. Use New session to clear the id and start fresh.
The settings dialog also keeps a Recent sessions list (most-recent first, up to 8 per base URL and agent). Each turn adds or updates an entry, auto-titling it with your first message (renameable via Rename); pick one to fill the Session ID field and Save to resume it, or use Remove / Clear recent to prune the list. This list is a per-browser convenience stored in local storage — it is not synced across devices or browsers, and it does not include sessions created through the raw HTTP API from other clients.
When you resume a session — whether by pasting an id or picking one from Recent sessions — the chat window reloads that conversation's earlier messages from the server (via a GET /agents/{slug}/history endpoint) so its history is visible right away, not just carried invisibly into your next turn. The replay is capped at the 200 most recent user and assistant messages; the UI shows a notice when older messages were omitted. Intermediate tool activity is not replayed. This requires the app's blob-backed session storage to be configured; without it — or on an older runtime that predates the history endpoint — the window simply starts empty and the resumed session still continues on your next message.
POST endpoints for programmatic access:
- Any agent with
builtin_endpoints.chat_api: true:POST /agents/{slug}/chatandPOST /agents/{slug}/chatstream
The JSON endpoint returns session_id, response, and tool_calls. The streaming endpoint uses Server-Sent Events (SSE) with session, delta, intermediate, tool_start, tool_end, done, and error events.
Pass x-ms-session-id header to continue a conversation across requests. If omitted, a new session is created automatically.
When builtin_endpoints: true or builtin_endpoints.mcp: true, the agent is exposed as an MCP tool named after its slug through the shared MCP-compatible endpoint at /runtime/webhooks/mcp. Requires the MCP extension system key in the x-functions-key header when deployed.
If no agent enables built-in endpoints, no chat UI, chat API, chatstream, or agent MCP tool is registered. The app still runs triggered functions. See docs/front-matter-spec.md#builtin_endpoints.
You can give your agent access to external MCP servers by creating an mcp.json file in the app root. Only remote HTTP MCP servers are supported. The type field is optional — when omitted, an entry with a url is treated as HTTP. When type is specified it must be "http" or "streamable-http"; any other transport (e.g. stdio, sse) is rejected with a warning.
String values in mcp.json support inline environment-variable substitution with both $VAR and %VAR%. Eligible fields include url, headers values, type, tools entries, and Azure identity auth values such as auth.scope and auth.client_id. Dictionary keys such as server names, environment-variable names, and header names are not substituted.
{
"servers": {
"microsoft-learn": {
"type": "http",
"url": "https://$MCP_HOST/api",
"headers": {
"Authorization": "Bearer $LEARN_MCP_TOKEN"
}
},
"custom-api": {
"type": "streamable-http",
"url": "https://example.com/mcp",
"headers": {
"Authorization": "Bearer $MCP_TOKEN"
}
},
"office365-outlook": {
"type": "http",
"url": "$O365_MCP_SERVER_URL",
"tools": ["office365_SendEmailV2"],
"auth": {
"scope": "https://apihub.azure.com/.default",
"client_id": "$O365_MCP_CLIENT_ID"
}
}
}
}Tools from configured MCP servers are automatically available to the agent at runtime. Each server entry supports:
type— optional. When set, must be"http"or"streamable-http". When omitted, an entry with aurlis treated as HTTP.url— the MCP server endpoint URL (required)headers— optional HTTP headers (e.g. for authentication)tools— optional exact-name allowlist. Omitted or any list containing"*"allows all tools;[]allows none; other lists allow only the named tools. Patterns such as"get_*"are not supported.auth— optional Azure Identity authentication configuration. Setauth.scopeto the token scope required by the MCP server. The runtime usesDefaultAzureCredentialto acquire the token.
MCP tools are the supported capability; MCP prompts are not surfaced. The MAF
adapter skips prompt loading for connector-backed servers that reject
prompts/list. Copilot uses normal native SDK connection and tool loading.
Per-agent mcp: false / mcp.exclude can further
narrow the selected servers.
By default, MCP auth follows the app-wide identity selection: AZURE_CLIENT_ID when set, otherwise the system-assigned identity/default Azure credential chain. To choose a user-assigned managed identity for a single MCP server without changing the app-wide identity, set auth.client_id in that server's mcp.json entry. If the configured client ID is empty or an unresolved placeholder, the runtime falls back to the app-wide identity selection.
In the Copilot preview, fresh MCP headers are supplied at native session create/resume between completed turns, not refreshed mid-turn. This is not full MAF auth parity. See the MCP and scoped skills boundary for noninteractive approvals, auth precedence, and unqualified scenarios.
Note: Entries without a
url, with unresolved placeholders inurl, or with atypeother than"http"/"streamable-http", are ignored with a warning. Use the remote HTTP transport instead.
The selected harness uses the existing Functions storage configuration.
AzureWebJobsStorage (connection string) takes precedence over
AzureWebJobsStorage__blobServiceUri (Entra authentication). Either setting
selects Azure Blob Storage, including during local development with Azurite.
Local files are selected only when neither is configured; configured Blob
auth, network or configuration failures surface without a local fallback.
With the default MAF harness, multi-turn conversations are persisted as JSON Lines, one record per message:
- Deployed apps (recommended). When
AzureWebJobsStorageis configured — as either a connection string or the identity-basedAzureWebJobsStorage__blobServiceUrisetting thatazdprovisions — history is written to Azure Blob Storage via the runtime'sBlobHistoryProvider. One Append Blob per agent/session pair is stored underagent-sessions/{agent_slug}/{session_id}.jsonlinside theazure-functions-agentscontainer (override withAZURE_FUNCTIONS_AGENTS_SESSION_CONTAINER). No file share, no storage account key, no mount path; the same identity that the function app already uses forAzureWebJobsStoragereads and writes sessions. In multi-identity Function Apps, storage honorsAzureWebJobsStorage__clientIdbeforeAZURE_CLIENT_IDto select the intended managed identity. - Local dev files. When neither
AzureWebJobsStoragenorAzureWebJobsStorage__blobServiceUriis set, history falls back to MAF'sFileHistoryProviderwriting to{AZURE_FUNCTIONS_AGENTS_SESSION_DIR}/agent-sessions/{agent_slug}/{session_id}.jsonl, defaulting to~/.azure-functions-agents/agent-sessions/.
Copilot session files use a separate namespace:
copilot-native/{agent_id}/{session_id}/{sdk_relative_path}. Here agent_id
is the shared trimmed, lower-case WEBSITE_SITE_NAME plus canonical agent slug,
or local/main when the site name is blank or unset. Files are opaque
to the host; their contents, continuation, recovery and compaction belong to
the SDK. This separate namespace applies only to the current internal Copilot
preview; the default MAF path and its agent-sessions/ history layout are
unchanged. Only the selected harness's persistence adapter is initialized or
closed, with no opposite-harness history probes.
Session ids must match ^[A-Za-z0-9._-]{1,128}$ — anything else is rejected at the API boundary.
The same caller-visible session id may be reused across agents for correlation, but each agent's
transcript remains independent.
Single-process scope: Turns for the same agent/session pair are serialized within one Python process, with bounded waiting. Independent sessions remain concurrent. Across workers or instances there is no distributed lock. Sequential resume depends on a shared accessible backend namespace, and overlapping same-session requests can observe stale state and commit in nondeterministic order.
See the samples/ directory for complete, deployable example apps:
basic-chat— minimal chat agent with sandboxdaily-azure-report— timer-triggered agent that emails a daily Azure status reportdaily-tech-news-email— timer-triggered agent that scrapes news and emails a digestoutlook-reply-agent— connector-triggered agent that drafts replies to incoming Office 365 Outlook emailmulti-agent-delegation— HTTP coordinator that delegates to two specialists viasubagents:, one of them endpoint-lessworkflow-incident-triage— interactive Dynamic Workflow with live progressworkflow-retry-policy— order recovery with Durable retry, per-attempt timeout, and optional-task continuationworkflow-queue-p0-report— queue-started fan-out workflow that publishes an HTML Blob reportworkflow-subagents-preview— queue-started parallel PR analysis with isolated workflow specialists and a stable HTML Blob reportper-agent-workflows— Engineering Operations Hub with two non-main workflow-enabled agents and independent policies
Set the model provider env vars described above. The preview samples use Microsoft Foundry (AZURE_FUNCTIONS_AGENTS_PROVIDER=foundry, FOUNDRY_PROJECT_ENDPOINT, and FOUNDRY_MODEL). Azure OpenAI (AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_DEPLOYMENT) and OpenAI (OPENAI_API_KEY and optionally AZURE_FUNCTIONS_AGENTS_MODEL) are supported alternatives. For Microsoft Foundry and Azure OpenAI, the provider-specific model/deployment setting takes precedence over AZURE_FUNCTIONS_AGENTS_MODEL.
When the agent uses connector-backed MCP servers, connector triggers, or dynamic_sessions_code_interpreter, the function app's system-assigned or user-assigned Managed Identity must be enabled and granted access to the target resource — otherwise DefaultAzureCredential will fail to obtain a token. In multi-identity Function Apps, set AZURE_CLIENT_ID so the runtime uses the intended managed identity for Azure OpenAI, Foundry, blob-backed session storage, ACA Dynamic Sessions, and ARM/data-plane connector calls. For an individual MCP server, set auth.client_id in mcp.json to choose a different managed identity just for that server. For an individual code interpreter pool, set system_tools.dynamic_sessions_code_interpreter.client_id.
Install azurefunctions-agents-runtime[monitor] to export the runtime's OpenTelemetry spans and
metrics to Application Insights. Enablement is simply the [monitor] extra plus
APPLICATIONINSIGHTS_CONNECTION_STRING; sensitive content stays off by default and is included only
when ENABLE_SENSITIVE_DATA=true. The runtime emits an agent.run span for each invocation, a
dynamic_session.execute span for sandbox calls, and a web_request span for outbound HTTP calls
(host only — never the full URL with query string or secrets), adds af.* attributes, marks failures
with af.fault_domain, and quiets noisy third-party loggers. For full setup and the span/attribute
reference, see docs/observability.md. If you also want host↔worker
correlation, host.json telemetryMode: OpenTelemetry is optional and additive.
| Setting | Purpose |
|---|---|
AZURE_FUNCTIONS_AGENTS_APP_ROOT |
Override the app root used to discover *.agent.md, agents/, tools/, skills/, and mcp.json |
AZURE_FUNCTIONS_AGENTS_SESSION_DIR |
Override the directory used for local session storage |
AZURE_FUNCTIONS_AGENTS_TIMEOUT_SECONDS |
Per-call timeout in seconds (default 900) |
AZURE_FUNCTIONS_AGENTS_PROVIDER |
Pin the model provider (openai/azure_openai/foundry) and skip auto-detection |
AZURE_FUNCTIONS_AGENTS_MODEL |
Runtime-owned model fallback when no provider-specific model/deployment is set |
AZURE_FUNCTIONS_AGENTS_REASONING_EFFORT |
Optional reasoning effort for supported reasoning models (low, medium, high, xhigh, max). Invalid values are warned and ignored so the SDK/provider default applies. |
AZURE_FUNCTIONS_AGENTS_REASONING_SUMMARY |
Optional reasoning summary mode for supported reasoning models (none, concise, detailed). Invalid values are warned and ignored so the SDK/provider default applies. |
# Clone the repo
git clone https://github.com/Azure/azure-functions-agents-runtime.git
cd azure-functions-agents-runtime
# Install in development mode
pip install -e .
# Build a wheel
pip install build
python -m build --wheelSee CONTRIBUTING.md.
MIT — see LICENSE.md.