Azure AI Foundry hosted agents
5-minute deploy · BYO container · Foundry-managed VM-isolated sandbox
This is the hosted-agent path on Azure AI Foundry: you package
your agent (LangGraph here) as a container, register it as a
Foundry hosted agent, and Foundry runs it in a VM-isolated sandbox
behind the project's agent endpoint. Because the sandbox runs
your code, waxell-observe installs and runs inside the
container just like any other Python dependency — the Foundry
hosting boundary is transparent to the SDK.
What you get
- Every LLM call your agent makes through the project's model deployment → captured as a child LLM span on the parent run with real model id, token counts, and cost.
- Every tool call in the agent loop → captured as tool spans, no manual per-tool wrapping (the LangGraph instrumentor handles it).
- Policies scoped to this agent — budget caps, tool blocks — enforced inside the container before the LLM/tool call fires.
- Runs stream natively into your Waxell dashboard — no App Insights pull, no telemetry export wiring.
The sandbox is just another Python host. Your agent code is unchanged; Azure handles isolation, scaling, and lifecycle.
Prerequisites
azd ext install azure.ai.agents # provides the `azd ai agent` commands
azd auth login
You also need:
- An Azure AI Foundry project with a model deployment (e.g.
gpt-5-mini). - The Foundry Project Manager role on the project (required
by
azd provision/azd deploy). - Docker running locally —
azdbuilds the agent image and pushes it to your project's Azure Container Registry.
Project layout
Three files: requirements.txt, Dockerfile, main.py.
requirements.txt:
waxell-observe
langchain-azure-ai[hosting]>=1.2.4
langgraph
langchain-core
langchain-openai
azure-identity
azure-ai-projects>=2.1.0
Dockerfile — the container serves the OpenAI Responses protocol
that Foundry hosted agents speak. The langchain-azure-ai[hosting]
extra requires pip 26 or newer, so upgrade pip before installing:
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
PORT=8088
# langchain-azure-ai's [hosting] extra requires pip >= 26
RUN pip install --no-cache-dir --upgrade "pip>=26"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
EXPOSE 8088
CMD ["python", "main.py"]
main.py — arm waxell-observe first, build a LangGraph graph,
and serve it with ResponsesHostServer (which also auto-exposes
the /readiness health endpoint Foundry probes — you don't
implement it):
import os
# Arm waxell-observe before anything else runs. init() reads
# WAXELL_API_KEY / WAXELL_API_URL from env and auto-instruments
# LangGraph / LangChain.
if os.environ.get("WAXELL_API_KEY"):
try:
import waxell_observe as waxell
waxell.init()
except Exception: # never let observability break the runtime
pass
from langchain_core.tools import tool
@tool
def lookup_account(account_id: str) -> dict:
"""Look up an account's tier and region."""
return {"account_id": account_id, "tier": "enterprise", "region": "eastus"}
def _model():
"""Chat model bound to the project's model deployment via the
agent's own Entra identity — no API keys in the container."""
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from langchain_openai import ChatOpenAI
deployment = os.environ.get("MODEL_DEPLOYMENT_NAME", "gpt-5-mini")
cred = DefaultAzureCredential()
client = AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], # injected by Foundry
credential=cred,
)
oai = client.get_openai_client()
token_provider = get_bearer_token_provider(
cred, "https://ai.azure.com/.default"
)
return ChatOpenAI(
base_url=str(oai.base_url),
model=deployment,
api_key="placeholder", # replaced per-request by the bearer token
default_headers={"Authorization": f"Bearer {token_provider()}"},
)
def build_graph():
from langgraph.prebuilt import create_react_agent
graph = create_react_agent(
_model(),
tools=[lookup_account],
prompt="You are a concise account assistant. One or two sentences.",
)
# LangGraph runs are attributed to graph.name — set it to the
# agent name you want to see in Waxell.
graph.name = "my-foundry-agent"
return graph
def main():
from langchain_azure_ai.agents.hosting import ResponsesHostServer
server = ResponsesHostServer(build_graph())
server.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8088")))
if __name__ == "__main__":
main()
Foundry injects FOUNDRY_PROJECT_ENDPOINT into the container and
the hosted agent authenticates to the model deployment with its
own dedicated Entra identity — no model API keys anywhere in your
image.
Deploy
azd ai agent init # scaffolds azure.yaml for the agent
azd ai agent run # optional: build + run locally first
azd provision
azd deploy # builds the image, pushes to ACR, registers an immutable agent version
Set two environment variables on the agent version (in
azure.yaml, or in the Foundry portal's agent-version settings):
WAXELL_API_URL—https://api.waxell.dev(or your dedicated tenant URL).WAXELL_API_KEY— your Waxell API key. Reference it from a Foundry CustomKeys connection rather than pasting a plaintext value into the version config.
Each azd deploy creates an immutable agent version, so you can
canary a new build with a weighted traffic split before cutting
over.
Invoke
Run the agent from the Foundry portal playground, or call its agent endpoint via the Responses API. Then check Waxell:
wax runs list --limit 5
You'll see the run under agent_name=my-foundry-agent — parent
agent span, a chat gpt-5-mini LLM child span with real token
counts and cost, and a tool span for each lookup_account call.
Network egress
Hosted-agent sandboxes egress over the public internet, so
api.waxell.dev (HTTPS) is reachable out of the box. If your
project restricts outbound traffic with a VNet, allow HTTPS egress
to your Waxell endpoint so runs keep streaming.
Policy enforcement inside the runtime
The full Waxell policy stack runs unchanged inside the hosted-agent
container — there is no Foundry-specific shim or escape hatch.
Policies you scope to my-foundry-agent (budget caps, tool
blocks, kill switches) are evaluated at the gate before each LLM
and tool call, and a block verdict aborts the agent before the
offending call lands — the run surfaces as Blocked and the
violation is recorded in your Waxell governance dashboard.
Connect your Foundry project to Waxell
Connecting the project itself (separate from the SDK inside your container) makes your hosted agent appear in Agent Discovery and brings the project's prompt-based agents under the same roof — discovery, trace pull, and content-filter policy push.
-
Create an app registration (service principal) in Entra: note the tenant id and client id, and create a client secret.
-
Grant the service principal three roles:
Role Scope What it enables Cognitive Services User Foundry account Agent read (Agent Discovery sync) Cognitive Services Contributor Foundry account Content-filter policy push + attach to model deployments Log Analytics Reader Subscription (or the App Insights workspace) Trace pull for prompt-based agents -
Enable tracing on the project: agent → Traces tab → Connect → create an Application Insights resource. This requires the
Microsoft.OperationalInsightsandMicrosoft.Insightsresource providers to be registered on the subscription. -
In Waxell: Agent Discovery → Azure AI Foundry → enter the project endpoint, tenant/client ids, subscription, resource group, and account name, paste the client secret, and Sync.