* 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>
local_responses_workflow — workflow target with structured intake + checkpoints
A Workflow (intake → writer → legal reviewer → formatter) hosted
behind both the Responses API and the Invocations API, with the
host configured to persist per-conversation checkpoints. Mirrors
../../foundry-hosted-agents/responses/04_workflows/
but uses the agent-framework-hosting stack instead of the
Foundry-Hosted-Agents runtime, and adds a structured intake step
(SloganBrief with topic / style / audience fields) at the front
of the workflow.
What's interesting
AgentFrameworkHost(target=workflow, …)— the host detects aWorkflowtarget and dispatches toworkflow.run(...)(noAgent.create_session(...)).- Two channels are mounted side-by-side (
ResponsesChannelat/responses,InvocationsChannelat/invocations/invoke). Both share the samebrief_hookthat adapts the channel-native input into the workflow start executor's typed input — Responses delivers alist[Message], Invocations delivers astr, but the hook normalises both to text and produces aSloganBrief. - The hook parses the inbound text as JSON
(
{"topic": ..., "style": ..., "audience": ...}); if parsing fails it uses the whole text astopicwith defaults. - The workflow's first executor (
BriefIntakeExecutor) acceptsSloganBriefdirectly — that's what gets sent intoworkflow.run(...)by the host. checkpoint_location=storage/checkpoints/— the host scopes aFileCheckpointStorageper conversation (Responses keys it onprevious_response_id/conversation_id; Invocations keys it onsession_id) and restores from the latest checkpoint at the start of every turn before applying the new input. Without an isolation key the host skips checkpointing for that request.- No
HistoryProvider— the workflow owns its own state via the checkpoint store.
Run
export FOUNDRY_PROJECT_ENDPOINT=https://<your-project>.services.ai.azure.com
export FOUNDRY_MODEL=gpt-5.4-nano
az login
uv sync
uv run hypercorn app:app --bind 0.0.0.0:8000
Single-process for quick iteration:
uv run python app.py
Call locally
Two clients are provided next to app.py:
call_server.py— Python client using the OpenAI SDK (Responses API only).call_server.rest— raw REST examples for both the Responses and Invocations endpoints (open in VS Code with the REST Client extension or any compatible HTTP-file runner).
uv sync --group dev
# Structured brief via the OpenAI SDK (Responses API):
uv run python call_server.py \
'{"topic": "electric SUV", "style": "playful", "audience": "young families"}'
# Plain topic (style/audience default to "modern" / "general"):
uv run python call_server.py "electric SUV"
# Continue an existing conversation by its `response.id`:
uv run python call_server.py --previous-response-id <response-id> \
'{"topic": "electric SUV", "style": "retro", "audience": "boomers"}'
After a few turns, inspect storage/checkpoints/<isolation_key>/ —
each conversation has its own subdirectory of checkpoint files written
by the host.
This sample is local-only — no Dockerfile, no Foundry packaging. For a Foundry-Hosted-Agents-compatible packaging see
../foundry_hosted_agent.