* Add Discord hosting channel Add an alpha agent-framework-hosting-discord package backed by Discord HTTP Interactions. The channel verifies signed slash-command requests, registers commands, runs hosted agents and ChannelCommand handlers, supports originating response hooks, streams by editing the original interaction response, and can push through Discord channel ids. Factor standard channel response-hook context application into hosting core so both host fan-out and originating channel replies use one helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Discord review chunking feedback Ensure Discord command replies are chunked and streaming preview edits stay under Discord's content limit while final streamed replies continue through the chunked reply path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * small fix in init * updated lock --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
agent-framework-hosting
Multi-channel hosting for Microsoft Agent Framework agents.
agent-framework-hosting lets you serve a single agent (or workflow)
target through one or more channels — pluggable adapters that
expose the target over different transports. The result is a single
Starlette ASGI application you can host anywhere (local Hypercorn,
Azure Container Apps, Foundry Hosted Agents, …).
The base package contains only the channel-neutral plumbing:
AgentFrameworkHost— the Starlette hostChannel/ChannelPush— the channel protocolsChannelRequest/ChannelSession/ChannelIdentity/ResponseTarget— the request envelope and routing primitivesChannelContext/ChannelContribution/ChannelCommand— the channel-side hooks for invoking the target and contributing routes, commands, and lifecycle callbacksChannelRunHook/ChannelStreamTransformHook— the per-request customization seamsDurableTaskRunner+InProcessTaskRunner— the seam used to dispatch non-originating push fan-out; the in-process runner is the default. Plug in a durable adapter (e.g.agent-framework-hosting-durabletask) forruntime_mode="ephemeral"deployments.
Concrete channels live in their own packages so you only install what you use:
| Package | Transport |
|---|---|
agent-framework-hosting-responses |
OpenAI Responses API |
agent-framework-hosting-invocations |
Foundry-native invocation envelope |
agent-framework-hosting-telegram |
Telegram Bot API |
agent-framework-hosting-activity-protocol |
Bot Framework Activity Protocol (Teams, Direct Line, Web Chat, …) |
agent-framework-hosting-teams |
Microsoft Teams (Teams SDK) |
agent-framework-hosting-entra |
Entra (OAuth) identity-link sidecar |
Architecture
graph LR
Caller[External caller /<br/>messaging app]
subgraph Host[AgentFrameworkHost]
direction TB
ASGI[Starlette app]
Router[Channel router]
Parse{parse →<br/>command or<br/>message?}
Auth[host.authorize]
Resolver[IdentityResolver]
Delivery[_deliver_response]
Push[_handle_push_task]
end
Channels[Channels<br/>Responses · Invocations ·<br/>Telegram · Activity ·<br/>IdentityLinker]
CmdHandler[CommandHandler<br/>via ChannelCommandContext]
Target[(Agent or Workflow)]
Runner[DurableTaskRunner]
StateStore[(HostStateStore)]
Caller --> ASGI
ASGI --> Router
Router --> Parse
Parse -- /command --> CmdHandler
Parse -- message --> Auth
CmdHandler -- ctx.run --> Auth
CmdHandler -- local reply --> Channels
Auth --> Resolver
Resolver --> StateStore
Auth --> Target
Target --> Delivery
Delivery -- originating sync --> Channels
Delivery -- non-originating --> Runner
Runner --> Push
Push --> Channels
Channels --> ASGI
For a richer set of flow diagrams — identity linking, multi-channel fan-out, server-side relays, background runs, durable-runner codec envelopes, echo idempotency, workflow targets — see the Python hosting spec.
Install
pip install agent-framework-hosting agent-framework-hosting-responses
# or with uvicorn pre-installed for the demo `host.serve(...)` helper
pip install "agent-framework-hosting[serve]" agent-framework-hosting-responses
# add the [disk] extra to opt in to on-disk persistence (see below)
pip install "agent-framework-hosting[disk]"
Quickstart
from agent_framework.openai import OpenAIChatClient
from agent_framework_hosting import AgentFrameworkHost, Channel
agent = OpenAIChatClient().as_agent(name="Assistant")
# Add channels from sibling packages, e.g. `agent-framework-hosting-responses`
# exposes a `ResponsesChannel` that serves the OpenAI Responses API.
channels: list[Channel] = []
host = AgentFrameworkHost(target=agent, channels=channels)
host.serve(port=8000)
See the hosting samples
for richer multi-channel apps (Telegram + Teams + Responses fan-out,
identity linking, ResponseTarget routing, etc.).
Optional disk persistence (state_dir)
By default the host keeps everything in memory: the durable-task runner's
pending push queue, the per-isolation-key session aliases, the active-channel
map, and the per-channel ChannelIdentity map. That is the right shape for
ephemeral runtimes (Foundry Hosted Agents et al.) where the host is
restarted per request and persistence lives behind a service like the Foundry
response store, and for short-lived local dev.
For long-running deployments (an always-on container, a local dev server
you restart often, a single-VM bot) opt in to disk persistence by passing
state_dir to AgentFrameworkHost. The runner queue and the session
bookkeeping use diskcache
(installed via the [disk] extra) protected by an OS-level advisory file
lock so two hosts pointed at the same directory can't double-execute
scheduled pushes. Workflow checkpoints (when the target is a Workflow)
use the framework's FileCheckpointStorage — no extra dependency. The
identity-link store path is offered to linkers that implement
SupportsLinkStorePath; linkers that manage persistence themselves should
be configured directly.
from agent_framework_hosting import AgentFrameworkHost
# Single path → host auto-derives `runner/`, `sessions/`, `links/`, and
# (for workflow targets) `checkpoints/` subpaths.
host = AgentFrameworkHost(
target=agent,
channels=channels,
state_dir="./.host-state",
)
# Or route components to different roots — use the HostStatePaths TypedDict
# (or a plain dict with the same keys) for editor autocomplete on the keys.
# Omit a key to opt that component out of persistence.
from agent_framework_hosting import HostStatePaths
host = AgentFrameworkHost(
target=workflow,
channels=channels,
state_dir=HostStatePaths(
runner="/var/lib/myapp/tasks",
sessions="/var/lib/myapp/state",
checkpoints="/var/lib/myapp/checkpoints",
links="/var/lib/myapp/links",
),
)
What survives a restart:
- Pending durable-task records — scheduled but not-yet-completed push
deliveries replay on the next host startup via
runner.resume(). Records that crashed mid-attempt resume with their already-consumed retry budget. _session_aliases— per-isolation-key session-id rewrites (via the reset-session command)._active— the most recently active channel for each isolation key (consumed byResponseTarget.active)._identities— channel-nativeChannelIdentityrows used byResponseTarget.channels([...])/.all_linkedfan-out.- Workflow checkpoints — when the target is a
Workflow, the host wraps thecheckpointspath in a per-isolation-keyFileCheckpointStorage(equivalent to passingcheckpoint_location=...directly; the explicit parameter takes precedence and emits a warning when both are set). - Identity-link store — when the configured linker implements
SupportsLinkStorePath, the host passes thelinkspath to it so pending challenges, linked identities, and verified claims can survive restarts.
What doesn't:
- Live
AgentSessionobjects (rehydrated lazily by the history provider on the next turn). - The
ContinuationTokenstore (separate concern, plug in your own).
Unpicklable push payloads raise PushPayloadNotPicklable eagerly from
schedule() so issues surface at the call site, not on the next restart.