# Host and deploy on Foundry

Agent Mission Control developer docs > Microsoft Agent Framework (Python)

> Serve an agent or workflow with ResponsesHostServer, package it, deploy it as a Foundry hosted agent version, and call it from a client.

**Before you start:** A Foundry project in a region that supports hosted agents, and a data-plane role on it.

## Before you start

- **Region:** Foundry hosted agents are region-limited, and an account's region is fixed when it is created. Check Microsoft's [hosted agents region list](https://learn.microsoft.com/azure/foundry/agents/concepts/hosted-agents#region-availability). An unsupported region fails with `bad_request: Unsupported region for Foundry Hosted Agents`.
- **Model:** the project needs a deployed chat model.
- **Role:** creating and invoking agent versions needs a **data-plane** role on the project, such as **Foundry User**. Control-plane Owner or Contributor alone is not enough.
- **Tooling:** locally you only need `azure-ai-projects>=2.3.0` and `azure-identity` to drive the deploy. Foundry installs `requirements.txt` itself in a remote build.

## Write main.py

`ResponsesHostServer` serves the Responses protocol (`/responses`) and a health probe on port `8088`, which is the runtime contract Foundry expects. A single agent is served directly:

`main.py (single agent)`
```python
from agent_framework_foundry_hosting import ResponsesHostServer

from agent import create_agent


def main() -> None:
    ResponsesHostServer(create_agent()).run()


if __name__ == "__main__":
    main()
```

A workflow is wrapped with `as_agent()` first. Passing a raw `Workflow` makes the host call `Workflow.run(messages=...)`, which fails. This version also turns on process-loss recovery and allow-lists the checkpoint types; [Harden an agent or workflow for AMC](https://www.agentmissioncontrol.ai/developers/af-amc-hardening/) explains both.

`main.py (workflow)`
```python
from agent_framework_foundry_hosting import CheckpointStoreProvider, ResponsesHostServer
from azure.ai.agentserver.responses import ResponsesServerOptions

from workflow import build_workflow, create_agents


def main() -> None:
    workflow = build_workflow(*create_agents())
    server = ResponsesHostServer(
        workflow.as_agent(name="contoso-content-pipeline"),
        options=ResponsesServerOptions(resilient_background=True),
        checkpoint_store_provider=CheckpointStoreProvider(
            allowed_checkpoint_types=["workflow:Draft"],
        ),
    )
    server.run()


if __name__ == "__main__":
    main()
```

## Container

`Dockerfile`
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m appuser
USER appuser
EXPOSE 8088
CMD ["python", "main.py"]
```

Keep `.env`, virtual environments and the deploy script out of the image and the deploy package.

## Deploy with the Python SDK

This script zips the folder, creates a new **code** version with a remote build, waits for it to go active, routes all traffic to it, and smoke-tests it. Each run creates a new immutable version.

`deploy.py`
```python
import sys, tempfile, time, zipfile
from pathlib import Path

from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
    AgentEndpointConfig, CodeConfiguration, CodeDependencyResolution,
    FixedRatioVersionSelectionRule, HostedAgentDefinition, ProtocolConfiguration,
    ProtocolVersionRecord, ResponsesProtocolConfiguration, VersionSelector,
)
from azure.identity import AzureCliCredential

ENDPOINT = "<your-project-endpoint>"
MODEL_NAME = "<model-deployment-name>"
AGENT_NAME = "contoso-content-pipeline"
SOURCE_DIR = Path(__file__).parent.resolve()
EXCLUDE_PARTS = {".git", ".venv", "__pycache__", ".azure", ".env"}
EXCLUDE_NAMES = {".env", ".dockerignore", "deploy.py"}


def make_zip() -> Path:
    zp = Path(tempfile.gettempdir()) / f"{AGENT_NAME}.zip"
    zp.unlink(missing_ok=True)
    with zipfile.ZipFile(zp, "w", zipfile.ZIP_DEFLATED) as zf:
        for p in SOURCE_DIR.rglob("*"):
            rel = p.relative_to(SOURCE_DIR)
            if not p.is_file() or rel.name in EXCLUDE_NAMES:
                continue
            if any(part in EXCLUDE_PARTS for part in rel.parts):
                continue
            zf.write(p, rel.as_posix())
    return zp


def wait_active(c, ver) -> None:
    for _ in range(90):
        time.sleep(10)
        status = c.agents.get_version(agent_name=AGENT_NAME, agent_version=ver)["status"]
        if status == "active":
            return
        if status in ("failed", "Failed"):
            raise RuntimeError("provisioning failed")
    raise TimeoutError("version never went active")


def main() -> None:
    zp = make_zip()
    with zp.open("rb") as code, AzureCliCredential() as cred, \
         AIProjectClient(endpoint=ENDPOINT, credential=cred) as c:
        created = c.agents.create_version_from_code(
            agent_name=AGENT_NAME,
            description="Contoso content pipeline",
            definition=HostedAgentDefinition(
                cpu="1", memory="2Gi",
                code_configuration=CodeConfiguration(
                    runtime="python_3_13", entry_point=["python", "main.py"],
                    dependency_resolution=CodeDependencyResolution.REMOTE_BUILD),
                environment_variables={
                    "FOUNDRY_PROJECT_ENDPOINT": ENDPOINT,
                    "FOUNDRY_MODEL_NAME": MODEL_NAME},
                protocol_versions=[ProtocolVersionRecord(protocol="responses", version="2.0.0")],
            ),
            code=code,
        )
        wait_active(c, created.version)
        c.agents.update_details(
            agent_name=AGENT_NAME,
            agent_endpoint=AgentEndpointConfig(
                version_selector=VersionSelector(version_selection_rules=[
                    FixedRatioVersionSelectionRule(agent_version=created.version, traffic_percentage=100)]),
                protocol_configuration=ProtocolConfiguration(responses=ResponsesProtocolConfiguration()),
            ),
        )
        with c.get_openai_client(agent_name=AGENT_NAME) as oai:
            r = oai.responses.create(input="Slogan for an affordable electric bike")
        print("smoke test:", r.status, r.error)


if __name__ == "__main__":
    sys.exit(main())
```

| Setting | Value | Why |
| --- | --- | --- |
| `runtime` | `python_3_13` | Agent Framework installs cleanly. Avoid `python_3_14`, where some wheels are missing. |
| `entry_point` | `["python", "main.py"]` | `main.py` starts `ResponsesHostServer`. |
| `dependency_resolution` | `REMOTE_BUILD` | Foundry installs `requirements.txt` in the cloud. |
| `protocol_versions` | `responses` `2.0.0` | Required by the responses 2.x runtime that process-loss recovery uses. |
| `cpu` / `memory` | `1` / `2Gi` | Allowed sizes are `0.5`/`1Gi`, `1`/`2Gi` and `2`/`4Gi`. |

A healthy smoke test has `status == "completed"` and `error` of `None`. To roll back, point `FixedRatioVersionSelectionRule` at an earlier version. Never delete the version that is currently routed. Microsoft also documents an `azd ai agent` flow; the reference deployments behind this guide use the SDK path above because it is easier to run non-interactively.

## Call the deployed agent from a client

This is a preview API. It is also how you read the raw stream AMC will see.

`verify.py`
```python
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import VersionRefIndicator
from azure.identity import DefaultAzureCredential

ENDPOINT = "<your-project-endpoint>"
AGENT_NAME = "contoso-content-pipeline"

with (DefaultAzureCredential() as cred,
      AIProjectClient(endpoint=ENDPOINT, credential=cred, allow_preview=True) as project):
    ver = project.agents.get(agent_name=AGENT_NAME).versions["latest"].version
    session = project.agents.create_session(
        agent_name=AGENT_NAME, version_indicator=VersionRefIndicator(agent_version=ver))
    oai = project.get_openai_client(agent_name=AGENT_NAME)
    stream = oai.responses.create(
        input="Draft a launch slogan.",
        stream=True,
        extra_body={"agent_session_id": session.agent_session_id},
        metadata={"x-ms-debug-mode-enabled": "1"},
    )
    for event in stream:
        print(event.type)
    project.agents.delete_session(agent_name=AGENT_NAME, session_id=session.agent_session_id)
```

`x-ms-debug-mode-enabled` gives richer events. AMC sends the same metadata when it runs the agent.

## Related

- [Harden an agent or workflow for AMC](https://www.agentmissioncontrol.ai/developers/af-amc-hardening/)
- [Register and test in AMC](https://www.agentmissioncontrol.ai/developers/af-register-and-test/)
- [Troubleshoot Agent Framework agents](https://www.agentmissioncontrol.ai/developers/af-troubleshooting/)

---
Source: https://www.agentmissioncontrol.ai/developers/af-host-and-deploy/ · Last updated 2026-10-21 · Full index: https://www.agentmissioncontrol.ai/llms.txt
