Files
agent-framework/docs/specs/002-python-hosting-channels.md
Eduard van Valkenburg 36ce0950e4 Simplify Python hosting core (#6492)
Remove linking, multicast, durable delivery, and host push machinery from the v1 hosting core. Keep those scenarios in a proposed follow-up ADR and update channel packages, samples, docs, tests, and workspace metadata around the smaller host/channel contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-12 08:34:08 +02:00

15 KiB

status, contact, date, deciders
status contact date deciders
proposed eavanvalkenburg 2026-06-11 eavanvalkenburg

Python hosting core and pluggable channels

Scope

This specification is the Python implementation plan for ADR-0027. It documents the simplified v1 host/channel contract only.

The v1 contract is:

  • AgentFrameworkHost owns one Starlette app, one hostable target, and one or more channels.
  • A hostable target is either a SupportsAgentRun-compatible agent or a Workflow.
  • Channels contribute routes, middleware, commands, and lifecycle callbacks.
  • Channels parse protocol-native input into ChannelRequest.
  • Channels render their own originating response.
  • Session continuity is explicit: a channel supplies ChannelSession(isolation_key=...), and the host resolves/caches an AgentSession for that key.
  • The host invokes ChannelRunHook and ChannelResponseHook; channels provide hook configuration and protocol context.

The host does not link identities, route responses to other channels, run background continuations, or multicast in v1. Those enhancements are tracked in ADR-0028.

Goals

  • Let an app expose one agent or workflow on multiple protocols without handwritten Starlette composition.
  • Keep protocol parsing and response formatting inside channel packages.
  • Provide one session-resolution path shared by all channels.
  • Keep the channel authoring surface small enough for new channels to implement.
  • Preserve full-fidelity agent and workflow results until a channel decides how to render them.

Non-goals for v1

The following are removed from the v1 implementation pass:

  • IdentityLinker, IdentityAllowlist, AuthPolicy, and LinkPolicy
  • ResponseTarget, active-channel routing, all_linked, fan-out, and multicast
  • ChannelPush and ChannelPushCodec
  • DurableTaskRunner, InProcessTaskRunner, and RetryPolicy
  • continuation tokens and background delivery
  • confidentiality tiers
  • agent-framework-hosting-entra
  • local_identity_link

These are follow-up design topics, not hidden requirements of the v1 host.

Packages

Package Import surface Contents
agent-framework-hosting agent_framework_hosting AgentFrameworkHost, channel protocols, key request/result types, hooks, reset_session, state-path helpers.
agent-framework-hosting-responses agent_framework_hosting_responses ResponsesChannel.
agent-framework-hosting-invocations agent_framework_hosting_invocations InvocationsChannel.
agent-framework-hosting-telegram agent_framework_hosting_telegram TelegramChannel and Telegram command helpers.
agent-framework-hosting-activity-protocol agent_framework_hosting_activity_protocol ActivityProtocolChannel for Activity Protocol over Azure Bot Service.
agent-framework-hosting-discord agent_framework_hosting_discord DiscordChannel and Discord command/interaction helpers.
agent-framework-foundry-hosting agent_framework.foundry_hosting Foundry isolation middleware and Foundry-backed hosting helpers usable with the v1 host.

Channel packages may depend on their native SDKs. The core hosting package should not depend on channel SDKs or on top-level legacy protocol hosts.

Key Types

AgentFrameworkHost

The host constructor accepts:

  • target: one SupportsAgentRun-compatible object or one Workflow
  • channels: one or more Channel instances
  • optional Starlette middleware
  • optional state_dir
  • optional workflow checkpoint_location

The host exposes:

  • app: the canonical Starlette ASGI application
  • serve(...): a convenience wrapper for local serving
  • reset_session(isolation_key: str): rotate the cached AgentSession for a host-tracked conversation

state_dir is narrowed to v1 host-owned local files only:

  • session aliases (isolation_key to current AgentSession id), and
  • workflow checkpoint paths when the app chooses the host-provided file layout.

It is not a store for identity links, continuations, active-channel state, delivery attempts, or multicast payloads.

Externally supplied isolation keys are trusted only after the channel or host middleware has authenticated and authorized the caller. The host uses isolation_key as a partition key; the string itself is not proof of identity or ownership.

Channel

A channel implements a small protocol:

  • declare a stable channel id/name,
  • contribute routes, middleware, commands, and lifecycle callbacks,
  • parse inbound protocol data into ChannelRequest,
  • call the host through ChannelContext.run(...) or ChannelContext.run_stream(...), and
  • serialize the returned result to the originating protocol response.

Channels own protocol authentication, signature validation, native command registration, and protocol-specific error bodies.

ChannelContribution

ChannelContribution is the channel's host-facing contribution:

  • Starlette routes and optional middleware,
  • native command descriptors,
  • startup and shutdown callbacks, and
  • any channel-local metadata needed by the package.

The host aggregates contributions but does not interpret protocol payloads.

ChannelRequest

ChannelRequest is the host-neutral request envelope produced by a channel. It carries:

  • target input,
  • optional ChannelSession,
  • optional ChannelIdentity,
  • options and attributes produced by the channel, and
  • request metadata useful to hooks and context providers.

The host may pass attributes through to context providers and middleware. Channels should treat attributes as a documented extension bag, not as a cross-channel delivery contract.

ChannelSession

ChannelSession(isolation_key=...) is the only v1 session-continuity mechanism.

When a request contains an isolation key:

  1. The host looks up or creates the cached AgentSession for that key.
  2. The target runs with that AgentSession when the target is an agent.
  3. reset_session(isolation_key) rotates the alias so the next request starts a new conversation.

If two channels produce the same isolation key on the same host, they share the same cached session. If they produce different keys, they do not share session state.

ChannelIdentity

ChannelIdentity is optional request metadata such as channel id, native user id, tenant id, claims, or display attributes.

In v1, ChannelIdentity does not link channels, authorize callers, select delivery destinations, or imply that two identities should share an AgentSession. A channel that wants shared history must still produce the same ChannelSession.isolation_key.

Hooks

Hooks are optional and channel-owned:

  • ChannelRunHook: runs after channel parsing and before host invocation; returns the ChannelRequest to execute.
  • ChannelResponseHook: runs after target completion and before the originating channel renders a one-shot response.
  • ChannelStreamUpdateHook: the host applies it to streamed updates before the originating channel serializes the stream.

Common uses include adapting chat text into workflow inputs, enforcing deployment-specific options, flattening rich output for text-only protocols, or filtering streamed updates for a protocol. Stream update hooks are update-only; they do not automatically sanitize get_final_response() output. Channels choose their response transport from the parsed protocol request before invoking run hooks.

HostedRunResult

HostedRunResult[T] wraps the target's full-fidelity result plus the resolved AgentSession | None.

  • Agent targets produce HostedRunResult[AgentResponse].
  • Workflow targets produce HostedRunResult[WorkflowRunResult].

The host does not flatten, filter, or translate the result. Each channel decides how much of the result its protocol can carry.

Host Behavior

  1. AgentFrameworkHost builds one Starlette app and asks each channel for its contribution.
  2. A channel route receives a protocol-native request.
  3. The channel validates/parses the native payload and creates ChannelRequest.
  4. The channel passes the request, optional ChannelRunHook, and protocol-native context to the host.
  5. The host invokes ChannelRunHook, if configured, and receives the prepared request.
  6. The host resolves an AgentSession from ChannelSession.isolation_key when present.
  7. The host invokes the agent or workflow target.
  8. The host wraps the result in HostedRunResult or the streaming equivalent.
  9. The host invokes ChannelResponseHook, if configured, for non-streaming/final response shaping.
  10. The host applies stream update hooks while the channel consumes streams; the channel renders the originating protocol response.

There is no host-level route from one channel's request to another channel's response in v1.

Workflow Checkpoints

Workflow checkpointing is explicit. Apps either configure checkpoint storage on the workflow itself or pass a checkpoint_location to the host so the workflow dispatch path can use the intended file location.

state_dir may provide a conventional location for workflow checkpoint files, but checkpointing is still opt-in and separate from agent session history. Checkpoints are workflow-runtime state, not channel state and not identity-link state.

Foundry Isolation Middleware

V1 keeps Foundry isolation as middleware rather than as a channel-linking feature.

The middleware is installed only when the Foundry hosting environment flag is present. In that environment it reads Foundry-provided isolation values at the trusted hosting boundary, exposes them as read-only request context for Foundry-aware history or memory providers, and rejects unsafe session resumes when the live isolation context does not match persisted session context. Outside Foundry, raw isolation headers are ignored unless an app supplies its own trusted middleware.

This middleware does not create cross-channel identity links and does not authorize non-Foundry channels.

Current Channels

Responses

ResponsesChannel exposes the OpenAI-compatible Responses API shape. It maps request body fields such as input, options, and conversation identifiers into ChannelRequest, and it renders Responses-compatible one-shot or streaming responses.

Responses session continuity uses a channel-selected isolation_key, commonly derived from a response/conversation id, caller-provided session id, Foundry isolation context, or deployment-specific request metadata.

Invocations

InvocationsChannel exposes an invocation endpoint for server-side callers and tools. It maps the request body into ChannelRequest and renders the invocation result on the same HTTP response.

Invocations is useful for typed workflow inputs because a ChannelRunHook can translate the request body into the workflow's expected input type.

Telegram

TelegramChannel supports webhook or polling transport, native command registration, and message rendering back to the originating Telegram chat.

The channel chooses a default isolation_key from Telegram-native data such as chat id, user id, or a configured user/chat scope. A /new or equivalent command may call reset_session for that isolation key.

Activity Protocol

ActivityChannel supports Activity Protocol requests, typically through Azure Bot Service for Teams, Web Chat, and other Bot Framework-fronted surfaces.

The channel maps incoming Activity objects to ChannelRequest and renders a reply activity to the originating conversation. Proactive Activity delivery, active-channel routing, and all-linked fan-out are not v1 host semantics.

Discord

DiscordChannel supports Discord messages, slash commands, and interactions as channel-native input.

The channel maps Discord-native user, guild, channel, thread, and interaction data into ChannelRequest metadata and a configured ChannelSession.isolation_key. It renders the result to the originating Discord response path.

High-level Samples

One agent on Responses

host = AgentFrameworkHost(
    target=agent,
    channels=[ResponsesChannel()],
)

app = host.app

One agent on multiple channels

host = AgentFrameworkHost(
    target=agent,
    channels=[
        ResponsesChannel(),
        InvocationsChannel(),
        TelegramChannel(bot_token=os.environ["TELEGRAM_BOT_TOKEN"]),
    ],
)

host.serve(host="localhost", port=8000)

The host owns one Starlette app. Each channel contributes its own routes and renders its own response.

Adapting a request before execution

from dataclasses import replace


def enforce_options(request: ChannelRequest) -> ChannelRequest:
    options = dict(request.options or {})
    options["temperature"] = 0
    return replace(request, options=options)


host = AgentFrameworkHost(
    target=agent,
    channels=[ResponsesChannel(run_hook=enforce_options)],
)

Workflow with explicit checkpoints

host = AgentFrameworkHost(
    target=workflow,
    channels=[InvocationsChannel(run_hook=adapt_to_workflow_input)],
    checkpoint_location=Path("./.af-hosting/workflow_checkpoints"),
)

The hook adapts channel-native input to the workflow's typed input. Checkpoints use the explicit workflow checkpoint location, not identity-link or delivery storage.

Message channel reset command

async def new_chat(context):
    if context.request.session is not None:
        await context.host.reset_session(context.request.session.isolation_key)
        await context.reply("Started a new conversation.")

Telegram, Activity Protocol, and Discord can expose equivalent native commands when their protocols support them.

Follow-up Enhancements

See ADR-0028 for the deferred design covering:

  • cross-channel identity linking,
  • authorization and allowlists,
  • non-originating response delivery,
  • active-channel routing,
  • multicast and all-linked delivery,
  • background runs and continuation tokens,
  • durable delivery runners,
  • retry/replay semantics, and
  • payload serialization.

Those enhancements must layer on top of this v1 contract without requiring v1 users to adopt them.

Validation Gates

The Python implementation should be considered complete when:

  • a sample uses one AgentFrameworkHost with multiple channels and no manual Starlette route composition,
  • each current channel has contract tests for route contribution, lifecycle, request parsing, hooks, and originating response rendering,
  • session tests prove shared isolation_key values share an AgentSession and reset_session rotates it,
  • workflow tests or samples use explicit checkpoint_location,
  • Foundry isolation middleware is covered by integration or contract tests,
  • no v1 package exposes the removed linking, multicast, durable-runner, or continuation APIs, and
  • this spec and ADR-0027 remain aligned.