Files
agent-framework/python/samples/04-hosting/af-hosting/foundry_hosted_agent
T
Eduard van Valkenburg 6b822853eb Python: add hosting Channels sample apps (#5645)
* samples(hosting): add hosting Channels sample apps under samples/04-hosting/af-hosting

Adds five end-to-end sample apps under
``python/samples/04-hosting/af-hosting/`` that exercise the
``agent-framework-hosting`` Channels stack from the simplest single-channel
case up to a multi-channel deployment with cross-channel identity linking.

Samples (ordered by complexity)
-------------------------------

* ``foundry_hosted_agent/`` — minimal Responses + Invocations host with a
  Foundry-backed agent and ``FoundryHostedAgentHistoryProvider``.
  ``agd``-deployable; bundles a ``Dockerfile`` and
  ``scripts/vendor-packages.sh`` that copies workspace packages into
  ``_vendor/`` for self-contained builds. ``_vendor/`` is gitignored.
* ``local_responses/`` — single-channel Responses host with a
  ``run_hook`` that strips caller-supplied options and forces a
  reasoning preset. Demonstrates the hook seam over the uniform
  ``ChannelRequest`` envelope.
* ``local_responses_workflow/`` — Responses + Invocations exposing a
  three-agent workflow with per-conversation checkpoint storage.
* ``local_telegram/`` — Responses + Telegram with a ``@tool``,
  ``FileHistoryProvider``, hooks, and a ``ResponseTarget`` multicast
  variant (``call_server_multicast.py``) that pushes a single Responses
  reply to a separate Telegram chat.
* ``local_identity_link/`` — full surface: Responses + Invocations +
  Telegram + Activity Protocol (Teams) + the ``EntraIdentityLinkChannel``
  sidecar. Resolves per-channel ids onto a single Entra object id so a
  user's history follows them across surfaces.

Notes
-----

* Samples that use Telegram/Teams via Activity Protocol depend on the
  renamed ``agent-framework-hosting-activity-protocol`` package (see the
  PR-5 series).
* All samples use ``[tool.uv.sources]`` editable workspace deps, except
  ``foundry_hosted_agent/`` which uses the ``./_vendor/`` self-contained
  layout for ``azd`` Docker builds.
* Each sample includes a ``README.md`` with run instructions and an
  ``app.py`` ASGI entrypoint plus a ``call_server.py`` client harness.

Depends on the prior hosting PRs (foundry-hosted-agent refactor +
hosting-core + the per-channel packages). After those merge, this
branch can be rebased onto ``main`` cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* samples(hosting): point sample deps at the feature/python-hosting GitHub branch

Switches every sample's ``[tool.uv.sources]`` from in-monorepo
editable path deps (which only resolve when running inside the
agent-framework workspace) to git refs targeting the
``feature/python-hosting`` branch on
``microsoft/agent-framework``. Samples now install standalone outside
the monorepo while the ``agent-framework-hosting*`` packages are still
pre-PyPI; once they publish, the ``[tool.uv.sources]`` block can be
dropped and the declared deps resolve from PyPI.

Cleanup
-------

* Drops ``foundry_hosted_agent/scripts/vendor-packages.sh``,
  ``_vendor/`` from ``.gitignore``, the ``hooks.prepackage`` block in
  ``azure.yaml`` and the ``COPY _vendor/`` step in the Dockerfile —
  vendoring is no longer needed because git refs make the deps
  network-resolvable from any context.
* Drops obsolete ``workspace.pyproject.toml`` reference and ``scripts/``
  / ``workspace.pyproject.toml`` entries from
  ``Dockerfile.dockerignore``.
* Updates the foundry sample's Dockerfile to ``uv sync --no-dev``
  (no ``--frozen``) so it locks fresh against the GitHub-hosted deps
  at build time.
* Drops every committed ``uv.lock`` because the resolver needs network
  access to ``feature/python-hosting`` to lock — they regenerate the
  first time a user runs ``uv sync`` after the branch lands.
* Refreshes the per-sample READMEs to mention the GitHub install path
  instead of "in-tree workspace packages".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* samples(hosting): address PR #5645 review comments

- foundry_hosted_agent/call_server.py: replace hard-coded
  project_endpoint and service_session_id with FOUNDRY_PROJECT_ENDPOINT,
  FOUNDRY_HOSTED_AGENT_NAME, and optional FOUNDRY_HOSTED_SESSION_ID
  environment variables. Session-id is now optional so the sample
  exercises the new-conversation path by default.

- local_identity_link/app.py:
  * make_telegram_hook: apply the reasoning bump regardless of
    identity-link state (the previous early-return on linked chats
    silently dropped the high-effort preset for the very flow the
    sample exists to demonstrate).
  * make_responses_hook: add a prominent DEV-ONLY warning that the
    client-supplied entra_oid shortcut bypasses identity verification
    and must be replaced by a JWT validator in production.
  * /link command: early-return when chat_id is missing instead of
    minting an authorize URL keyed on "telegram:None" (which would
    poison the link store with a binding any future chat_id-less
    update would collapse onto).
  * Switch ENTRA_CERT_PATH / ENTRA_CERT_PASSWORD env vars to the
    longer ENTRA_CERTIFICATE_PATH / ENTRA_CERTIFICATE_PASSWORD names
    that the README already documents.
  * channels: Sequence[Channel] -> list[Channel] (the next line
    appends, which a Sequence type doesn't expose).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(hosting-samples): apply sample formatting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(hosting-samples): guard command input text

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6b822853eb · 2026-05-28 14:57:46 +02:00
History
..

foundry_hosted_agent — Responses + Invocations (Foundry Hosted Agents compatible)

Smallest end-to-end hosting sample. One Foundry-backed agent, two channels, no human-chat surface — and that minimal shape is the whole point: a host configured with at least the Responses and Invocations channels under their default mount roots is runtime-compatible with the Foundry Hosted Agents platform. The same container image runs locally, behind any ASGI server, or as a Hosted Agent — no protocol shim, no extra adapter.

Route Channel Used by
POST /responses ResponsesChannel OpenAI Responses clients (call_server.py)
POST /invocations/invoke InvocationsChannel Host-native JSON envelope (Hosted Agents)

Conversation history

The agent is wired with FoundryHostedAgentHistoryProvider (from agent-framework-foundry-hosting). When a Responses request supplies previous_response_id, the channel uses it as the session id and the provider fetches the prior turn chain directly from {FOUNDRY_PROJECT_ENDPOINT}/storage/... using the same managed-identity credential as the chat client. Locally (when FOUNDRY_HOSTING_ENVIRONMENT is unset) it transparently falls back to an in-memory store, so the same code runs in dev. Writes are a no-op — Foundry persists Responses turns authoritatively as the runtime executes them.

For richer scenarios (custom tools, history providers, run hooks, multicast, Telegram, Teams, identity linking) see ../local_telegram and ../local_identity_link.

Layout

foundry_hosted_agent/
├── app.py                       # the host (ResponsesChannel + InvocationsChannel)
├── call_server.py               # client: openai SDK / agent framework / FoundryAgent
├── agent.yaml                   # Foundry Hosted Agents minimal definition
├── agent.manifest.yaml          # Foundry Hosted Agents full deployment manifest
├── azure.yaml                   # azd service config (build context = this folder)
├── Dockerfile                   # built from this folder; uv fetches deps from GitHub
├── Dockerfile.dockerignore      # BuildKit allowlist that trims the context
├── pyproject.toml               # depends on the hosting packages via GitHub git refs
└── README.md                    # this file

Run locally

export FOUNDRY_PROJECT_ENDPOINT=https://<your-project>.services.ai.azure.com
export MODEL_DEPLOYMENT_NAME=gpt-4.1-mini
az login                       # any DefaultAzureCredential source

uv sync
uv run python app.py           # binds 0.0.0.0:8000

The env var names match agent.manifest.yaml so the same shell environment works for both local runs and Hosted Agent deployments.

Call locally

uv sync --group dev

# OpenAI SDK pointed at the local /responses endpoint.
uv run python call_server.py --via openai "hello there"

# The same call via the Agent Framework Agent + OpenAIChatClient stack.
uv run python call_server.py --via af "hello there"

# Once deployed as a Hosted Agent: target the Foundry-managed endpoint.
export FOUNDRY_HOSTED_AGENT_NAME=agent-framework-hosting-sample
uv run python call_server.py --via foundry "hello there"

Docker

The Docker build context is this sample folder. pyproject.toml declares the in-tree agent-framework-hosting* packages via [tool.uv.sources] git refs pointing at the feature/python-hosting branch of microsoft/agent-framework, so uv sync inside the image fetches them directly. No vendoring step is required — the build just needs network access to GitHub. Once the hosting packages publish to PyPI you can drop the [tool.uv.sources] overrides and rely on PyPI resolution.

# From this folder — context = `.` (sample folder).
DOCKER_BUILDKIT=1 docker build -t hosting-sample-hosted-agent .

docker run -p 8000:8000 \
    -e FOUNDRY_PROJECT_ENDPOINT -e MODEL_DEPLOYMENT_NAME \
    -e AZURE_CLIENT_ID -e AZURE_TENANT_ID -e AZURE_CLIENT_SECRET \
    hosting-sample-hosted-agent

Hosted Agent deployment

azure.yaml keeps project: . and uses docker.remoteBuild: true — the remote builder receives only this sample folder and runs uv sync to pull the hosting packages from GitHub.

The two YAMLs follow the same convention as the foundry-hosted-agents/ reference samples — agent.yaml is the minimal kind/protocols/resources card, agent.manifest.yaml is the full template + environment-variable + model-resource binding used during deployment.

azd up        # provisions infra/ + builds + pushes + deploys
azd deploy    # rebuild + redeploy only

Required Foundry RBAC

The container runs as the Hosted Agent's managed identity. That identity needs permission to call the Foundry project's agent/Responses endpoints — without it the call returns 401 PermissionDenied. Grant the Azure AI Project Manager role (or the more granular Microsoft.CognitiveServices/accounts/AIServices/agents/* data actions) on the Foundry project to the Hosted Agent's managed identity. See https://aka.ms/FoundryPermissions for the full role list.

Health probe

The Foundry Hosted Agents runtime probes GET /readiness; AgentFrameworkHost exposes that route automatically (returns 200 ok). No extra wiring needed.

The host code never imports anything Foundry-specific beyond the chat client itself — swapping FoundryChatClient for OpenAIChatClient (or any other client) flips this sample from a Hosted Agent target to a non-Foundry deployment without touching the channels.