Skip to main content

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:

  1. An Azure AI Foundry project with a model deployment (e.g. gpt-5-mini).
  2. The Foundry Project Manager role on the project (required by azd provision / azd deploy).
  3. Docker running locally — azd builds 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.

  1. Create an app registration (service principal) in Entra: note the tenant id and client id, and create a client secret.

  2. Grant the service principal three roles:

    RoleScopeWhat it enables
    Cognitive Services UserFoundry accountAgent read (Agent Discovery sync)
    Cognitive Services ContributorFoundry accountContent-filter policy push + attach to model deployments
    Log Analytics ReaderSubscription (or the App Insights workspace)Trace pull for prompt-based agents
  3. Enable tracing on the project: agent → Traces tab → Connect → create an Application Insights resource. This requires the Microsoft.OperationalInsights and Microsoft.Insights resource providers to be registered on the subscription.

  4. 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.