foundry-hosted-agents
Skill8 starsChanged 12 days agoMITView on GitHub
Deploy + manage Foundry hosted agents — container deploy via unified azure.yaml is GA; source-code --deploy-mode code is still preview. MAF 1.14 current, azd ext install microsoft.foundry, implicit agent access (no default role grant). USE FOR: deploy foundry agent, hosted agent, container agent, azure.yaml, azd ai agent, microsoft.foundry, MAF, FoundryChatClient, ResponsesHostServer, ACR push, batch eval, agent identity, Foundry User role, entra-agent-id, Responses/Invocations protocols, Activity protocol, blue-green deploy, canary rollout, version rollback, traffic routing, version_selector, agent_endpoint, update_details. DO NOT USE FOR: Harness runtime composition (use agent-framework-harness), prompt agents (use foundry-prompt-agents), ACA MCP (use foundry-mcp-aca), GHCP coding agent (use ghcp-hosted-agents), Citadel hub/spoke (use citadel-hub-deploy), pilot pipeline (use threadlight-deploy), continuous eval (use foundry-evals), Routines (use foundry-routines), A2A wiring (use foundry-toolbox).
- Reads credentials
- Installs packages
We split this skill into the rules it gives an agent
21 rules. About 0 are common, 8 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 84
Run the new capabilities phase of the existing preflight before building or publishing an image.
- Very specific: line 90
Before hosted registration, select the account/project/network mode and follow the single deployment preflight and evidence contract.
- Very specific: line 92
Run its read-only references/python/deploy_preflight.py gate on fresh, operator-collected observations.
- Distinctive: line 113
Keep management and runtime environments separate.
- Very specific: line 133
Use the canonical stager, runtime and native SDK oracle below.
- Very specific: line 230
After a MAF 1.4.0 rebuild, agents using :latest work.
- Distinctive: line 338
Fix: Add a changing environment variable to force a new version:
- Very specific: line 346
When per-job images are built via the Azure Container Registry DockerBuildRequest API (e.g., from deploy.py), ACR Tasks uses
- Very specific: line 348
layer caching by default: If the per-job Dockerfile's COPY .
- Very specific: line 624
When SkillsProvider is wired, _load_instructions() MUST contain only the baseline orchestration prompt (copilot-instructions.md) plus …
- Distinctive: line 744
Reduce unnecessary tokens first, then optimize reuse: Keep baseline instructions, the authorized skill catalog, and tool schemas/order …
- Very specific: line 754
Keep the canonical downstream store=False: response/history persistence is separate from prompt caching.
- Distinctive: line 776
Use the client-swap pattern to connect to existing prompt/hosted agents in the same project:
- Very specific: line 883
Use MCPStreamableHTTPTool directly with a custom parse_tool_results callback that extracts the TextContent.text payload from the MCP …
- Very specific: line 1039
When the MCP endpoint authenticates initialize / tools/list (any Azure AI Search KB MCP, any PMI-protected MCP), supply auth at the httpx …
- Very specific: line 1377
If you instead copy the canonical files into an existing repository and run azd deploy directly, azure.yaml alone is insufficient.
- Distinctive: line 1428
Verify what's installed:
- Very specific: line 1540
If you were relying on references/bash/postdeploy-agent.sh or a manual Foundry User grant to the agent's instance_identity / …
- Very specific: line 1645
Not all regions support hosted agents.
- Distinctive: line 1661
Verify current model + hosted-agent availability together before you commit a region:
- Distinctive: line 1694
Look for ALL of these together:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Microsoft Foundry Hosted Agents — Reference Guide
Production-tested patterns for deploying hosted agents on Microsoft Foundry.
Container-based deployment (Dockerfile + unified azure.yaml + azd) is
GA. Source-code (--deploy-mode code) deployment is a separate, still-preview
surface — see § Preview appendix: source-code deploy
for that path in isolation. Covers the Agent + FoundryChatClient +
ResponsesHostServer (MAF) variant exclusively.
✅ Core 1.14.0 is the current dependency stack. Use the canonical pins in this skill (
agent-framework-core~=1.14.0,agent-framework-foundry~=1.11.0,agent-framework-foundry-hosting==1.0.0b260813) for new hosted-agent work and refreshes.
MAF 1.8.0matters here only as the historical feature boundary where the caller-sideagent_framework.foundry.FoundryAgent(timeout=...)knob landed (overrides the OpenAI-SDK 5s connect / 600s total defaults) — see § MAF 1.8.0 update.If you are still on 1.3.x, upgrade directly to current 1.14.0 and absorb the 1.4.0 breaking changes below plus the 1.6.0 telemetry bundling and the 1.8.0 timeout knob in one pass.
When to Use
- Deploying a custom container agent to Foundry (GA path)
- Debugging hosted agent failures (401, 500, import errors)
- Understanding agent identity + when (rarely) explicit RBAC is needed
- Authoring the unified
azure.yaml(azure.ai.project+azure.ai.agentservices) - Blue-green / canary / rollback traffic routing across agent versions
- Migrating from MAF 1.3.x → 1.4.0 (§ below)
- Migrating from the legacy two-file
agent.yaml+agent.manifest.yamlcontract — select and verify the versioned consumer before migration; the supported unified profile uses a singleazure.yaml(§ azure.yaml)
| Need | Use |
Build an already-hostable Agent with create_harness_agent | agent-framework-harness |
Choosing a model? See
references/model-selection.mdfor the model / region / capacity / data-residency decision before youazd provision.
Deployment preflight
Select a versioned contract before authoring. The authoritative profile is
references/hosted-contract.json, consumed by
hosted_contract.py and the stager.
It binds the tested consumer, manifest/environment shape, protocol, runtime
dependencies, identities and lifecycle; the existing template and pyproject
remain the source of their actual content. The recorded private BASIC profile
is azd 1.34.1 + azure.ai.agents 1.0.0-beta.14, not “this version or newer”.
Its historical no-tools evidence does not certify other modes or business tools.
Legacy two-file manifests are migration-only. Preserve them until the owner
selects and verifies a migration; do not delete them merely because this skill
documents a unified manifest. Current Learn pages use an env map while the
recorded consumer template uses an environmentVariables list. Do not mix
the shapes or rewrite a working cohort without checking actual consumer emission.
An unknown toolchain is UNSUPPORTED_CAPABILITY, not permission to upgrade.
Run the new capabilities phase of the existing preflight before building or publishing an image; then the setup and execution phases at their own boundaries. See capability evidence. The capability check is not a new deployment framework and never changes Azure.
Platform-managed compute does not mean that private project setup is optional.
Before hosted registration, select the account/project/network mode and follow
the single deployment preflight and evidence contract.
Run its read-only references/python/deploy_preflight.py gate on fresh,
operator-collected observations. It performs no Azure calls or repairs.
| Selected route | Capability-host prerequisite before registration |
| Public, platform-managed azd setup | Let the selected template/platform own setup; do not add manual capability hosts from legacy instructions. Raw/legacy public provisioning needs its own explicit contract review. |
| Private Basic, including raw SDK or brownfield azd | Read the project capability-host inventory and exact host GET. Require Agents / Succeeded, no BYO stores. The canonical Basic template has a project host only; no customer-created account host is required by that template. |
| Private Standard/BYO | Verify the account host/subnet and project host with the selected existing BYO connection names. Preserve hosts, connections and stores. |
For a missing Basic project host, stop and request explicit authorization to use
only the existing project-host module.
An unreadable inventory is not absence. A failed, incompatible or BYO host is
not permission to recreate it; use
foundry-caphost-lifecycle to inspect.
azd deploy against a pre-existing project does not certify these prerequisites.
The gate also checks project/model scope, actual operator/runtime-path evidence,
the credential-free project ContainerRegistry connection and retained native
identity mapping, mode-appropriate project-MI ACR pull access/policy, immutable image and the selected
runtime cohort. Preserve the default container identity and verify writable
/home/session/.sessions; a fixed UID can break the native mounted session home.
Keep management and runtime environments separate. Do not change pins merely
because another working deployment uses a different tested cohort.
Registration, readiness, invocation and business proof are separate. LIST
versions is inventory, not readiness: require two consecutive direct version
GET observations, then exact endpoint routing, authenticated invocation and the
real requested tool result with independent audit. Account Succeeded, image
build, LIST active, health, or a noop are insufficient. Lost create ACK means
reconcile existing versions against the frozen definition before any retry.
Retain old versions and signed bindings; changed image/version/identity requires
a new exact observation and association. A prompt agent is not an automatic substitute.
Private BASIC consumer
For an existing private project/ACR, execute the bounded private BASIC procedure and its opt-in private fixture. This no-tools bootstrap is independent of tool consent and governed business execution; the public CI consumer and private prompt-agent E2E are not substitutes.
Use the canonical stager, runtime and native SDK oracle below. Source builds
retain lock/base/context provenance and need a network-reachable builder.
Prebuilt images use a service-level immutable image with native
docker.imagePassthrough: true / remoteBuild: false, plus explicit
azd deploy "$AGENT" --from-package "$IMAGE" --no-prompt on the live-validated
azd 1.34.1 + azure.ai.agents 1.0.0-beta.14 compatibility baseline
(not a minimum-version claim). azd 1.27.0 is blocked for this private
prebuilt route: it can still republish despite that flag. This remote-image argument
is distinct from azd publish --from-package, which consumes a local package
and pushes it. A matching connection is reused; missing configuration routes
to separately authorized native connection-only provisioning, while unreadable
or conflicting state blocks without repair. No public-network opening, new
grants, global tool update or OCI-format ban is part of this route.
Validated 2026-09-17 (#500): the no-tools private BASIC consumer activated from the existing immutable image, completed one model request and passed independent response/session/version readbacks. Exact owned native objects were subsequently verified absent; retained image/cache and service history have explicit bounded owner custody, not a purge claim. This proves the tested BASIC path, not governed business execution or other runtime/tooling combinations.
MAF 1.4.0 breaking changes (May 2026)
Historical boundary only. The pins below are preserved for the original 1.4.0 cutover. If you are still on 1.3.x today, do not copy these historical 1.4 pins — use the current canonical 1.14 stack in
references/python/pyproject.toml, which already incorporates the 1.4 breaking changes, the 1.6 telemetry update, and the 1.8 timeout correction.
Azure renamed the Foundry data-plane role from "Azure AI User" to
"Foundry User" and changed the AAD token audience the SDK requests
from https://cognitiveservices.azure.com/.default to
https://ai.azure.com/.default. agent-framework-core 1.4.0
(2026-05-14, alongside agent-framework-foundry-hosting 1.0.0a260514)
is the first SDK that requests the new scope; everything pinned to 1.3.x
or earlier requests the old scope and gets 401 Unauthorized from
the post-rename data plane.
The three changes you have to absorb
| # | Change | Symptom on pinned 1.3.x | Fix |
| 1 | AAD token scope: cognitiveservices.azure.com → ai.azure.com | Every Responses request → 401 Unauthorized (with valid Foundry User RBAC) | Upgrade to agent-framework-core~=1.4.0 + agent-framework-foundry~=1.4.0 + agent-framework-foundry-hosting==1.0.0a260514 |
| 2 | Role display name "Azure AI User" → "Foundry User" | az role assignment create --role "Azure AI User" fails with RoleDefinitionNotFound | Use --role "Foundry User" OR pin by GUID 53ca6127-db72-4b80-b1b0-d745d6d5456d (unchanged across the rename — the safest call-site form is the GUID) |
| 3 | AzureOpenAIChatClient removed from agent_framework.azure | ImportError: cannot import name 'AzureOpenAIChatClient' after pip install -U | Use OpenAIChatClient(azure_endpoint=..., model=..., credential=...) from agent_framework.openai — see snippet below |
AzureOpenAIChatClient → OpenAIChatClient migration
FoundryChatClient is the right choice for hosted Foundry agents and is
unaffected. The removal hits adjacent code paths that talked directly
to Azure OpenAI (eval judges, batch scoring, sidecar services, agents
that route through APIM):
# OLD (MAF 1.3.x — REMOVED in 1.4.0)
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import get_bearer_token_provider, DefaultAzureCredential
client = AzureOpenAIChatClient(
endpoint=AZURE_OPENAI_ENDPOINT,
deployment_name=DEPLOYMENT,
ad_token_provider=get_bearer_token_provider(
DefaultAzureCredential(),
"https://cognitiveservices.azure.com/.default",
),
)
# NEW (MAF 1.4.0)
from agent_framework.openai import OpenAIChatClient
from azure.identity import DefaultAzureCredential
client = OpenAIChatClient(
azure_endpoint=AZURE_OPENAI_ENDPOINT, # NB: kwarg renamed from `endpoint`
model=DEPLOYMENT, # NB: kwarg renamed from `deployment_name`
credential=DefaultAzureCredential(), # SDK derives the right token scope internally
)Drop the explicit get_bearer_token_provider / ad_token_provider —
OpenAIChatClient now derives the scope itself. The unused
get_bearer_token_provider import can be removed.
Image-tag staleness trap (mandatory read for any pilot that pins by digest)
Hosted agent versions reference the orchestrator container by ACR image reference. There are two common shapes:
<acr>.azurecr.io/tl-maf-orchestrator:latest— re-resolves to the newest pushed image on every container start. Auto-picks up MAF rebuilds the next time the agent provisions.<acr>.azurecr.io/tl-maf-orchestrator@sha256:abc…— pinned to a specific registry manifest/index digest (not a local image configuration ID or an individual layer digest). Reproducible, but frozen at the MAF version that was in the image when the digest was computed.
After a MAF 1.4.0 rebuild, agents using :latest work; agents using
@sha256:… digests from the 1.3.x era keep hitting the old token scope
and fail. Re-import every hosted agent version (or pin by digest to
the freshly-built 1.4.0 image) as part of the upgrade. azd ai agent
deploy does this automatically if the YAML references :latest.
Rebuild recipe (copy-paste)
# 1. Upgrade orchestrator pyproject.toml + regenerate uv.lock
sed -i.bak 's/"agent-framework-core[^"]*"/"agent-framework-core~=1.4.0"/' src/orchestrator/pyproject.toml
sed -i.bak 's/"agent-framework-foundry[^"]*"/"agent-framework-foundry~=1.4.0"/' src/orchestrator/pyproject.toml
sed -i.bak 's/"agent-framework-foundry-hosting[^"]*"/"agent-framework-foundry-hosting==1.0.0a260514"/' src/orchestrator/pyproject.toml
(cd src/orchestrator && uv lock)
# 2. ACR remote build — tag with :latest AND a date-pinned tag for forensics
ACR=tl<your-acr-suffix>
az acr build --registry "$ACR" \
--image "tl-maf-orchestrator:maf14-$(date +%Y%m%d%H%M)" \
--image "tl-maf-orchestrator:latest" \
--file src/orchestrator/dockerfile src/orchestrator/
# 3. Re-deploy + re-import each agent so it picks up the fresh image
azd deploy agents
(cd infra/scripts && uv run deploy_job.py)
# Then: azd ai agent show — confirm new image digest under each versionWhy date-pinned tags matter.
:latestis fine for the agent reference but leaves zero forensic trail in ACR's image history. Themaf14-YYYYMMDDHHMMtag lets you correlate "which agent version is running which MAF build?" months later when a regression bisect needs it.
MAF 1.6.0 update (May 2026)
agent-framework-core 1.6.0 and agent-framework-foundry-hosting
1.0.0a260521 ship with instrumentation enabled by default. The
hosting package transitively pulls azure-ai-agentserver-core>=2.0.0b3
which depends on microsoft-opentelemetry>=1.0.0 (resolves to 1.1.0),
bundling all OTel instrumentors:
| Bundled instrumentor | What it captures |
opentelemetry-instrumentation-openai-v2==2.3b0 | gen_ai spans: model name, token usage, latency for every OpenAI/Responses call |
opentelemetry-instrumentation-openai-agents-v2==0.1.0 | Agent-level invocation spans |
opentelemetry-instrumentation-httpx | HTTP dependency spans |
azure-core-tracing-opentelemetry | Azure SDK call tracing |
What this means for container.py
Remove all manual OTel code. No configure_azure_monitor(), no
OpenAIInstrumentor().instrument(), no custom TracerProvider. The
platform handles everything. Your container code only needs:
# Ensure env var is set (deploy.py passthrough for O-012 workaround)
cs = os.getenv("APPLICATION_INSIGHTS_CONNECTION_STRING") or \
os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
if cs:
os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"] = cs
# Optionally: SDK-level telemetry setup (after FoundryChatClient creation)
try:
await client.configure_azure_monitor(enable_sensitive_data=True)
except Exception:
pass # Agent works without telemetrygen_ai spans appear under cloud_RoleName = "agent_framework"
Important for KQL queries: gen_ai dependency spans use
cloud_RoleName = "agent_framework", NOT agent-{jobId}-maf.
Queries filtering cloud_RoleName startswith "agent-" will miss them.
// gen_ai spans — model, tokens, latency
dependencies
| where timestamp > ago(1h)
| where cloud_RoleName == "agent_framework"
| project timestamp, name, duration,
model=tostring(customDimensions['gen_ai.response.model']),
input_tokens=tostring(customDimensions['gen_ai.usage.input_tokens']),
output_tokens=tostring(customDimensions['gen_ai.usage.output_tokens']),
op=tostring(customDimensions['gen_ai.operation.name'])
| order by timestamp descUpgrade recipe (→ 1.7.0)
Historical boundary only — do not copy these pins; use
references/python/pyproject.toml.
sed -i.bak 's/"agent-framework-core[^"]*"/"agent-framework-core~=1.7.0"/' pyproject.toml
sed -i.bak 's/"agent-framework-foundry[^"]*"/"agent-framework-foundry~=1.7.0"/' pyproject.toml
sed -i.bak 's/"agent-framework-foundry-hosting[^"]*"/"agent-framework-foundry-hosting==1.0.0a260528"/' pyproject.toml
# Remove explicit OTel deps — now bundled via hosting package:
# azure-monitor-opentelemetry, opentelemetry-sdk, opentelemetry-instrumentation-*create_version deduplication trap
Foundry deduplicates create_version when environment variables and
metadata are identical — even if the container image tag/digest
differs. This is SEPARATE from the ACR layer cache trap below. After
a base image rebuild, new code never reaches the container because
Foundry returns the existing version.
Fix: Add a changing environment variable to force a new version:
import time
env_vars["_BUILD_TS"] = str(int(time.time()))ACR layer cache trap (per-job images built via DockerBuildRequest)
When per-job images are built via the Azure Container Registry
DockerBuildRequest API (e.g., from deploy.py), ACR Tasks uses
layer caching by default. If the per-job Dockerfile's COPY . ./
content hasn't changed (only the base image changed), ACR produces
the identical digest — and Foundry deduplicates the
create_version call, silently returning the existing version.
New base-image code never reaches the container.
Fix (both are required):
no_cache=TrueonDockerBuildRequest— forces ACR to rebuild all layers from scratch.- A used
ARGin the generated Dockerfile — Docker only invalidates cache when an ARG changes AND is consumed:
Without theARG BASE_IMAGE=hosted-agent-base-maf:v6 FROM ${BASE_IMAGE} ARG BUILD_TS=1716400000 RUN echo "build=$BUILD_TS" > /app/.build_ts # ... rest of DockerfileRUNline, Docker ignores the ARG for caching.
Discovered May 2026: base image rebuild with SkillsProvider.from_paths()
fix + observability fix never reached any agent until both guards were
applied.
MAF 1.8.0 update (June 2026)
agent-framework-core and agent-framework-foundry 1.8.x land one
hosted-agent-relevant new knob and two [BREAKING] markers — both
non-impact for this skill.
FoundryAgent(timeout=...)— new HTTP timeout kwarg on the caller-sideagent_framework.foundry.FoundryAgentclass. Overrides the underlying OpenAI-SDK default (connect: 5s, total: 600s). Useful when orchestrator code calling INTO a hosted agent wants a bounded ceiling. See § below.
MAF 1.8.0 breaking markers — non-impact analysis
MAF 1.8.0 ships two [BREAKING] markers; neither affects hosted-agent code:
agent-framework-github-copilotsub-package internal rename — this skill does not pin or importagent-framework-github-copilot. N/A.- Experimental
Skillabstract-class refactor inagent-framework-core— this skill uses the high-levelSkillsProvider.from_paths(...)facade (see § Skill Loading below), not the experimentalSkillABC. Hosted-agent containers continue to work unchanged. Non-impact.
FoundryAgent timeout parameter (MAF 1.8.0)
MAF 1.8.0 adds timeout: float | None = None to
agent_framework.foundry.FoundryAgent.__init__. This is an HTTP-layer
timeout that overrides the OpenAI SDK's default (connect: 5s, total: 600s)
for ALL requests made by this agent instance. Per the upstream docstring:
"HTTP timeout in seconds for requests. When not provided, the OpenAI SDK
default is used (connect: 5s, total: 600s)."
Use it to:
- Lower the ceiling for short-running agents that should fail fast
(e.g.
timeout=30for tool-routing agents with user-facing chat budgets). - Raise the ceiling for long-running agents where 600s isn't enough
(e.g.
timeout=1800for multi-step research loops).
Scope note: this kwarg lives on the CALLER-side FoundryAgent class
(orchestrator code calling a hosted agent). The CONTAINER-side runtime
covered by § Runtime Pattern uses
FoundryChatClient directly and does NOT accept timeout= in 1.8.0 —
apply HTTP-layer transport timeouts on the underlying client instead.
MUST: Copy verbatim from
references/python/foundry_agent_timeout.py. Do NOT redefine inline — the validator enforces single-source-of-truth.
See also (MAF 1.8.0, experimental):
AgentFileStore— new abstract base class for agent file-management backends. Re-exported asfrom agent_framework import AgentFileStore. Concrete implementations + usage patterns live infoundry-memoryandfoundry-toolbox. NOT consumed by the hosted-agents runtime patterns documented in this skill.
Upgrade recipe (→ 1.8.0)
Historical boundary only — do not copy these pins; use
references/python/pyproject.toml.
sed -i.bak 's/"agent-framework-core[^"]*"/"agent-framework-core~=1.8.0"/' pyproject.toml
sed -i.bak 's/"agent-framework-foundry[^"]*"/"agent-framework-foundry~=1.8.0"/' pyproject.toml
sed -i.bak 's/"agent-framework-foundry-hosting[^"]*"/"agent-framework-foundry-hosting==1.0.0a260528"/' pyproject.toml
# Telemetry bundling from MAF 1.6.0 is unchanged in 1.8.0 — no other deps to touch.Historical note: this recipe used an exact alpha pin because PEP 440
compatible-release syntax could drift to a later pre-release. It is not
current guidance; current operators must use the exact beta agent-framework-foundry-hosting==1.0.0b260813
from the canonical
references/python/pyproject.toml.
Container GA status and adjacent preview surfaces
Status. Container-based hosted agent deploy (Dockerfile + unified
azure.yaml+azd) is GA — that is the stable path documented throughout the rest of this skill. Container deploy was still labelled preview as recently as the March 2026 announcement blog; treat "GA" as current as of this skill'slast_validateddate inreferences/upstream-pin.md, not as a permanently-fixed fact — re-check the sources below if it's been a while.A few adjacent surfaces remain preview or partial-GA and are called out explicitly wherever they appear in this skill, rather than assumed GA by association:
- Source-code deploy (
--deploy-mode code, ZIP upload, no Dockerfile) is preview — isolated in its own § Preview appendix at the end of this file. Do not mix its guidance into the container (GA) path documented above it.- A2A 1.0 is GA; A2A 0.3 remains preview. This does not imply that every hosted runtime can expose an incoming A2A endpoint. See
foundry-toolboxfor the protocol/client/access boundary. Voice Live retains its separately documented status infoundry-voice-live.- Hosted tracing (distributed traces / gen_ai spans in App Insights) is partial GA / preview depending on the exact signal — see
foundry-observabilityfor the current split.- Agent guardrails (content-safety toggle), Optimizer, and Memory remain preview surfaces layered on top of the GA hosted-agent runtime — see their owning skills (
foundry-memory) for details; this skill does not restate their contracts.- Routines is a GA service with a Python
beta.routinesclient surface. Seefoundry-routinesfor client versions, trigger/delivery evidence and creator identity; it does not require a hosted-framework upgrade.Sources: container deploy, source-code deploy — preview, author azure.yaml, azure.yaml reference, hosted agent permissions, install azd Foundry extensions.
Built-in content-safety toggle (preview)
Content safety is a Foundry portal toggle under the Guardrails
blade of the agent resource — not an SDK call, not an azure.yaml
field. Toggle on to enable platform-side prompt/response filtering with
the project's default content-safety policy. Toggle off if you bring
your own filtering layer upstream (e.g., APIM policy fragment, in-agent
middleware). Guardrails remain a preview capability layered on the
GA hosted-agent runtime.
The toggle is per-agent, per-environment. Production environments should turn it ON by default; dev environments may turn it off to test prompt patterns that would otherwise be blocked.
Responses, Invocations, Invocations (WebSocket), and Activity
Hosted agent containers can expose one or more protocols side by side. Per Microsoft Learn: "The Responses, Invocations, and Invocations (WebSocket) protocols are available in all regions that support Hosted agents." — none of the three is region-restricted.
| Protocol | Python library | Use for |
responses | azure-ai-agentserver-responses | Conversational chatbots, streaming, multi-turn with platform-managed history. This skill's primary pattern. |
invocations | azure-ai-agentserver-invocations | Webhook receivers, non-conversational processing, custom async workflows. |
invocations_ws | azure-ai-agentserver-invocations (same package, WebSocket route) | Bidirectional streaming — real-time voice agents, interactive media. Documented here; not exercised by this skill's fixture, which only drives Responses. |
| Activity (auto-bridge) | n/a — platform-managed | Foundry automatically bridges Responses to the Bot Framework Activity protocol for Teams / Microsoft 365 Copilot publishing. No code change on the agent side. Publishing setup itself is out of scope for this skill — see foundry-teams-bot / the Teams publishing backlog for that contract. |
Declare the protocols a container serves in the protocols list of the
azure.ai.agent service in azure.yaml (§ below) — a single container
can serve multiple protocols simultaneously by importing the matching
libraries.
Unified azure.yaml (replaces the old two-file contract)
For the selected unified-manifest consumer, configuration lives in
azure.yaml; the older agent.yaml/agent.manifest.yaml pair is a
separate migration-only contract, not interchangeable input. See
§ azure.yaml (unified hosted-agent configuration)
below for the canonical shape. Migrate only after reviewing the actual consumer,
comparing the rendered definition and retaining the old project for rollback.
Hosting SDK matrix (Python)
| Framework | Hosting package | Server class |
| MAF | agent-framework-foundry-hosting | agent_framework_foundry_hosting.ResponsesHostServer |
| BYO Responses | azure-ai-agentserver-responses | azure.ai.agentserver.responses.ResponsesHostServer |
| BYO Invocations (WS) | azure-ai-agentserver-invocations | azure.ai.agentserver.invocations.InvocationsHostServer |
| LangGraph | langchain-azure-ai[hosting] | langchain_azure_ai.agents.hosting.ResponsesHostServer |
Pin to the MAF row unless you have a documented reason to use one of the BYO packages (custom protocol handler, non-MAF framework lock-in, LangGraph state-graph requirements).
Runtime Pattern (MAF Variant)
Model selection (verified across recent pilots). Default to
gpt-5.4for production agents that run instruction chains with 10+ tool steps (case investigation, multi-source RAG synthesis, regulatory drafting). Usegpt-5.4-minionly for trivial 1-2-step flows (single-tool lookups, formatters). The mini variant's tool-call discipline degrades sharply on long chains: in recent strict-smoke reproducibility on a 7-skill flow went from 1/3 with gpt-5.4-mini to 3/3 with gpt-5.4 (same MCP server, same prompts). The mini variant tends to call commit-style tools before evidence is gathered — partially mitigated by the validate-or-reject pattern infoundry-mcp-aca, but gpt-5.4 still fixes the root cause.Cost/latency note.
gpt-5.4GlobalStandard at 50 capacity costs a few cents per scenario at idle the deployment ticks negligibly, so the typical pilot cost is dominated by the scenarios you actually run. Don't downgrade to mini just to save on the deployment standby.Full model catalog. For the complete May 2026 Foundry model selector (task/modality tables, region availability tiers, embeddings, rerank, image/video gen, Document AI, audio), see the
agentic-loopskill § "Foundry Model Selector". That table is the single source of truth for model selection across all awesome-gbb skills — we don't duplicate it here.
MUST: Copy verbatim from
references/python/main.py. Do NOT redefine inline — the validator enforces single-source-of-truth. That file is the field-validatedFoundryChatClient+Agent+ResponsesHostServershape for a single-purpose hosted agent (one tool, noSkillsProvider, MAF 1.6.0).
Key points (why each line in main.py matters):
FOUNDRY_PROJECT_ENDPOINTis injected by the platform — never declare inazure.yamldefault_options={"store": False}— hosting platform manages conversation historyResponsesHostServerhandles liveness/readiness probes nativelyDefaultAzureCredentialresolves to the agent's dedicated Entra identity- Custom tools use
@tool(approval_mode="never_require")withAnnotatedtype hints
Canonical reference files for the rest of this skill (each one is the source of truth for its § below; SKILL.md prose never re-defines these):
| Reference file | Consumed by § |
references/python/main.py | § Runtime Pattern (MAF Variant) |
references/python/container.py | § Skill Loading — SkillsProvider (multi-SKILL composition + guarded _init_telemetry()) |
references/python/pyproject.toml | § Dependencies (pyproject.toml) |
references/docker/Dockerfile | § Dependencies (pyproject.toml) → Dockerfile |
references/yaml/azure.yaml | § azure.yaml (unified hosted-agent configuration) — the single source of truth for hosted-agent config, replacing the old agent.yaml + agent.manifest.yaml two-file contract |
references/python/version_rollout.py | § Version rollout patterns (blue-green / canary / rollback) |
references/python/deploy_preflight.py | § Deployment preflight |
references/python/private_bootstrap.py | § Private BASIC consumer: offline staging and build/manifest inventory |
references/python/basic.py | § Private BASIC consumer: no-tools runtime |
references/docker/Dockerfile.basic | § Private BASIC consumer: locked container build |
references/python/hosted_smoke.py | § Private BASIC consumer: native model/session oracle |
⚠️ Deprecation:
ChatAgentis gone in MAF 1.6.0Any older examples showing
from agent_framework import ChatAgentfollowed byChatAgent(chat_client=...)are stale — no migration alias is exported. The class was renamed toAgentand the kwarg changed fromchat_client=toclient=. If you're copying code from pre-May-2026 prose or SKILLs, replaceChatAgent(chat_client=client, ...)withAgent(client=client, ...).
Skill Loading — SkillsProvider (recommended) vs. inline _load_skills() (legacy)
Authoritative source. Microsoft Agent Framework — Agent Skills (Python).
SkillsProvideris a first-class MAF Python class shipped in the sameagent_frameworkpackage asAgent,MCPStreamableHTTPTool, and@tool(no extra install). It implements the agentskills.io 4-stage progressive disclosure pattern: Advertise (~100 tokens/skill) →load_skill→read_skill_resource→run_skill_script. Wired viacontext_providers=[skills_provider]on theAgent(...)constructor — orthogonal totools=[...].
Cross-link. The exact same
SkillsProvider.from_paths(skills_dir)wiring shape is whatthreadlight-local-testPattern 0 — Quickstart runs locally against a designed PoC — so the prompt context the local demo agent sees is identical to what ships to Foundry.
⚠️ Separation of concerns (read this first)
When SkillsProvider is wired, _load_instructions() MUST contain only
the baseline orchestration prompt (copilot-instructions.md) plus
SPEC-derived constants (config.json: thresholds, enums, templates).
It MUST NOT also concat SKILL.md content. SkillsProvider advertises
each skill in ~100 tokens, then the agent calls load_skill(name) on
demand. Concatenating both double-counts every skill body, blows
out the static prompt, and silently defeats progressive disclosure.
Recommended — SkillsProvider context provider
⚠️ DO and DO NOT — API change in MAF 1.4.0 / 1.6.0:
- DO use
SkillsProvider.from_paths(skills_dir)(MAF 1.6.0 recommended, works on 1.4.0+)- DO NOT use the legacy
SkillsProvider(skill_paths=skills_dir)constructor — it was removed in MAF 1.4.0 with no aliasIf you see
TypeError: SkillsProvider.__init__() got an unexpected keyword argument 'skill_paths'at container startup, replaceSkillsProvider(skill_paths=...)withSkillsProvider.from_paths(...)immediately.
Defensive-init helper. The agent stays runnable with context_providers=[]
even if the skills directory is missing or a SKILL.md is malformed —
critical for ops, since a single broken skill file should not crash the
container at startup.
MUST: Copy verbatim from
references/python/container.py. Do NOT redefine inline — the validator enforces single-source-of-truth. That file ships the canonical_init_telemetry()(guarded against O-012 AppIn injection failures) +_build_skills_provider()(returnsNoneon missing/corrupt skills dir so the agent stays runnable) + the fullFoundryChatClient/Agent/ResponsesHostServerwiring for a multi-SKILL hosted agent.The pattern in one line:
context_providers=[skills_provider] if skills_provider else []on theAgent(...)ctor — andinstructions=_load_instructions()returns base prompt + SPEC config ONLY (never concatenateSKILL.mdbodies; that double-counts and defeats progressive disclosure).
Constructor variants.
SkillsProvider.from_paths(skills_dir)— classmethod, the form used in production hosted agents. Use this.— REMOVED in MAF 1.4.0. CausesSkillsProvider(skill_paths=skills_dir)TypeError: SkillsProvider.__init__() got an unexpected keyword argument 'skill_paths'→ container crashes before readiness → stickysession_not_readyon every invocation. Always usefrom_paths().
The provider searches up to two levels deep, so skills/<name>/SKILL.md
and skills/<group>/<name>/SKILL.md layouts are both auto-discovered.
Per-query cost. SkillsProvider adds +1 load_skill tool call per
skill the agent activates per query (typically 1-3 per query). This is
NOT the 20-34-internal-call overhead seen on CopilotClient runs —
that overhead lives in CopilotClient itself and is unrelated to
SkillsProvider.
Quantified benefits (from production MAF deployments):
- ~75% token saving on the static prompt vs. concatenating all
SKILL.mdbodies into instructions. - Per-skill App Insights telemetry — every
load_skill(name)is a traceable tool-call span, so routing analysis (which skills get used? which never load? which load on the wrong intent?) is free.
Legacy alternative — inline _load_skills() concat
The pre-SkillsProvider pattern: read every skills/*/SKILL.md at
startup and append the bodies to the system prompt. Still supported,
still works, occasionally still the right choice.
def _load_skills() -> str:
chunks: list[str] = []
skills_dir = Path(__file__).parent / "skills"
for skill_file in sorted(skills_dir.glob("*/SKILL.md")):
chunks.append(
f"\n\n---\n\n# Skill: {skill_file.parent.name}\n\n"
+ skill_file.read_text(encoding="utf-8")
)
return "".join(chunks)
def _load_instructions_legacy() -> str:
base = (Path(__file__).parent / "copilot-instructions.md").read_text(encoding="utf-8")
return f"{base}\n\n{_load_skills()}"
agent = Agent(
client=client,
instructions=_load_instructions_legacy(), # everything baked into the prompt
tools=[my_tool, mcp_tool],
# context_providers omitted — skills already in instructions
default_options={"store": False},
)When to choose which
SkillsProvider (recommended) | _load_skills() concat (legacy) | |
| Per-query overhead | +1 load_skill per skill loaded (1-3/query typical) | None |
| Static prompt size | ~100 tokens/skill advertised | Full SKILL.md bodies always present |
| Total skill content >5 KB | ✅ recommended | ⚠️ blows up context budget every turn |
| Strict latency budget (sub-2s tools) | ⚠️ adds load_skill round-trip | ✅ faster cold path |
| Per-skill usage telemetry | ✅ free (every load_skill is a span) | ❌ invisible |
| New PoCs | ✅ default | only if total skill content is small (<5 KB) |
| Mixing both | ❌ never — double-counts every skill body | n/a |
Centralizing skills via the Foundry Skills REST API
The patterns above all assume SKILL.md files live in the agent's source
tree. If multiple Hosted agents in the same project need to share a
canonical set of SKILL.md files (without each repo carrying its own
copy), publish them to the project-level Foundry Skills store
({project}/skills) and consume them via either:
- Build-time bundle —
azd predeployhook downloads all published skills intosrc/skills/so the standardSkillsProvider.from_paths(…)above keeps working unchanged. - Runtime fetch —
SkillsProvider(source=FoundrySkillsSource(…))with a customSkillsSourcethat pulls from the REST API on session create.
See foundry-skill-catalog for the REST surface, the silent
JSON-mode-is-write-only trap, and the verified-working FoundrySkillsSource
adapter.
Prompt caching and inference efficiency
In ResponsesHostServer -> Agent + SkillsProvider -> FoundryChatClient ->
model service, Foundry owns hosting, MAF owns the tool/inference loop,
and the model service owns the KV cache. Do not rewrite the loop to
benefit from automatic prompt caching on supported models.
Reduce unnecessary tokens first, then optimize reuse. Keep baseline
instructions, the authorized skill catalog, and tool schemas/order stable;
keep variable context after reusable content where the framework permits.
Preserve progressive disclosure: never duplicate skill bodies in
instructions, and never add padding just to reach a cache threshold.
Inference options belong on the Agent/client inside the hosted runtime,
not merely on the external caller's Responses request. There is no hosted
deployment/YAML cache switch here. An implicit cache policy does not add
an explicit breakpoint after the instructions and skill catalog.
Keep the canonical downstream store=False: response/history persistence
is separate from prompt caching. Do not add a second history manager to the
default ResponsesHostServer path. Persisted sessions, cached skill sources,
and warm containers neither are nor keep alive the model's KV cache.
Compatibility and evidence boundary. This is public-source guidance, not a live-validated caching recipe for the pinned stack. Automatic caching, explicit breakpoints, and PTU-M have different model/deployment requirements.
ChatMiddlewareis a documented per-inference extension point, but this skill has no verified adapter that marks the complete stable Agent + SkillsProvider prefix. Never blindly mark the last system/developer message; it can contain variable memory or context.prompt_cache_keyis not an authorization boundary. Measure internal-inference cache reads/writes, cost, and TTFT without logging sensitive prompts or outputs; HTTP 200 and session continuity are not cache-hit evidence.See the operational guide and compatibility checklist for the dated service requirements,
mainversus pin comparison, and validation still needed before adopting advanced options.
Multi-Agent: Calling Other Foundry Agents as Tools
Use the client-swap pattern to connect to existing prompt/hosted agents in the same project:
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from azure.ai.projects.aio import AIProjectClient # MUST be .aio (async)!
from azure.identity import DefaultAzureCredential
project_client = AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=DefaultAzureCredential(),
)
# Create FoundryChatClient, swap its OpenAI client to the agent-bound one
sub_client = FoundryChatClient(project_client=project_client, model="gpt-5.4-mini")
sub_client.client = project_client.get_openai_client(agent_name="my-sub-agent")
sub_agent = Agent(name="my-sub-agent", client=sub_client)
orchestrator = Agent(
client=FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model="gpt-5.4-mini",
credential=DefaultAzureCredential(),
),
instructions="You orchestrate sub-agents.",
tools=[sub_agent.as_tool()],
default_options={"store": False},
)
ResponsesHostServer(orchestrator).run()Critical rules:
AIProjectClientMUST be fromazure.ai.projects.aio(async) — the sync version returns a syncOpenAIclient that silently fails insideFoundryChatClientallow_preview=Trueis not required foragent_name-basedget_openai_client()— verified againstazure-ai-projects2.3.0: the base-URL / query-param resolution the SDK uses for the agent endpoint doesn't gate onallow_preview(the constructor docstring is stale on this point; the actual code path isn't). Only opt intoallow_preview=Trueif you need a genuinely preview surface (e.g.project.beta.*operations).FoundryChatClientdoes NOT acceptagent_name/agent_version— use the client-swap pattern
⚠️ Historical (MAF 1.1.1) — sub-agent delegation note.
FoundryAgentin MAF 1.1.1 silently hardcodedextra_body={"agent_reference": ...}internally — the old initial-preview pattern that silently failed against the refreshed preview ("Function failed." on tool calls). As of MAF 1.8.0,FoundryAgenthas been rehabilitated:__init__takesproject_endpoint+agent_name+agent_versiondirectly andextra_bodyis only available as a user opt-in viadefault_options. The 1.1.1-era prohibition no longer applies; useFoundryAgentfreely under MAF ≥ 1.8.0. The client-swap pattern above remains the recommended path for sub-agent delegation in this skill's container runtime (it composes cleanly withFoundryChatClient's async surface);FoundryAgentis most useful for caller-side orchestration code (see § FoundryAgent timeout parameter (MAF 1.8.0)). Exact version of rehabilitation between 1.1.1 and 1.8.0 not determined.
MCP Tools via FoundryChatClient
🚫 DO NOT and DO — URL and Environment Variable Requirements:
- DO NOT ship
${ENV_VAR}placeholders inurlparameters unless they are validated upstream to expand to a non-empty value. Empty expansion producesinvalid_payloaderrors at runtime.- DO reject any URL that is not
http://orhttps://before callingget_mcp_tool(url=...). Guard withif not url or not url.startswith(("http://", "https://"))to skip gracefully when the MCP server is unreachable or unconfigured.
🟡 Status: bug-009/014 FIXED in
agent-framework-core1.3.0 (released 2026-05-07 via PR #5581, merged 2026-04-30). The fix prefers plain string entries, then.textattribute, thenentry["text"]forMappingentries, thenjson.dumps(output, default=str)final fallback — sidestepping thestr()fallback that produced[<Content object at 0x...>]Python reprs from the canonical MCP raw-JSON shape. Companion fix PR #5687 (also in 1.3.0) stopsMCPStreamableHTTPToolfrom swallowingasyncio.CancelledErrorwhen the MCP server is unreachable.Both surfaces are now valid in 1.3.0+:
client.get_mcp_tool()— concise, hosted MCP shape, STATICheaders: dict[str, str]only (noheader_providercallback — bearer tokens expire after ~1h, so this path is not viable for AAD-bearer-authenticated MCP servers like AI Search KB MCP unless headers can be pinned for the agent's lifetime; fine for API-key auth or unauthenticated MCP)MCPStreamableHTTPTool + parse_tool_results— verbose, client-side, supportsheader_provider: Callable[[dict], dict[str, str]]for per-call token refresh — REQUIRED for AAD-bearer authChoose
MCPStreamableHTTPToolwhenever the MCP server uses short-lived bearer tokens or you need per-request header logic; chooseclient.get_mcp_tool()for static-header MCP servers when you want the hosted MCP execution model.Pre-1.3.0 versions (1.1.x, 1.2.x) still have the bug. If pinned to an older release, stay on
MCPStreamableHTTPTool + parse_tool_results.
# ✅ OK in 1.3.0+ (was buggy in 1.1.x/1.2.x); STATIC headers only
mcp_tool = client.get_mcp_tool(
name="my-mcp",
url="https://my-mcp-server.azurecontainerapps.io/mcp",
headers={"Authorization": f"Bearer {long_lived_or_api_key}"},
approval_mode="never_require",
)
agent = Agent(client=client, tools=[my_tool, mcp_tool], ...)URL must be a valid URI (starts with
http://orhttps://). Unresolved${ENV_VAR}placeholders that expand to empty strings causeinvalid_payloaderrors at runtime.
MCP Tools — recommended pattern (MCPStreamableHTTPTool + parse_tool_results)
Use MCPStreamableHTTPTool directly with a custom parse_tool_results
callback that extracts the TextContent.text payload from the MCP
CallToolResult and surfaces it to the model as plain JSON. This
sidesteps FoundryChatClient.get_mcp_tool() entirely and avoids the
[<Content object>] repr leak.
Worked example — validated in recent pilots, running on a
10-tool MCP server with gpt-5.4-mini:
import json
from agent_framework import Agent, MCPStreamableHTTPTool, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import DefaultAzureCredential
def _mcp_text_extractor(result):
"""Convert an MCP CallToolResult into the plain JSON string the model expects.
Without this callback, agent_framework's default rendering of MCP results
leaks the Python repr of `[<Content object>]` into the model's view, which
gpt-5.4-mini reads as a tool failure. We surface the first TextContent
payload (which the MCP server returns as `json.dumps(...)`) verbatim.
"""
if getattr(result, "isError", False):
return json.dumps({
"error": "mcp_tool_error",
"content": [str(c) for c in (result.content or [])],
})
for c in result.content or []:
text = getattr(c, "text", None)
if isinstance(text, str) and text:
return text
sc = getattr(result, "structuredContent", None)
if sc is not None:
return json.dumps(sc, default=str)
return json.dumps({"_empty": True})
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
mcp_tool = MCPStreamableHTTPTool(
name="<process>_mcp",
url=f"https://{os.environ['MCP_SERVER_FQDN']}/mcp",
approval_mode="never_require",
parse_tool_results=_mcp_text_extractor, # ⚠️ THE FIX — opts out of default rendering
request_timeout=60,
)
agent = Agent(client=client, tools=[my_local_tool, mcp_tool], ...)Why this works:
MCPStreamableHTTPToolis the lower-level MAF primitive thatclient.get_mcp_tool()wraps — bypassing the wrapper avoids the buggy renderer.parse_tool_resultsis invoked by MAF on every tool result before it's rendered into the model's history; returning a plain string makes that string what the model sees verbatim.- The MCP server should return
json.dumps(...)from each tool handler (FastMCP wraps it as a singleTextContent); the extractor unwraps that back to the JSON string.
When pure-compute logic doesn't need to be on the MCP server,
inline it as @tool. The MCP-vs-@tool rule from recent pilots:
- MCP server for any tool with I/O (DB lookups, file reads, external API calls).
- Inline
@toolfor pure computation (date math, formatting, validation) — cheaper round-trip, no HTTP hop.
Probe to verify the fix is in place — dump the raw
custom_tool_call_output from a real Foundry trace; the output
field should be a plain JSON string (e.g. {"case_id":"...","status":"..."}),
NOT [<agent_framework._types.Content object at 0x...>]. Your
process repo should ship tests/probe_mcp_output.py for this.
Status note. As of
agent-framework-core1.3.0 (2026-05-07, PR #5581),client.get_mcp_tool()is also bug-free — but only supports STATICheaders(no per-call refresh callback), so it cannot be used for AAD-bearer-authenticated MCP servers like AI Search KB MCP. TheMCPStreamableHTTPTool + parse_tool_resultspattern remains the canonical choice for any MCP server that needs short-lived tokens viaheader_provider. Captured from recent PoC retrospectives.
MCP with per-call AAD bearer (header_provider)
These credential-based patterns do not establish the Playground user's delegated identity. The foundry-mcp-auth candidate owns the OAuth composition through the upstream Toolbox wrapper, including its unresolved late-consent and direct-MCP caller-context limits.
⚠️ Bootstrap caveat (CRITICAL — read first).
header_provider=only coverscall_tool()requests, NOT the MCP bootstrap exchange (initialize+tools/listissued by_ensure_connected). For AAD-secured MCP endpoints that require auth on bootstrap (Azure AI Search Knowledge Base MCP, anything behind PMI),header_provider=fails with 401 BEFORE the firstcall_toolever fires — the_mcp_call_headersContextVar thatheader_providerwrites to is only set insidecall_tool()(agent_framework/_mcp.py~line 1589). On hosted agents the symptom is every Responses request returningserver_errorwith no useful log signal. Usehttpx.AsyncClient(auth=httpx.Auth)viahttp_client=instead (companion example below) — seefoundry-iqSKILL § "KB access from a hosted MAF agent — three routes" → Route B for the canonical pattern.
For MCP servers backed by Azure services that authenticate with
Microsoft Entra ID where bootstrap does NOT require auth (e.g.
some custom MCP servers that auth-gate per-tool but allow open
discovery), use the header_provider callback to mint a fresh
bearer token per tool invocation. Per the SDK's own docstring, the
callback "receives the runtime keyword arguments" — a generic
dict[str, Any] populated by MAF at tool-invoke time — and returns
a dict[str, str] of HTTP headers attached to every outbound request
during that tool call.
import os
from agent_framework import MCPStreamableHTTPTool
from azure.identity import DefaultAzureCredential
# Module-level singleton so the in-memory token cache is reused across
# MCP calls; get_token() returns a cached token if it's valid for >= ~5 min
_credential: DefaultAzureCredential | None = None
def _get_credential() -> DefaultAzureCredential:
global _credential
if _credential is None:
_credential = DefaultAzureCredential()
return _credential
def _bearer_headers(_kwargs: dict) -> dict[str, str]:
"""MAF header_provider — mints a fresh AAD bearer per MCP call.
Cached by the credential layer; refreshed automatically on expiry.
"""
tok = _get_credential().get_token("https://search.azure.com/.default")
return {"Authorization": f"Bearer {tok.token}"}
kb_mcp = MCPStreamableHTTPTool(
name="my_kb_mcp",
url=f"{os.environ['AI_SEARCH_ENDPOINT']}/knowledgebases/<kb-name>/mcp"
f"?api-version=2025-11-01-preview",
approval_mode="never_require",
parse_tool_results=_mcp_text_extractor, # same extractor as above
header_provider=_bearer_headers, # 🔑 per-call refresh
request_timeout=60,
)Companion: httpx.Auth for AAD-bootstrap-required MCPs (CANONICAL)
When the MCP endpoint authenticates initialize / tools/list (any
Azure AI Search KB MCP, any PMI-protected MCP), supply auth at the
httpx transport layer instead — httpx.Auth.auth_flow() is invoked
on EVERY outbound request including bootstrap, so the agent's MCP
handshake completes successfully:
import os, httpx
from agent_framework import MCPStreamableHTTPTool
from azure.identity import DefaultAzureCredential
_credential = DefaultAzureCredential()
class _AADBearerAuth(httpx.Auth):
"""Mints AAD bearer per request — covers MCP bootstrap, not just call_tool."""
def __init__(self, scope: str) -> None:
self.scope = scope
def auth_flow(self, request: httpx.Request):
token = _credential.get_token(self.scope).token
request.headers["Authorization"] = f"Bearer {token}"
yield request
_http = httpx.AsyncClient(
auth=_AADBearerAuth("https://search.azure.com/.default"),
timeout=120.0,
)
kb_mcp = MCPStreamableHTTPTool(
name="my_kb_mcp",
url=(f"{os.environ['AI_SEARCH_ENDPOINT']}/knowledgebases/<kb-name>/mcp"
f"?api-version=2025-11-01-preview"),
http_client=_http, # 🔑 auth covers BOOTSTRAP, not just call_tool
parse_tool_results=_mcp_text_extractor,
load_prompts=False, # avoid prompts/list 500s on KB MCP
request_timeout=120,
)Do NOT combine
header_provider=ANDhttp_client=(with auth). Pick one.httpx.Authis canonical for AAD-secured Azure MCP endpoints;header_provider=is appropriate only for tool-call-only headers (e.g. per-call correlation IDs that don't need to cover bootstrap).
⚠️ MCP
pingtrap on Foundry-hosted MCP servers. MAF'sMCPStreamableHTTPTool._ensure_connected()issues an MCPpingrequest during agent registration. The Foundry Toolbox MCP endpoint is documented to return HTTP 500 onpingbecause the server doesn't implement the optional MCPpingmethod (per the Toolbox docs). The same failure mode has been observed on direct KB MCP wiring (/knowledgebases/<n>/mcp) but is not yet documented upstream — treat as suspected, not confirmed, until you've reproduced it on your tenant. When the trap fires, agent registration fails and every invoke returnsserver_errorfrom the responses endpoint with no useful log signal. Workarounds:(a) Recommended (MAF 1.6.0+): set
_ping_available = Falsein__init__. MAF 1.6.0's base_ensure_connected()already respects a_ping_availableflag (setTrueby default inMCPStreamableHTTPTool.__init__). Setting it toFalsemakes the base skipsend_ping()without overriding_ensure_connectedat all — cleaner and survives internal attribute renames across MAF versions (e.g._client→clientin 1.6.0 broke overrides that referencedself._client):class _PingSkipMCPTool(MCPStreamableHTTPTool): """Skip MCP ping — KB MCP / Toolbox MCP return 500 on ping.""" def __init__(self, **kwargs): super().__init__(**kwargs) self._ping_available = False(b) Legacy (pre-1.6.0): override
_ensure_connectedwith a no-op subclass to skip the ping entirely. ⚠️ This pattern broke in MAF 1.6.0 whenself._clientwas renamed toself.client— if you reference internal attributes in the override, the agent crashes withAttributeErroron the first request (container starts fine, readiness passes, but every actual invoke returns emptyoutput: []/model: ""). Prefer option (a) on 1.6.0+.(c) Use the static-headers
client.get_mcp_tool()shape if your MCP doesn't need per-call AAD refresh.
File Generation (@tool pattern)
Agents can generate downloadable files (XLSX, PDF, CSV, HTML) using a custom @tool.
Files are written to $HOME inside the container; the hosting platform exposes them
via the session files API, and the Teams bot delivers them via FileConsentCard
(see foundry-teams-bot skill).
@tool(approval_mode="never_require")
def save_report(
filename: Annotated[str, Field(description="Filename (e.g. report.xlsx, summary.pdf)")],
content: Annotated[str, Field(description="File content: CSV text, JSON for XLSX, HTML for pdf")],
format: Annotated[str, Field(description="Format: csv, xlsx, html, or pdf")] = "csv",
) -> str:
"""Save a report file that the user can download."""
from pathlib import Path
home = Path.home()
filepath = home / filename
if format == "xlsx":
import openpyxl
import json as _json
data = _json.loads(content)
wb = openpyxl.Workbook()
ws = wb.active
if data and isinstance(data, list):
ws.append(list(data[0].keys()))
for row in data:
ws.append(list(row.values()))
wb.save(str(filepath))
elif format == "pdf":
from fpdf import FPDF
import re
# Strip CSS but keep structural HTML for write_html()
cleaned = re.sub(r"<style[^>]*>.*?</style>", "", content, flags=re.DOTALL)
cleaned = re.sub(r'\s+(class|style|id)="[^"]*"', "", cleaned)
cleaned = cleaned.replace("<table", '<table border="1" width="100%"')
cleaned = cleaned.replace("<th", '<th bgcolor="#d0d0d0"')
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", "", 10)
pdf.write_html(cleaned)
pdf.output(str(filepath))
else:
filepath.write_text(content, encoding="utf-8")
return f"Report saved: {filename} ({filepath.stat().st_size:,} bytes). User can download it."Required dependencies in pyproject.toml:
dependencies = [
# ... agent-framework packages ...
"openpyxl>=3.1.0", # XLSX generation
"fpdf2>=2.8.0", # PDF generation (pure Python, no system deps)
]Instructions directive — add to copilot-instructions.md:
## Report Generation
You have a `save_report` tool for generating downloadable files:
- **XLSX**: JSON array of objects → `save_report(filename="report.xlsx", content=json_data, format="xlsx")`
- **PDF**: HTML content → `save_report(filename="report.pdf", content=html, format="pdf")`
- **CSV**: Raw CSV text → `save_report(filename="report.csv", content=csv, format="csv")`
- **HTML**: HTML content → `save_report(filename="report.html", content=html, format="html")`Key points:
fpdf2is pure Python — no Playwright, wkhtmltopdf, or headless Chrome neededfpdf2.write_html()renders HTML tables with borders and header highlighting- Files written to
Path.home()are automatically available via the session files API - The bot captures
agent_session_idfrom theresponse.completedevent to locate files - Wrap each format in try/except to return a clean error if generation fails
Validated — XLSX and PDF file delivery tested end-to-end in recent file-delivery pilots. FileConsentCard renders clickable OneDrive cards in Teams.
Dependencies (pyproject.toml)
🔴 DO NOT install the
agent-frameworkmeta-package with>=or let this stack float transitively. Pin the direct consumer set:agent-framework-core~=1.14.0 agent-framework-foundry~=1.11.0 agent-framework-foundry-hosting==1.0.0b260813 azure-ai-agentserver-core==2.1.0b1 azure-ai-agentserver-responses==2.1.0b1 azure-ai-agentserver-invocations==1.1.0b1 azure-ai-projects~=2.3.0 azure-identity~=1.25.3 mcp~=1.29.0 python-dotenv~=1.2.2
agent-framework-foundry~=1.11.0requiresazure-ai-projects>=2.2,<2.4, so this hosted-agent stack deliberately pinsazure-ai-projects~=2.3.0as a direct dependency. Do not independently bump one package. The hosting package is the current beta pre-release and stays pinned exact at==1.0.0b260813; MCP remains on~=1.29.0because hosting still requiresmcp<2. This August release train moves Agent Server Responses/Core2.1.0b1and Agent Server Invocations1.1.0b1together. Pin all three Agent Server prereleases directly and exactly: uv does not admit those transitive prereleases underprerelease = "if-necessary-or-explicit"unless the root project names them. Do not globally switch uv toprerelease = "allow"; that can pull unrelated prereleases inside otherwise bounded dependency windows.
🚨 READ FIRST. Five pyproject mistakes silently break this stack:
agent-framework>=1.14.0meta-package — non-deterministic transitive resolution; resolves differently across uv versions. DO pinagent-framework-coreandagent-framework-foundryindividually.- Transitive Agent Server betas — hosting
b260813declares lower bounds on Agent Server core/responses>=2.1.0b1and invocations>=1.1.0b1, but uv's safeif-necessary-or-explicitpolicy does not treat those transitive markers as explicit. Pinazure-ai-agentserver-core==2.1.0b1,azure-ai-agentserver-responses==2.1.0b1, andazure-ai-agentserver-invocations==1.1.0b1directly oruv syncreports that only older stable releases are available.agent-framework-core[mcp]extra — that extra does NOT exist.MCPStreamableHTTPTool/MCPSseTool/MCPStdioToolare top-level exports ofagent_framework; the bareagent-framework-core~=1.14.0pin already includes them. Writing[mcp]produces a uv warning but does NOT fail resolution, so the pyproject can ship looking "MCP-ready" while operators chase phantom problems.- Floating or transitive
azure-ai-projects—agent-framework-foundry~=1.11.0requiresazure-ai-projects>=2.2,<2.4, and this skill depends on the stableproject.agents.update_detailssurface available in~=2.3.0. Pinazure-ai-projects~=2.3.0directly in every hosted-agentpyproject.toml; do not bump it independently of the Foundry package.- Missing
mcp—agent_framework_foundry_hosting._responsesimportsfrom mcp import McpErrorat module-load time, so the container crashes at startup withModuleNotFoundError: No module named 'mcp'even when no MCP tool is wired. The platform surfaces this assession_not_readyafter a ~60 s timeout, so diagnosis cost is high. Pinmcp~=1.29.0in every hosted-agentpyproject.tomlbecause the current beta hosting package still requiresmcp<2.
MUST: Copy verbatim from
references/python/pyproject.toml. Do NOT redefine inline — the validator enforces single-source-of-truth. That file pinsagent-framework-core~=1.14.0,agent-framework-foundry~=1.11.0, the beta hosting at exact==1.0.0b260813, Agent Server core/responses==2.1.0b1, Agent Server invocations==1.1.0b1, directazure-ai-projects~=2.3.0(because Foundry 1.11.0 requires>=2.2,<2.4),azure-identity~=1.25.3,mcp~=1.29.0(mandatory — hosting still requiresmcp<2), andpython-dotenv~=1.2.2. The header comment captures the five pitfalls (meta-package, transitive Agent Server betas, phantom[mcp]extra, floatingazure-ai-projects, missingmcp) the file prevents.
Do NOT use agent-framework>=1.14.0 as a meta-package. The meta-package's transitive
resolution is non-deterministic across uv versions. Pin agent-framework-core~=1.14.0,
agent-framework-foundry~=1.11.0, and direct azure-ai-projects~=2.3.0 instead. Do not
independently bump the Foundry and Projects packages: agent-framework-foundry~=1.11.0
requires azure-ai-projects>=2.2,<2.4, and this skill uses the stable 2.3 line for
project.agents.update_details. Pin the beta hosting package by exact version
==1.0.0b260813 — pre-release cap math doesn't survive across beta boundaries, so ~=
would silently jump to a later beta (this is a recurring policy bug in this skill's own
pin-validation script — see references/upstream-pin.md
for the fix).
Simplified deps. The hosting package bundles microsoft-opentelemetry
which transitively pulls ALL OTel instrumentors (openai-v2, agents-v2, httpx, logging,
fastapi, etc.) and the Azure Monitor exporter. Remove any explicit
azure-monitor-opentelemetry, opentelemetry-sdk, opentelemetry-instrumentation-*
lines from your pyproject — they are now transitive and declaring them explicitly causes
version conflicts.
Mandatory adjacent rules (lessons from dependency-resolution retrospectives):
- Add exact Agent Server prerelease pins for core/responses
==2.1.0b1and invocations==1.1.0b1. Hosting declares compatible lower bounds but uv does not activate transitive prereleases underif-necessary-or-explicit; the exact direct pins both enable resolution and prevent cross-beta drift. - Add explicit direct
azure-ai-projects~=2.3.0— mandatory, not transitive.agent-framework-foundry~=1.11.0requiresazure-ai-projects>=2.2,<2.4, and this stack uses the stable 2.3 line forproject.agents.update_details. Do not let it float via transitive resolution or bump it independently of the Foundry package. - Add explicit
mcp~=1.29.0— mandatory, not conditional.agent_framework_foundry_hosting._responsesimportsfrom mcp import McpErrorunconditionally (module-level), so the container crashes at startup withModuleNotFoundError: No module named 'mcp'even when the agent uses no MCP tools. The platform surfaces this assession_not_readyafter a ~60 s timeout (not as an import error), so the diagnosis cost is high — pinmcpin every hosted-agentpyproject.toml. Verified onagent-framework-foundry-hosting==1.0.0b260813; the current beta hosting package still requiresmcp<2. - Do NOT write
agent-framework-core[mcp]. The[mcp]extra does NOT exist inagent-framework-core(PEP 503 /setup.cfgof the published wheel has no[project.optional-dependencies] mcp = […]entry).MCPStreamableHTTPTool/MCPSseTool/MCPStdioToolare top-level exports ofagent_framework— pinagent-framework-core~=1.14.0(plain, no extras) and import them withfrom agent_framework import MCPStreamableHTTPTool. Writing the non-existent extra produces a uv warning but does not fail resolution, so a pyproject can ship looking "MCP-ready" while actually missing nothing (the transports are already there) — but the warning suggests something's wrong and operators chase the wrong thing. - Include
[tool.setuptools] packages = []for clean uv resolution.
prerelease = "if-necessary-or-explicit" is correct — packages with explicit
prerelease markers (e.g. ==1.0.0b260813) resolve to prereleases; everything else
stays GA. Do NOT use "allow" — it can pull an unpinned beta azure-identity.
Dependency Chain (verified on PyPI)
| Package | Version | Type | Pulls in |
agent-framework-core | 1.14.0 | ✅ Stable | pydantic, opentelemetry-api (instrumentation enabled by default) |
agent-framework-foundry | 1.11.0 | ✅ Stable | core, openai, azure-ai-projects (>=2.2,<2.4) |
agent-framework-foundry-hosting | 1.0.0b260813 | ⚠️ Beta (pinned exact) | Requires the Agent Server beta train below |
azure-ai-agentserver-core | 2.1.0b1 | ⚠️ Beta (pinned exact) | Direct pin required for uv prerelease admission; pulls microsoft-opentelemetry (bundles all OTel instrumentors + exporters) |
azure-ai-agentserver-responses | 2.1.0b1 | ⚠️ Beta (pinned exact) | Direct pin required for the hosted Responses protocol |
azure-ai-agentserver-invocations | 1.1.0b1 | ⚠️ Beta (pinned exact) | Coherent with the hosting beta's Agent Server contract |
azure-ai-projects | 2.3.0 | ✅ Stable (direct pin) | Stable project.agents.update_details; deliberately held below 2.4 to stay aligned with agent-framework-foundry 1.11.0 |
mcp | 1.29.0 | ✅ Stable | Required by every hosted agent — agent_framework_foundry_hosting._responses imports from mcp import McpError unconditionally, even when no MCP tools are used. Hosting still requires mcp<2 |
azure-identity | 1.25.3 | ✅ Stable | |
python-dotenv | 1.2.2 | ✅ Stable | .env parity for local/container env loading |
No override-dependencies needed — the direct exact prerelease pins are compatible
with the hosting package's declared bounds.
Dockerfile
MUST: Copy verbatim from
references/docker/Dockerfile. Do NOT redefine inline — the validator enforces single-source-of-truth. That file is the canonical, field-validated container image for a hosted agent (uv-based dependency sync, port 8088, remote-buildable viaazd deploy).
- Port 8088 is the standard Foundry agent port
--platform linux/amd64only needed for local buildsazd deploybuilds remotely in Azure Container Registry by default — no local Docker needed. Useazd ai agent runwhen you intentionally want to build and run the agent locally.
azure.yaml (unified hosted-agent configuration)
For the selected profile, a single azure.yaml at the project root is the source of
truth for both the Foundry project/model deployment and the hosted
agent itself — declared as a graph of services, each with a host
field and a uses list of dependencies.
MUST: Copy the complete configuration verbatim from
references/yaml/azure.yaml. Do not redefine it inline; that file is the canonical unifiedhost: azure.ai.agentconfiguration used by the fixture.
Critical rules
| Rule | Why |
kind: hosted on the azure.ai.agent service | Marks it as a containerized/source-built hosted agent (as opposed to a Foundry Agent Service prompt agent) |
Protocol version 2.0.0 | Current GA protocol version. "v1" and "1.0.0" are historical preview values from before the two-file contract was retired — see § Migration from Initial Preview |
container.resources nested object | NOT a top-level resources: list |
NO FOUNDRY_PROJECT_ENDPOINT in environmentVariables | Reserved — the platform injects it automatically, along with FOUNDRY_PROJECT_ARM_ID, FOUNDRY_AGENT_NAME, FOUNDRY_AGENT_VERSION, FOUNDRY_AGENT_SESSION_ID, and APPLICATIONINSIGHTS_CONNECTION_STRING. Declaring any of them yourself is redundant at best and risks shadowing the platform value |
uses: [ai-project] on the agent service | Forms the dependency graph azd resolves at provision/deploy time — the agent can't reference the project's model deployment without it |
Connect to an existing project via endpoint: | Set services.ai-project.endpoint from ${FOUNDRY_PROJECT_ENDPOINT} to reuse a pre-provisioned Foundry project instead of having azd provision a new one; omit deployments: you don't want azd to manage |
infra.provider: microsoft.foundry | Selects the Foundry extension's bicep-less provisioning provider for the unified services graph; it does not rename or replace the azure.ai.agents extension |
One schema, no scaffold-time mustache. Unlike the old two-file contract, there is no separate
{{VAR}}-templated companion file.${VAR_NAME}inazure.yamlresolves client-side from yourazdenvironment (.azure/<env>/.env) atazd provision/deploytime;${{ connections.<name>.<path> }}resolves server-side, at sandbox start, from a Foundry project connection (e.g. secrets) — see theazure.yamlreference for the full field list, includingazure.ai.connectionandazure.ai.toolboxservices this skill doesn't otherwise cover.
Foundry provider is bicep-less.
infra.provider: microsoft.foundryselects the Foundry extension's provisioning provider, which synthesizes infrastructure from theservicesgraph atazd provisiontime; there is no localinfra/directory. If you explicitly eject infrastructure withazd ai agent init --infraor--infra=terraform, the generated project switches the provider tobiceporterraformand uses those local files instead.
Guided init vs. direct-copy brownfield deploy
The recommended guided path adopts the manifest and wires the existing project/registry context into the active azd environment:
PROJECT_ID="/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/projects/<project>"
azd ai agent init -m ./azure.yaml \
--project-id "$PROJECT_ID" \
--deploy-mode containerIf you instead copy the canonical files into an existing repository and run
azd deploy directly, azure.yaml alone is insufficient. The
azure.ai.agents service target requires the full project ARM ID, project
endpoint, subscription, and bare ACR login server in the active azd
environment:
azd env set AZURE_SUBSCRIPTION_ID "<sub-id>"
azd env set FOUNDRY_PROJECT_ENDPOINT \
"https://<account>.services.ai.azure.com/api/projects/<project>"
azd env set AZURE_AI_PROJECT_ID \
"/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/projects/<project>"
azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT "<registry>.azurecr.io"
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME "<model-deployment>"
azd deploy my-agent --no-promptAZURE_CONTAINER_REGISTRY_ENDPOINT is a bare hostname — no
https:// scheme and no trailing slash. This direct-copy limitation is
the adoption wiring covered by
Azure/azure-dev #8981:
azd ai agent init ... --deploy-mode container wires it automatically;
plain azd deploy cannot infer the omitted brownfield values.
Installing the azd Foundry extensions
azd ext install microsoft.foundrymicrosoft.foundry is a beta meta-package — it contributes no
commands of its own. Installing it pulls in every individual Foundry
extension: azure.ai.agents (azd ai agent, the one this skill uses),
azure.ai.connections, azure.ai.inspector, azure.ai.projects,
azure.ai.routines, azure.ai.skills, and azure.ai.toolboxes. It is
not a rename or replacement of azure.ai.agents — the agent
extension still owns the azd ai agent command group, the
azure.ai.agent service host value, and the microsoft.foundry
provisioning identifier azd uses internally when it synthesizes
infrastructure. Installing just azure.ai.agents also pulls in
azure.ai.inspector (a dependency), so it's a valid narrower
alternative to the meta-package:
# Just the agent surface (also pulls in azure.ai.inspector)
azd ext install azure.ai.agents
# Upgrade later
azd ext upgrade microsoft.foundryVerify what's installed:
azd ext list --output jsonModel Version Lookup
| Model | Version |
gpt-5.6-sol | 2026-07-09 |
gpt-5.6-terra | 2026-07-09 |
gpt-5.6-luna | 2026-07-09 |
gpt-5.4 | 2026-03-05 |
gpt-5.4-mini | 2026-03-17 |
gpt-5.4-nano | 2026-03-17 |
gpt-5.3-codex | 2026-02-24 |
gpt-5.2 | 2025-12-11 |
gpt-5 | 2025-08-07 |
gpt-5-mini | 2025-08-07 |
gpt-4.1 | 2025-04-14 |
gpt-4.1-mini | 2025-04-14 |
GPT-5.6 GA boundary (verified 2026-07-15). Microsoft publishes
gpt-5.6-sol,gpt-5.6-terra, andgpt-5.6-lunaas three distinct model IDs at version2026-07-09. All three are GA and have a documented retirement date of2027-07-09. They share a 1,050,000-token context envelope (922,000 input + 128,000 output), accept text and image input, produce text output, and support both the Responses API and Chat Completions API.gpt-5.6andgpt-5.6-miniare not documented Azure model IDs - do not invent either alias. Sources: model catalog and retirement schedule.
Verify with: az cognitiveservices account list-models --resource-group <rg> --name <account> -o table
Which model/version/capacity/SKU to put in the block above →
references/model-selection.md.
azd env variables for the azure.ai.agents extension
azd ai agent init is bicep-less by default — it doesn't write an
infra/ directory; azd synthesizes infrastructure from the
services graph in azure.yaml at azd provision time. Guided init
writes the brownfield values below. A direct-copy deploy must set them
explicitly before azd deploy:
| Env var | Set via | Format | Why it matters |
AZURE_SUBSCRIPTION_ID | azd env set or guided init | subscription GUID | Selects the subscription containing the existing project and registry |
FOUNDRY_PROJECT_ENDPOINT | azd env set or guided init | https://<account>.services.ai.azure.com/api/projects/<project> | Deploy-time project lookup for direct-copy brownfield deploy; also injected by Foundry into the running container, but MUST NOT be listed under environmentVariables |
AZURE_AI_PROJECT_ID | azd env set or guided init | /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/projects/<project> | Full ARM ID required by ensureFoundryProject; an endpoint alone is insufficient |
AZURE_CONTAINER_REGISTRY_ENDPOINT | azd env set or guided init | <registry>.azurecr.io | Bare ACR login server used for container build/push; no scheme or trailing slash |
AZURE_AI_MODEL_DEPLOYMENT_NAME | azd ai agent init records it automatically from the deployment you select; change later with azd env set | deployment name (e.g. gpt-5.4-mini) | Projected through the agent service's environmentVariables entry in azure.yaml, and read by container.py / main.py as os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] |
azd provision writes these into .azure/<env>/.env. If you eject
Bicep (azd ai agent init --infra), the generated main.bicep exposes
the equivalent outputs and you rarely need to hand-wire them.
Deploy-time azd env vs. runtime container env.
FOUNDRY_PROJECT_ENDPOINTMUST be set in the active azd environment for direct-copy brownfield deploy, but MUST NOT be projected through the agent'senvironmentVariableslist. Foundry injects it into the running container. The same container-only prohibition applies toFOUNDRY_PROJECT_ARM_ID,FOUNDRY_AGENT_NAME,FOUNDRY_AGENT_VERSION,FOUNDRY_AGENT_SESSION_ID, andAPPLICATIONINSIGHTS_CONNECTION_STRING.
Identity & RBAC
Agent Identities
Each hosted agent gets a dedicated Microsoft Entra agent identity
(a service principal) at deploy time — this is what your running
container uses to call models and tools. azd, the VS Code Foundry
Toolkit, or the SDK/REST create it automatically; you don't configure
managed identities by hand. View it with azd ai agent show.
⚠️ ServiceIdentity Cosmos limitation. The agent identity's principal type is not accepted by every data-plane RBAC engine — Cosmos DB in particular rejects direct role assignments against it with
unsupported type [Unfamiliar]. The same has been seen against Azure AI Search data-plane in some configurations. Workaround: route Cosmos / Search access through an MCP server backed by a separate User-Assigned Managed Identity (UAMI). Grant the data-plane role to that UAMI, give the agent only the MCP tool; the agent never touches the data plane directly.
Per-instance identity for Graph API. When a hosted agent needs its own audit-distinct identity — calling Microsoft Graph (Calendar, Mail, Teams), performing on-behalf-of (OBO) flows, or operating cross-tenant — the standard project-scoped agent identity is not enough. The official
entra-agent-idskill documents thefmi_pathtoken-exchange pattern, app-registration setup, and theai.azure.comtoken-scope requirement.
Default case: no agent role assignment needed
Per the Hosted agent permissions reference: "The agent identity can access model inferencing through the project endpoint and session storage by default." Implicit access covers:
- Model inferencing through the project endpoint (
FoundryChatClient(project_endpoint=..., credential=DefaultAzureCredential())— the standard pattern in this skill) - Session storage read and write
No explicit role assignment or additional configuration is needed
for the standard case documented in § Runtime Pattern.
There is no postdeploy RBAC-grant hook to run, and no canonical
account-scope Foundry User grant for the agent identity in this
skill's default path — both were removed as part of the GA migration.
If you were relying on references/bash/postdeploy-agent.sh or a
manual Foundry User grant to the agent's instance_identity /
blueprint.principal_id from a pre-GA build of this skill, delete
that step; it's not just unnecessary now, it's dead weight.
Deploying user still needs a role to create/update the agent:
| Role | Scope | Why |
Foundry Project Manager | Foundry project | Data-plane permissions to create/update agents, plus the ability to create role assignments for the agent identity if an advanced scenario needs one |
azd and the VS Code Foundry Toolkit handle the ACR pull role
(Container Registry Repository Reader on the project's managed
identity) automatically as part of azd deploy.
Agent access beyond defaults (advanced scenarios only)
Additional permissions are only needed when an agent uses connected tools that access external resources, references data outside its own project, or calls account-level capabilities directly (bypassing the project endpoint). Grant the least-privilege role for the specific capability actually called — don't reach for a broad role by default:
| Scenario | Role | Scope | Notes |
| Agent calls the account-level OpenAI endpoint directly (bypassing the project endpoint) | Cognitive Services OpenAI User | Foundry account | Covers only OpenAI data actions — narrower than Foundry User |
| Agent calls account-level non-OpenAI capabilities directly (Speech, Content Safety, Vision, Document Intelligence, Language, Translator) | Cognitive Services User | Foundry account | Covers those capabilities without granting OpenAI/agents access |
| Agent needs both of the above with a single grant | Foundry User | Foundry account | Broadest of the three — use only if the agent genuinely needs both OpenAI and non-OpenAI account-level capabilities |
| Custom project-level access beyond the implicit default | Custom role scoped to Microsoft.CognitiveServices/accounts/AIServices/responses/* + agents/*/read + agents/*/write | Foundry project | Lower privilege than the built-in Foundry User role for the project-level inferencing case; requires Microsoft.Authorization/roleAssignments/write at the project scope to create |
Because a role assignment against the agent identity can't be created until after the agent exists, the user/principal that creates the agent also needs permission to assign roles.
Foundry Project Managerat the project scope is the recommended assignment for agent creators in this scenario — it includes both the data-plane permissions and the ability to assign theFoundry Userrole.
Deployment Flow
azd up
├── provision → synthesize infra from azure.yaml services (Foundry
│ project + model deployment, ACR, monitoring) — bicep-less by
│ default; use ejected Bicep/Terraform if you materialized infra
├── postprovision hooks (if any)
├── azd ai agent extension →
│ ├── Build container remotely via ACR (default)
│ ├── Push image + create hosted agent version
│ └── Agent gets a dedicated Entra identity with implicit access
│ to model inferencing + session storage — no RBAC grant step
└── deploy other services (bot, MCP servers, etc.)Invocation
Persist the original operation before dispatch and its returned identity/status before interpreting content. The short example below omits custody plumbing; use Operation recovery for executable consumers with tools or remote effects. A failed response does not authorize another invoke.
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
project = AIProjectClient(
endpoint="<project_endpoint>",
credential=DefaultAzureCredential(),
)
oai = project.get_openai_client(agent_name="my-agent")
response = oai.responses.create(input="Hello!", stream=False)
print(response.output_text)allow_preview=True is not required for this call (see the
Multi-Agent § note above for the SDK-level detail). Or via CLI:
azd ai agent invoke "Hello!". Or via REST:
POST {project_endpoint}/agents/{name}/endpoint/protocols/openai/responses?api-version=v1Compute Lifecycle
- Automatic — no manual start/stop
- Provisions on first request
- Deprovisions after 15 minutes of inactivity
- No replica management
Container status API. The
.../versions/{v}/containers/defaultendpoint (and the:startaction) is not a supported public status surface for hosted agents — the platform auto-provisions containers. If your deploy or eval script polls a container-status endpoint to check readiness, skip straight to a warmup chat instead. Pattern: attempt aresponses.create("ping")warmup with retry/backoff. Poll the version'sstatusfield (creating→active/failed) viaproject.agents.get_version(...)orazd ai agent showif you need a structured readiness signal.
Region Availability
Not all regions support hosted agents. If you get "The requested experience is
not available for this subscription", try a different region.
Snapshot — 2026-07-14. Per the authoritative Region availability page, Hosted agents are currently available in 20 regions: East US 2, North Central US, Sweden Central, Canada Central, Canada East, Southeast Asia, Poland Central, South Africa North, Korea Central, South India, Brazil South, West US, West US 3, Norway East, Japan East, France Central, Germany West Central, Switzerland North, Spain Central, Australia East. This list is not evergreen — Microsoft updates it as more regions come online. Re-check the live page before you commit a region; East US 2 in particular used to fail (a stale, now-outdated claim from an earlier preview build of this skill) and is on the current list.
Verify current model + hosted-agent availability together before you commit a region:
# Hosted agents + model deployability in an existing account's region
az cognitiveservices account list-models --resource-group <rg> --name <account> -o table
# Model availability in a region (pre-account planning)
az cognitiveservices model list --location <region> -o tableRegion choice interacts with model + capacity availability and data residency — see
references/model-selection.md§§ 2–4.
Gateway throttle finding (historical, pre-GA — unverified post-GA)
Status: historical. The finding below was observed during the May 2026 preview era, before container hosted-agent deploy reached GA. It has not been re-verified against the current GA gateway. Treat it as a diagnostic pattern to recognize if you see the same symptoms — not as production sizing guidance for a GA deployment. If you reproduce it post-GA, that's worth a fresh, dated finding of its own; don't assume the ~6-call boundary still applies.
The Foundry hosted-agent gateway had an undocumented preview-time
throttle: ~6 sustained sequential invocations per warm period
before sticky internal_server_error responses with a 5–10 minute
recovery window. Validated in May 2026 against a real pilot agent
across 3 successive eval runs, pre-GA.
Detection signature
Look for ALL of these together:
- Cold-start: first 1–2 warmup pings return
server_errorin 5–10s before the platform brings a replica up - Warm phase: exactly 5–6 sequential invocations answer in 30–60s each (real tool work) with normal latency
- Throttle phase: from invocation 7–9 onwards, every call returns
internal_server_error(orserver_error/model:"") in 5–10s consistent latency — too fast for real tool work, too slow for a 4xx routing failure - Recovery: 5–10 min idle restores normal operation (no manual restart, no replica scale, no env change required)
What's NOT the cause
Each lever was independently bumped in a controlled test (single pilot agent, 30-scenario eval) and the boundary did NOT move:
| Lever | Before | After | Effect on 6-call boundary |
| Container CPU | 0.25 | 1.0 | none |
| Container memory | 0.5Gi | 2Gi | none |
| Replicas | 1 (no scale) | min=1 max=3 | none |
| Model TPM | 50K | 300K | +1 answered scenario only |
| Pacing between invocations | 5s | 30s | none |
| Invocation retries | 1 | 3 | retry burst makes it WORSE |
The boundary is gateway-side, not container-side. Container restart,
replica swap, and azd deploy agent all leave the throttle window
unchanged.
Mitigation (eval / workshop posture)
- Run evals in 5-scenario batches with 5-minute cooldown between
batches. Pre-warm each batch with a throwaway invocation. See
foundry-evalsSKILL § "Warmup retry loop, not single-shot" and § "Resume-after-cooldown". - Move tool work to INGEST time where feasible — visual extraction,
document parsing, embedding generation should run in a one-shot
ACA Job or postprovision script, not inside the agent's tool budget.
See
foundry-doc-vision-speechSKILL § Pattern X for the ingest-time vision pattern that explicitly sidesteps this throttle. - Sample, don't burst for continuous-eval — see
foundry-evalsSKILL § Continuous Evaluation Loop, Plan AEvaluationRulewithevent_type=SAMPLED(notRESPONSE_COMPLETED).
Production posture (not just demo)
- Target architecture for production traffic at scale: APIM gateway in front of the hosted agent, with retry + queue semantics in the gateway (not in every client). The hosted-agent runtime itself is the bottleneck; horizontally scaling the agent container does NOT bypass the throttle.
- File a Foundry support ticket with detection-signature evidence if the throttle blocks your production sizing — preview limits are often raised on request once a pattern is documented end-to-end.
Diagnosis blocker
Confirming root cause is hard because the throttle window typically
coincides with zero AppExceptions / AppTraces reaching App
Insights — the gateway absorbs the failed requests and never emits
container-side telemetry for them. Ensure your _init_telemetry()
guard is in place (see § Troubleshooting → AppInsights row + gap O-011
in foundry-observability) BEFORE investigating; otherwise you're
flying blind.
Version rollout patterns (blue-green / canary / rollback)
Foundry hosted agents are versioned and the platform supports native
weighted traffic routing between versions — no client-side router,
custom load balancer, or APIM rewrite required. Each create_version
call produces an immutable agent version; the agent endpoint's
version_selector distributes traffic across them via FixedRatio
rules (0-100 % per version, integers summing to 100). This unlocks
three production rollout patterns from a single primitive.
Requires: Python SDK
azure-ai-projects~=2.3.0(stable, noallow_previewneeded for this surface), Azure CLI >= 2.80, andazdextensionazure.ai.agents. Traffic routing is exposed through the stableproject.agents.update_details(...)method — the oldproject.beta.agents.patch_agent_details(...)call and theFoundry-Features: AgentEndpoints=V1Previewheader it required are gone;azure-ai-projects2.3.0 does not exposepatch_agent_detailsunder.beta.agentsat all (verified:BetaAgentsOperationsonly coversAgentsOptimizationoperations).
When to use which pattern
| Pattern | Use when | Risk profile | Time-to-rollback |
| Blue-green | Image / model / role-binding change; want instant cutover with prior version warm in case of regression | Atomic; if v2 is broken, every request fails until rollback | < 30 s (one update_details call back to v1) |
| Canary | Same change classes; want gradual exposure with bounded blast radius | Bounded — typically 5-10 % see v2 first, ramp on success | < 30 s (one update_details call back to v1) at any ramp stage |
| Rollback | Production v2 misbehaving after promotion; emergency revert | Re-warm of v1 if cold; creating window only if v1 was already deleted | < 30 s (re-call) or 2-5 min (re-create v1 from prior image digest) |
The three patterns share one primitive: create v2 -> poll to
active -> update routing -> invoke -> (optional) delete prior.
Blue-green is "0 -> 100 % in one call"; canary is "10 -> 50 -> 100 %
across multiple calls"; rollback is "100 -> 0 % to the new version in
one call". Same SDK surface end to end.
The 4-step rollout primitive
- Create v2 with
project.agents.create_version(agent_name=..., definition=HostedAgentDefinition(kind="hosted", cpu=..., memory=..., environment_variables={"AZURE_AI_MODEL_DEPLOYMENT_NAME": _env("AZURE_AI_MODEL_DEPLOYMENT_NAME"), "_BUILD_TS": ...}, container_configuration=ContainerConfiguration(image=...), protocol_versions=[ProtocolVersionRecord(protocol=AgentEndpointProtocol.RESPONSES, version="2.0.0")])). Returns immediately withstatus: "creating". A new immutable version does not inherit omitted definition fields, so carry every required runtime variable forward;_BUILD_TSis additive, not a replacement. - Poll the version via
project.agents.get_version(agent_name, agent_version)untilstatus == "active"(typically 2-5 min;failedis terminal — read theerrorfield for cause). - Update routing via
project.agents.update_details(agent_name=..., agent_endpoint=AgentEndpointConfig(version_selector=VersionSelector(...), protocol_configuration=ProtocolConfiguration(responses=ResponsesProtocolConfiguration())))withversion_selection_rulessumming to 100. - Invoke the agent endpoint as usual (
responses.create(...)) — the platform routes per the rules. (Optional) delete the prior version withproject.agents.delete_version(...)ONLY after sustained success; keep it warm if you might need to roll back.
The canonical reference is
references/python/version_rollout.py
— a single script that demonstrates all three patterns end-to-end
against a real project, using the stable azure-ai-projects 2.3.0
surface. Import it from your tooling; do NOT redefine inline (per
AGENTS.md § 7 SSOT rule).
This routing example is not the deployment preflight or a business acceptance
test. Complete the shared prerequisite and evidence contract
for the selected mode first. wait_for_active requires two consecutive direct
GETs but cannot certify a native session or tool result. Do not rerun a create
after losing its ACK: reconcile frozen inputs first, and retain every version,
image, identity and signed association.
Blue-green example (atomic 0 -> 100 % cutover)
from azure.ai.projects.models import (
AgentEndpointConfig,
ProtocolConfiguration,
ResponsesProtocolConfiguration,
FixedRatioVersionSelectionRule,
VersionSelector,
)
# After create_version + wait-for-active for v2:
project.agents.update_details(
agent_name=AGENT_NAME,
agent_endpoint=AgentEndpointConfig(
version_selector=VersionSelector(
version_selection_rules=[
FixedRatioVersionSelectionRule(
agent_version="2", traffic_percentage=100
),
]
),
protocol_configuration=ProtocolConfiguration(
responses=ResponsesProtocolConfiguration()
),
),
)The cutover is atomic at the gateway — the next request after the call returns sees v2. Existing inflight requests on v1 finish on v1 (no mid-request swap).
Canary example (10 % -> 50 % -> 100 % staged promotion)
# Phase 1 - 90/10:
project.agents.update_details(
agent_name=AGENT_NAME,
agent_endpoint=AgentEndpointConfig(
version_selector=VersionSelector(
version_selection_rules=[
FixedRatioVersionSelectionRule(agent_version="1", traffic_percentage=90),
FixedRatioVersionSelectionRule(agent_version="2", traffic_percentage=10),
]
),
protocol_configuration=ProtocolConfiguration(
responses=ResponsesProtocolConfiguration()
),
),
)
# Observe gen_ai response.id mix in AppIn for hours-to-days.
# Promote phases: 50/50 then 0/100 via the same update_details shape.Traffic split percentages can be arbitrary integers summing to 100 — not fixed buckets. Choose ramps (5/95 -> 25/75 -> 50/50 -> 100/0) sized to your traffic volume and observation window. For low-QPS agents, 50/50 may be the smallest split that produces statistically meaningful v2 sample within a single business day.
Rollback (immediate revert)
If v2 misbehaves after promotion, route back to v1 at 100 % — provided v1 was NOT yet deleted:
project.agents.update_details(
agent_name=AGENT_NAME,
agent_endpoint=AgentEndpointConfig(
version_selector=VersionSelector(
version_selection_rules=[
FixedRatioVersionSelectionRule(
agent_version="1", traffic_percentage=100
),
]
),
protocol_configuration=ProtocolConfiguration(
responses=ResponsesProtocolConfiguration()
),
),
)This is < 30 s round-trip to atomic revert. If you already deleted v1, recovery requires re-creating it from the prior image digest (2-5 min cold provision + warmup) — which is why the don't delete v1 until v2 is proven discipline matters. **Keep prior version for
= 24 h** after promotion to 100 % is the safe default.
Preconditions and traps
The rollout patterns are sensitive to two existing traps documented above — these are NOT new failure modes but they become much more visible when you're moving versions around:
create_versiondedup trap — if v2 has identical environment variables, image tag, and metadata to v1, the platform silently returns v1 (no error). Your "v2 deploy" is then your v1, and the canary appears stuck at 100 % v1 even though theupdate_detailscall succeeded. Always bump a_BUILD_TSor_GIT_SHAenvironment variable on everycreate_versioncall to defeat the dedup, while also carrying required runtime variables such asAZURE_AI_MODEL_DEPLOYMENT_NAMEinto the new complete definition. The canonical reference script does both.- ACR layer-cache trap
— if you tag both versions
:latestand ACR caches a stale layer, v2 may pull v1's image bits. Pin by digest (@sha256:...) for canary windows so the gateway routes to byte-identical-on-disk versions. Tag by date for human-readable rollback handles (my-agent:rollback-20260612).
Inspection
azd ai agent show returns both the active set of versions AND the
current agent-endpoint traffic split — use it as the one-command
sanity check after every routing update:
azd ai agent show
# Versions:
# 1 active image: my-agent@sha256:abc...
# 2 active image: my-agent@sha256:def...
# Endpoint protocols: responses (2.0.0)
# Traffic routing:
# version 1: 90 %
# version 2: 10 %If the traffic split shown by azd ai agent show does not match what
you just set, you are likely hitting the dedup trap (v2 silently
became v1) — re-check azd ai agent show for whether v2 was actually
created, and inspect each version's environment_variables for a
unique build stamp.
Production posture
- The traffic-routing surface (
update_details+AgentEndpointConfig) is stable, GA SDK surface — no preview header needed. Still worth a non-prod dry run before your first production rollout of a given change class, same as any other production routing change. - Keep prior version for >= 24 h after promotion to 100 %. That's enough for instant rollback without re-provisioning. Idle versions cost nothing — see § Compute Lifecycle (auto- deprovision after 15 min idle; no replica charges in between requests).
- Never canary across the agent version AND the backing chat-model
deployment simultaneously. If you're also bumping
AZURE_AI_MODEL_DEPLOYMENT_NAMEin v2, the traffic split conflates two independent variables and the gen_ai telemetry becomes ambiguous. Split on agent version first; after promotion, do a separate model rollout. - Pair the canary phase with continuous evaluation — see
foundry-evals§ Continuous Evaluation Loop. The eval runner's per-response sampling naturally surfaces v2 quality regressions within the canary window. - ACA-side rollout primitives (revisions, ingress weighting) do NOT
apply here. Foundry hosted agents run on platform-managed compute,
not customer-managed ACA. The "split-traffic-between-revisions"
pattern in
foundry-caphost-lifecycleis a sibling primitive for a different runtime — it has no integration point with the Foundry agent-version surface. Do NOT try to wire ACA ingress weights to agent versions.
Operation recovery
MUST: use the response/effect custody contract
and canonical operation_evidence.py.
The caller supplies a private durable recorder; failure to persist intent
prevents dispatch. These standard-library primitives do not select a hosted
profile or dispatch work.
Request intent, dispatch, service completion, business effect, independent
readback and client/browser delivery are separate facts. A client correlation
ID is not a service-issued retrievable response ID.
The private oracle persists a safe HTTP envelope and response/session IDs before semantic parsing, with an optional private raw body capture. A pending version may omit its future identity; it is observed without invoking. An active version still requires the exact binding. No timeout, malformed output, generic404 or failed client assertion clears UNKNOWN or authorizes replay.
Adoption is separate from source review. Run the read-only
hosted_contract.py --references <actual-loaded-references-directory> inventory
on the reviewed source and the actual adopted directory. Compare every hash,
not just metadata.version. Record source commit, any local correction, contract
profile and installed/packaged hashes separately; no automatic mirror update.
Downstream authoring/refresh flows must stop on drift, review a contract-version
change, regenerate only the selected profile and rerun its acceptance cases.
Threadlight adoption belongs to its owner and is not completed by a merge here.
Troubleshooting
Diagnosing server_error locally
Foundry's server_error is a generic envelope that hides the actual
failure code. Don't read it as "the agent is broken" — it almost always means
a specific downstream layer failed (chat-model quota, telemetry init,
identity, MCP) and the runtime swallowed the real exception in the response
body. Always pull run.last_error first before chasing higher-level
theories — it carries the actual code and message in most cases.
Step 1 — pull run.last_error with the SYNC client (async
AIProjectClient silently fails to surface last_error on streamed
responses; use the SYNC client for this one-shot diagnostic):
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
pc = AIProjectClient(endpoint="<your-project-endpoint>",
credential=DefaultAzureCredential())
client = pc.get_openai_client(agent_name="<agent-name>")
resp = client.responses.create(input="ping")
print(resp.model_dump_json(indent=2)) # inspect `last_error.code` + `.message`Common last_error.code values seen behind server_error:
last_error.code | Real cause | Where to look next |
rate_limit_exceeded | Backing chat-model deployment hit TPM/RPM quota | "Backing-model 429" row below; run references/bash/diagnose_server_error.sh |
model_error / invalid_request | Bad request shape (tools schema, max tokens) | Reproduce against the model deployment directly with the helper's Step 3 |
internal_error + empty message | Container crashed at startup, or _init_telemetry() raised | "telemetry init" rows below; pull :logstream |
(no last_error at all) | Agent identity has no inference role on the deployment | "agent returns server_error … zero rows" row below |
Step 2 — run the bash helper for the most common root cause (backing-model
quota exhaustion). It consolidates deployment-quota inspection, a direct
chat-completions reproduction that bypasses the agent runtime, and an
optional App Insights KQL probe — see
references/bash/diagnose_server_error.sh:
AZURE_RESOURCE_GROUP=<rg> FOUNDRY_ACCOUNT=<acct> \
MODEL_DEPLOYMENT_NAME=<deployment> \
./references/bash/diagnose_server_error.shSet DIAGNOSTIC_EVIDENCE_DIR to an existing private directory. By default the
helper reads quota metadata only. A new inference requires the separate explicit
ALLOW_NEW_MODEL_PROBE=yes decision; its result describes that new call, not
the original failure. Every CLI/curl call has a finite deadline. Raw body/headers
stay private; telemetry failure is inconclusive, never “LIKELY 429”.
Any capacity change needs a separate approved capacity/cost decision.
| Symptom | Cause | Fix |
FOUNDRY_PROJECT_ENDPOINT is reserved | Declared in azure.yaml environmentVariables | Remove it — platform injects automatically |
AGENT_* env var is reserved | All FOUNDRY_* and AGENT_* prefixed vars are reserved | Use a different prefix (e.g. TL_SUB_AGENTS) |
session_not_ready (424) | Container crashed before readiness probe | Check logstream: curl ...sessions/{sid}:logstream. Common causes: import error, sync/async mismatch, missing dep |
| Sub-agent tool calls return "Function failed" | FoundryAgent uses old agent_reference pattern | Use the client-swap pattern: sub_client.client = project_client.get_openai_client(agent_name=...) |
| Sub-agent calls silently fail (empty output) | AIProjectClient imported from sync (azure.ai.projects) | MUST use azure.ai.projects.aio — sync get_openai_client returns sync OpenAI which fails in async FoundryChatClient |
PermissionDenied: Principal does not have access (standard project-scoped model inference) | Should not happen by default — the agent identity has implicit access. If it does, the project's own managed identity may be missing Foundry User on the Foundry account (an account/project provisioning issue, not an agent-identity issue) | See § Identity & RBAC. Do NOT reach for a broad Foundry User grant on the agent identity as a first fix — that's not how access is modeled anymore |
PermissionDenied when the agent calls an external resource or the account-level endpoint directly | Advanced scenario beyond the implicit default | Grant the least-privilege role for the specific capability called — see § Agent access beyond defaults |
401 Unauthorized on every Responses call after azd deploy; RBAC visibly correct (Foundry User on both scopes) | Orchestrator image still on agent-framework-core ≤ 1.3.x — requests the OLD cognitiveservices.azure.com token scope, which the post-rename Foundry data plane rejects. Most commonly happens to agent versions pinned by sha256@… digest (digest is frozen at the 1.3.x build) while a sibling agent on the same project that uses :latest works fine because ACR re-resolved it to a fresh 1.4.0 layer | Upgrade orchestrator pyproject.toml to agent-framework-core~=1.4.0 + agent-framework-foundry~=1.4.0 + agent-framework-foundry-hosting==1.0.0a260514, regenerate uv.lock, az acr build with BOTH :latest AND a date-pinned tag (maf14-YYYYMMDDHHMM), azd deploy agents, then re-import every agent version. See § MAF 1.4.0 breaking changes for the full recipe |
ImportError: cannot import name 'AzureOpenAIChatClient' from 'agent_framework.azure' after pip install -U agent-framework-* | AzureOpenAIChatClient was removed in agent-framework-core 1.4.0 (2026-05-14). Hits sidecars, agents service, eval judges — anywhere that talked directly to Azure OpenAI without going through FoundryChatClient | Swap to from agent_framework.openai import OpenAIChatClient and use OpenAIChatClient(azure_endpoint=…, model=…, credential=DefaultAzureCredential()). Drop the explicit get_bearer_token_provider / ad_token_provider — the SDK derives the right scope itself. See snippet in § MAF 1.4.0 breaking changes |
ImportError: cannot import name 'ChatAgent' from 'agent_framework' | ChatAgent was renamed to Agent in agent-framework-core 1.6.0. No alias is exported. Any prose / older SKILL examples showing ChatAgent(chat_client=...) are stale | DO from agent_framework import Agent and call Agent(client=chat_client, tools=[...], instructions=...). Note: the kwarg also renamed from chat_client= to client= |
AttributeError: module 'agent_framework' has no attribute 'tools' or ImportError: cannot import name 'MCPStreamableHTTPTool' from 'agent_framework.tools.mcp' | The agent_framework.tools.mcp submodule was promoted to top-level in 1.6.0. MCPStreamableHTTPTool, MCPSseTool, MCPStdioTool are now top-level exports of agent_framework | DO from agent_framework import MCPStreamableHTTPTool. No [mcp] extra is needed (and none exists in 1.6.0 — see § Dependencies) |
uv warning unknown extra 'mcp' for agent-framework-core during install; pyproject ships looking MCP-ready but operators chase phantom issues | [mcp] extra does NOT exist in agent-framework-core 1.6.0. uv emits a warning but does NOT fail resolution, so the pyproject ships and the package is installed without the extra. MCP transport adapters are top-level exports already — no extra is needed | Remove [mcp] from the dep line: agent-framework-core~=1.6.0 (NOT agent-framework-core[mcp]~=1.6.0). See § Dependencies callout at top |
Workload UAMI (companion service, e.g. a bot or MCP server, using its own identity) hits Foundry User-gated 403 even with Foundry Account Owner | Foundry Account Owner covers control-plane operations only; account-level data-plane access (model inference, agents endpoint) requires an explicit grant. This is about a companion service's own identity, not the hosted agent's — the agent identity itself has implicit access by default (see § Identity & RBAC above) | Grant Foundry User (or the narrower Cognitive Services OpenAI User / Cognitive Services User) to the companion service's identity on the Foundry account scope. See § Agent access beyond defaults for the least-privilege options |
Experience not available for this subscription | Region doesn't support hosted agents | Try a region from the current § Region Availability list |
| Eval items have empty responses | Concurrent eval requests overwhelm cold-start container | Use sequential eval with warm-up request first (see run_evals() in evals.py) |
| Agent skips evidence-gathering tools and emits hollow packets | gpt-5.4-mini tool-call discipline degrades on long instruction chains (10+ steps); model calls commit-tool before evidence is ready | Two complementary fixes: (1) switch MODEL_DEPLOYMENT_NAME to gpt-5.4 (full); (2) make commit-tools refuse hollow inputs server-side via the validate-or-reject pattern in foundry-mcp-aca. Recent strict-smoke runs showed low reproducibility with mini + permissive MCP and high reproducibility with gpt-5.4 + validate-or-reject. |
Managed environment provisioning timed out / ProvisioningError | Generic failure; missing private project setup is one possible prerequisite failure, not a proven universal cause | Follow Deployment preflight: private Basic requires a project Agents / Succeeded host; Standard also requires its account/BYO contract. Never blanket-create/delete hosts. Preserve direct version GET errors and stop after bounded observation; platform-managed compute alone does not prove project setup. |
| Private ACR image pull fails | Project generation, project-MI permissions, registry policy or private reachability may be incompatible | Learn checked 2026-09-12: projects created after June 25, 2026 support private registries. Verify systemData.createdAt, registry permission mode and azureADAuthenticationAsArmPolicy, then actual pull path. Older/unknown/boundary date requires review, never automatic public exposure. |
APPLICATIONINSIGHTS_CONNECTION_STRING is reserved (HTTP 400 invalid_request_error at create_version) | Set in azure.yaml environmentVariables OR HostedAgentDefinition.environment_variables (e.g. as escape-hatch when platform auto-injection silently failed) | Remove it. Cannot be escape-hatched. You MUST guard configure_azure_monitor() defensively in container.py instead — use _init_telemetry() from foundry-observability (gap O-011). Observed in hosted-agent validation |
| Agent traces not appearing in AppInsights | Agent identities lack Monitoring Metrics Publisher (GUID 3913510d-...) OR AppInsights connection missing on account. IMPORTANT: GUID f526a384-... is "Azure Event Hubs Data Owner" — a completely wrong role despite being mislabeled in some references. When DisableLocalAuth: false, RBAC is not required — ikey auth works | Assign Monitoring Metrics Publisher RBAC to both identity principal IDs. Create AppInsights connection on the account (not project): category AppInsights, target = ARM resource ID, metadata ApiType: Azure. |
Hosted agent returns server_error/model:"" on every smoke; AppIn 0 rows; azd ai agent show reports active | container.py calls raw configure_azure_monitor() as the first line of main() with no try/except. When the platform fails to auto-inject APPLICATIONINSIGHTS_CONNECTION_STRING (e.g. AppIn account-level connection persisted with credentials: null), the SDK raises ValueError. Container crashes before ResponsesHostServer binds. Foundry runtime sees no agent. The agent itself is fine — telemetry init is what killed it. | Wrap telemetry init in _init_telemetry() (no-ops on missing env / SDK ImportError / any SDK exception). Never call configure_azure_monitor() raw at module/main scope. See foundry-observability gap row O-011 |
| AppInsights connection PUT 400 ValidationError "AuthType for AppInsights Connection can only be ApiKey" | Account-RP scope 2025-10-01-preview in some regions rejects authType: AAD despite skill guidance (correlation IDs available) | Use authType: ApiKey with credentials.key in body. BUT: the key is silently dropped server-side — GET returns credentials: null and platform never injects the env var. There is no working workaround at the platform layer. File a support ticket; ship with guarded _init_telemetry() so the agent functions without telemetry; consider region pivot |
| AppInsights connection account-level "1-per-category" limit | Account-level AppInsights connections enforce a single-instance-per-category constraint — cannot create parallel connections in the same account. Re-creation requires DELETE first | DELETE the existing connection BEFORE re-PUT. Use az rest --method DELETE with full URI as a variable (do NOT inline ?api-version=... — see next row) |
az rest --method DELETE strips ?api-version=... query string when URI is inlined on PowerShell | PowerShell argument parsing eats the ?api-version= before az sees it. The DELETE then fails with "MissingApiVersionParameter" or behaves inconsistently against the bare resource without the version | Workaround: assign the URI to a variable first, then pass via --uri $delUri: $delUri = "https://management.azure.com/.../connections/<name>?api-version=2025-10-01-preview"; az rest --method DELETE --uri $delUri |
| ACA job uses old code after deploy | Postdeploy hook fails (AZURE_AI_PROJECT_ENDPOINT not set) | Run cd infra/scripts && uv run deploy_job.py manually after each azd deploy |
mcp-config.json ${ENV_VAR}/mcp expands to /mcp when env var unset | Regex expansion ${MCP_SERVER_URL} → "" produces URL "/mcp" — truthy but not a valid HTTP URL. get_mcp_tool(url="/mcp") either crashes or sends requests to localhost. | In _create_mcp_tools(), guard with if not url or not url.startswith("http") instead of just if not url. Agent gracefully skips and runs instruction-only when no MCP server is deployed. |
Container starts but agent_reference errors in logs | FoundryAgent used for sub-agents | Replace with client-swap pattern |
| Protocol version error | Using "v1" or the historical "1.0.0" | Use the current GA semver "2.0.0" |
Sticky 424 session_not_ready for 8+ minutes; ZERO AppIn / LAW signal | New Python module added to agent code but NOT included in Dockerfile COPY line. Module import fails on container start → ResponsesHostServer never binds → /readiness never returns 200 → Foundry retries readiness in long backoff → every Responses request returns sticky 424. App Insights shows nothing because _init_telemetry() never runs (the module that calls it didn't even import). Near-impossible to diagnose from logs because there are no logs | Explicitly enumerate every Python module in the Dockerfile COPY line; do NOT rely on COPY ./* . or COPY . . globs (silently skip dotfiles + reorder hazards + cache-bust footguns). Pattern: COPY container.py corpus.py my_kb_tool.py copilot-instructions.md ./. After ANY new .py added to the agent module, re-check Dockerfile + rebuild |
Sticky 424 session_not_ready after MAF 1.4.0 upgrade; logstream shows TypeError: SkillsProvider.__init__() got an unexpected keyword argument 'skill_paths' | SkillsProvider(skill_paths=...) keyword constructor was removed in agent-framework-core 1.4.0. Container crashes at agent init before ResponsesHostServer binds. All agents using skill_paths= fail; agents without skills (or using from_paths()) work fine on the same base image | Replace SkillsProvider(skill_paths=skills_dir) with SkillsProvider.from_paths(skills_dir). Rebuild base image + all per-job overlay images. Verify via logstream: look for SkillsProvider configured log line instead of TypeError |
Skill silently missing from agent — load_skill never offered for one skill; container starts fine, other skills work | SkillsProvider (MAF 1.6.0) validates each SKILL.md YAML frontmatter description: field against a 1024-character limit. If a skill's description exceeds 1024 chars, MAF logs ERROR agent_framework._skills Skill '<name>' has an invalid description: Must be 1024 characters or fewer and silently drops the skill from the advertised set. No crash, no session_not_ready — the agent runs with N-1 skills. Extremely hard to diagnose without container logs because the agent appears healthy | Trim the YAML description: field to ≤ 1024 chars. Condense USE FOR / DO NOT USE FOR phrases; move verbose guidance into the skill body below the frontmatter. Verify with: python3 -c "import yaml; d=yaml.safe_load(open('SKILL.md').read().split('---')[1]); print(len(d.get('description','')))" — must print ≤ 1024. Discovered May 2026 — a skill with 1054-char description was silently dropped, only 3/4 skills loaded |
azure.yaml container.resources block silently not applied by an older azd ai agent deploy | Historical (pre-GA) reports of the deploy CLI accepting the block at the YAML schema layer but not passing it through to the platform — deployed agents came up at default cpu/memory regardless of what the YAML declared. Re-verify against your current azd extension version before assuming this still applies post-GA | Confirm actual deployed resources with azd ai agent show --output json (definition.cpu / definition.memory). If they don't match azure.yaml, retry after upgrading the azure.ai.agents extension; file a CLI bug if reproducible on a current extension version |
Scary RED 404 NotFound block at the tail of azd deploy <any-service> (agents/<key>/versions/<n> not found); the actual deploy succeeded | The azure.ai.agents azd extension's postdeploy hook fires after every azd deploy invocation (including unrelated services like bot, workspace, mcp) and looks up agents/<service-key>/versions/<n> — using the SERVICE KEY from azure.yaml verbatim, not the actual agent name. If your azure.yaml has e.g. services.agent.host: azure.ai.agent but the real agent is named orchestrator, the postdeploy hook 404s on agents/agent/versions/<n> every single time. Benign-but-loud false alarm; pollutes CI logs and CX in pilots | Preferred: rename the service key in azure.yaml to match the agent name (services.orchestrator.host: azure.ai.agent). If you can't rename (downstream scripts reference the key): error is cosmetic — verify with azd ai agent show <agent-name>. Track as azure.ai.agents extension bug; consider proposing an extension-level config like agentName: override |
Agent returns server_error; run.last_error.code is rate_limit_exceeded | The backing chat-model deployment (the one named in AZURE_AI_MODEL_DEPLOYMENT_NAME) hit its TPM/RPM quota. The agent runtime catches the 429 and re-emits the generic server_error in the chat envelope, so the throttle is invisible from the agent's response body. Hits demos and bursty eval loops most often. This is the deployment's own quota — NOT the historical gateway-level throttle described in § Gateway throttle finding, which is a different layer. | Run references/bash/diagnose_server_error.sh (lists deployment Capacity, reproduces a direct chat call to confirm the 429 is at the model layer, optionally KQLs App Insights for 429). On CONFIRMED 429, raise Capacity (TPM, in thousands) via az cognitiveservices account deployment update --deployment-name <name> --capacity <N> or the portal. No agent redeploy needed. See ### Diagnosing server_error locally above for the SYNC last_error probe. |
Agent returns server_error on every call; a manual advanced-access role assignment appears not to have taken effect; az role assignment list --assignee <principal_id> --all returns ZERO rows | Deploy script calls ARM PUT roleAssignments/ for the agent identity (an advanced-scenario grant per § Agent access beyond defaults), but the deployer's own identity only has Contributor — which excludes Microsoft.Authorization/roleAssignments/write. The ARM PUT returns 403, deploy script logs a warning and continues | Grant Microsoft.Authorization/roleAssignments/write (e.g. via Role Based Access Control Administrator or User Access Administrator) to the deployer's identity, scoped to the Foundry project (not the whole subscription) — least-privilege scope. Verify: az role assignment list --assignee <deployer_identity> --scope <project_id> --query "[].roleDefinitionName" |
Deploy script polls .../versions/{v}/containers/default and gets 404 for minutes; agent is actually working | Refreshed-preview hosted agents auto-provision containers — the legacy /containers/default status endpoint and :start action are not exposed. Polling this endpoint wastes 3+ min and blocks downstream steps (RBAC assignment, eval warmup) | Skip the container status/start API entirely. Go straight to a warmup chat (responses.create("ping")) with retry/backoff. If the warmup succeeds, the agent is ready — no container API needed. See § Compute Lifecycle note on refreshed preview |
Cross-skill ownership
This SKILL describes runtime patterns for hosted agents on Foundry (container build, SDK wiring, debugging). Some concerns belong to sibling SKILLs:
- Telemetry initialization (
configure_azure_monitor()guard,_init_telemetry()pattern): owned byfoundry-observability§ Layer 2. See gap O-011 / O-012 for AppInsights connection failures.- Deploy hooks (
azd postdeployscripts, role assignment automation): owned byazd-patterns§ azd hooks. See section on environment injection and dependency ordering.- Model selection (region availability, task/modality tables, tier maps): owned by
agentic-loop§ Foundry Model Selector — that table is the single source of truth across all awesome-gbb skills.Link back to these SKILLs when diagnosing deploy-stage failures.
Container Logs (Logstream API)
# Get session ID from session_not_ready error, then:
curl -H "Authorization: Bearer $TOKEN" -H "Accept: text/event-stream" \
"$PROJECT_ENDPOINT/agents/orchestrator/versions/$VER/sessions/$SID:logstream?api-version=2025-11-15-preview"Returns SSE events with {"stream":"stderr","message":"..."} — shows startup logs, tracebacks.
Also: azd ai agent monitor (session auto-resolves from the last invoke; --session-id optional)
Useful Commands
azd ai agent show # Agent status, identities, version, endpoints (--output json|table)
azd ai agent monitor # Stream logs; session auto-resolves from last invoke (--follow to tail, --session-id optional)
azd ai agent invoke "Hello!" # Quick test; consecutive invokes REUSE the session (--new-session to reset)
azd ai agent sessions # Manage sessions (list / show / stop / delete)
azd deploy <service> --no-prompt # Redeploy a service without reprovisioningCLI shape gotchas (verified live against
azure.ai.agents@1.0.0-beta.4, July 2026):
- Multiple hosted-agent services: pass the service name positionally.
azd ai agent show / invoke / monitor [name]— name and version auto-resolve fromazure.yaml+ the current azd environment when there's a single service. With more than onehost: azure.ai.agententry, give the service name (fromazure.yaml) as the first positional argument, or scope with the global-C/--cwd. (azd ai agent sessionsuses--agent-nameinstead of a positional for the same purpose.)azd ai agent monitordoes NOT require--session-id. The session auto-resolves from the last invocation;--session-idis optional (use it to target a specific session). Add--follow/-fto tail in real-time,--type systemfor system events,--tail N(1-300) for recent lines.azd ai agent invokereuses the session by default. Sessions are persisted per-agent — consecutive invokes reuse the same session automatically; pass--new-sessionto force a reset. One positional arg is the message (name auto-detected fromazure.yaml); two args are[name] [message].--input-file/-fsends a file as the request body,--localtargets a locally-running agent (azd ai agent run), and--output rawdumps the verbatim server response (status line + headers + body) for debugging.- Run agent commands from the azd project root (where
azure.yamlis), or scope with the global-C/--cwd; otherwise the extension can't resolve the project and errors out.
azd ai agent show output shape
The JSON shape a deploy script (e.g. one that persists the agent id
into azd env for downstream services) needs to parse:
{
"object": "agent.version",
"id": "<service-name>:<version>",
"name": "<service-name>",
"version": "1",
"agent_guid": "<guid>",
"agent_endpoints": {
"responses": "https://<acct>.services.ai.azure.com/api/projects/<proj>/agents/<service-name>/endpoint/protocols/openai/responses?api-version=v1"
},
"definition": {
"container_protocol_versions": [ { "protocol": "responses", "version": "2.0.0" } ],
"cpu": "1", "memory": "2Gi",
"image": "<acr>.azurecr.io/<service>/<service>-<env>:azd-deploy-<unix-ts>",
"kind": "hosted",
"environment_variables": { … }
},
"playground_url": "https://ai.azure.com/nextgen/r/…/build/agents/<service-name>/build?version=1",
"status": "active" | "creating" | "failed"
}Typical deploy-hook snippet (persist agent id for downstream services to read):
# Use .name (not .id) — .id returns "<service-name>:<version>", but downstream
# services need the bare agent name to construct /agents/<name>/endpoint/... URLs.
AGENT_NAME=$(azd ai agent show -o json | jq -r '.name')
azd env set WEATHER_AGENT_ID "$AGENT_NAME"No role grant is needed for the agent to reach the project's own model deployment (see § Identity & RBAC). Only add an explicit role assignment against the agent's identity if it needs access beyond the implicit default — e.g. calling the account-level OpenAI endpoint directly instead of through the project endpoint.
Migration from Initial Preview (pre-April 2026)
| Initial Preview | Refreshed Preview |
from_agent_framework(agent).run() | ResponsesHostServer(agent).run() |
ChatAgent | Agent (from agent_framework) |
AzureOpenAIChatClient | FoundryChatClient (from agent_framework.foundry) |
AzureAIClient(agent_name=...) | Client-swap: FoundryChatClient.client = project_client.get_openai_client(agent_name=...) |
@ai_function | @tool(approval_mode="never_require") |
azure-ai-agentserver-agentframework | agent-framework-foundry-hosting |
Protocol version "v1" | "1.0.0" (semver) |
extra_body={"agent_reference": ...} | get_openai_client(agent_name=...) |
| Shared project MI | Dedicated agent Entra identity |
Manual start/stop | Automatic compute lifecycle |
ENABLE_CAPABILITY_HOST=true | Historical public/platform-managed template flag only: ENABLE_CAPABILITY_HOST=false is NOT an instruction to omit a private Basic project host or overwrite a Standard/BYO host. See Deployment preflight. |
project_client.agents.list() at startup | TL_SUB_AGENTS env var (avoids blocking readiness) |
azure.ai.projects (sync) in container | azure.ai.projects.aio (ASYNC) — sync silently fails |
⚠️ Historical (MAF 1.1.1):
FoundryAgentin MAF 1.1.1 silently hardcodedextra_body={"agent_reference": ...}internally — the old initial-preview pattern that fails against the refreshed preview. As of MAF 1.8.0,FoundryAgenthas been rehabilitated:__init__takesproject_endpoint+agent_name+agent_versionproperly, supports both PromptAgents and HostedAgents, andextra_bodyis only available as a user opt-in viadefault_options. The 1.1.1-era prohibition no longer applies; useFoundryAgentfreely under MAF ≥ 1.8.0. The client-swap pattern above remains the recommended path for sub-agent delegation in this skill's container runtime;FoundryAgentis most useful for caller-side orchestration code (see § FoundryAgent timeout parameter (MAF 1.8.0)). Exact version of rehabilitation between 1.1.1 and 1.8.0 not determined.
Deadline: Initial preview backend retires May 22, 2026.
Reference: Migration guide
GA migration reference (two-file era → unified azure.yaml)
A separate, more recent migration than the table above: moving from
the refreshed-preview two-file contract to the current GA
unified-azure.yaml contract this skill now documents throughout.
| Refreshed preview (two-file era) | Current GA |
agent.yaml (literal ContainerAgent schema) + agent.manifest.yaml (mustache scaffold) | Single azure.yaml — azure.ai.project + azure.ai.agent services |
Protocol version "1.0.0" | Protocol version "2.0.0" |
project.beta.agents.patch_agent_details(agent_endpoint=AgentEndpoint(...)) for traffic routing | project.agents.update_details(agent_endpoint=AgentEndpointConfig(...)) — stable, no preview header |
Foundry-Features: AgentEndpoints=V1Preview REST header for routing calls | Not required — the routing surface is stable GA |
allow_preview=True required for get_openai_client(agent_name=...) | Not required — verified against azure-ai-projects 2.3.0 |
Postdeploy hook auto-assigns Foundry User to the agent identity | No role assignment needed by default — implicit access to model inferencing + session storage |
northcentralus/eastus/swedencentral/westus known-working region list (April 2026) | 20-region list including East US 2 — see § Region Availability |
If migrating an older project, retain its files and grants until the selected consumer, identity and exact access requirements have been reviewed. Generate the new profile separately, compare the emitted definition and validate it. Removing an obsolete file is not authorization to revoke an existing grant; advanced-access requirements remain operation-specific.
Preview appendix: source-code deploy (still preview)
Status. Everything in this appendix is preview — isolated here on purpose so it never bleeds into the GA container-deploy guidance above. "Functionality, region availability, and APIs might change before general availability." Source: Deploy a hosted agent from source code (preview). Prefer the container (GA) path documented in the rest of this skill for production. Use this appendix only when you deliberately want a Docker-less inner loop.
Source-code deploy uploads a .zip of your Python or .NET code; the
platform builds it remotely instead of you building/pushing a
container image.
--deploy-mode code
azd ai agent init --deploy-mode code --runtime python_3_13 \
--entry-point main.py --dep-resolution remote_buildAfter initialization, azd writes the source-code settings to a
codeConfiguration block on the azure.ai.agent service in
azure.yaml:
my-agent:
host: azure.ai.agent
kind: hosted
codeConfiguration:
runtime: python_3_13
entryPoint: main.pySupported runtimes
The codeConfiguration.runtime field accepts only these values —
pick the one matching the binaries in your zip (Linux x86_64 wheels
for Python, or the TargetFramework of your dotnet publish output
for .NET):
| Language | Supported runtime values |
| Python | python_3_13, python_3_14 |
| .NET | dotnet_10 |
Python 3.11 and 3.12 are NOT supported for the source-code path.
If your code targets 3.11/3.12, bump to 3.13+ before adopting
--deploy-mode code. This constraint is specific to the
platform-managed source-code runtime — container-mode deploys with a
custom Dockerfile can keep whatever Python the base image ships.
Dependency resolution: --dep-resolution
| Value | Behavior | When to use |
remote_build (default) | Platform resolves + installs from your pyproject.toml or requirements.txt server-side. | Faster iteration; smaller repo. |
bundled | You vendor wheels locally (pip download --target packages/ --platform manylinux2014_x86_64 --only-binary=:all:) and the platform installs from the bundle. | Air-gapped policies; reproducible builds; deps not on PyPI. |
The resolver auto-detects pyproject.toml first, then falls back to
requirements.txt. Mixing both is unsupported.
Preview REST header
Source-code deploy uses the preview project.beta.agents surface for
mutating calls. On raw REST (Create/Update/Delete), send:
Foundry-Features: CodeAgents=V1Preview,HostedAgents=V1Previewazd ai agent adds this for you; raw REST / custom tooling must set it
explicitly. GET requests work without it today, but include it to be
safe — the header gates preview behaviour and may be enforced more
strictly before GA. On the Python SDK side, create the client with
allow_preview=True — this preview surface (unlike the GA
get_openai_client(agent_name=...) invoke path documented in the rest
of this skill) genuinely requires it, and create_version_from_code /
download_code live on project.beta.agents, not the stable
project.agents.
Retirement policy
Foundry aligns hosted-agent language support with each language's
community end-of-life date. After a language's end-of-life date, you
can still create/update/run agents on the retired runtime value, but
they're not eligible for support, new features, or security patches
until you set a current codeConfiguration.runtime value and redeploy.
Why this stays out of the GA guidance above
Mixing preview source-code-deploy guidance into the GA container-deploy sections would make it easy to accidentally follow a preview code path (with its own preview header, preview SDK surface, and narrower runtime support) while believing you're on the stable GA path. Keep them separate; if you need this path, treat every claim here as preview and re-verify against the source doc before shipping.
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- microsoft/frontier-agentic-cobuild-rvas★ 7same name
- microsoft/frontier-ai-starter-kit-rvas★ 7same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository46 other files it gives its agents.
- AGENTS.mdAGENTS.md
- docs/llms.txtllms.txt
- agent-framework-harnessSkill
- auto-demo-producerSkill
- azd-patternsSkill
- azure-backup-readinessSkill
- azure-monitor-alert-baselineSkill
- azure-resource-diagnosticsSkill
- azure-sre-agentSkill
- azure-tenant-isolationSkill
- citadel-hub-deploySkill
- citadel-spoke-onboardingSkill
- copilot-doctorSkill
- foundry-agentopsSkill
- foundry-agtSkill
- foundry-caphost-lifecycleSkill
- foundry-cost-monitoringSkill
- foundry-cross-resourceSkill
- foundry-doc-vision-speechSkill
- foundry-evalsSkill
- foundry-iqSkill
- foundry-mcp-aca-jobsSkill
- foundry-mcp-acaSkill
- foundry-mcp-authSkill
- foundry-network-runbookSkill
- foundry-observabilitySkill
- foundry-prompt-agentsSkill
- foundry-rbac-auditSkill
- foundry-routinesSkill
- foundry-skill-catalogSkill
- foundry-teams-botSkill
- foundry-toolboxSkill
- foundry-vnet-deploySkill
- foundry-voice-agentsSkill
- foundry-voice-liveSkill
- gbb-humanizerSkill
- gbb-pptxSkill
- ghcp-cli-configSkill
- ghcp-hosted-agentsSkill
- html-deck-to-pptxSkill
- html-presentationSkill
- ip-catalogSkill
- paygo-ptu-cost-analyzerSkill
- progress-guardSkill
- token-economicsSkill
- web-experience-designSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

