Skip to content

Python: add agent-framework-hosting core package#5638

Open
eavanvalkenburg wants to merge 2 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-core
Open

Python: add agent-framework-hosting core package#5638
eavanvalkenburg wants to merge 2 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-core

Conversation

@eavanvalkenburg
Copy link
Copy Markdown
Member

Motivation and Context

Implements §4 (Core abstractions) and §5 (AgentFrameworkHost) of SPEC-002 (merged via #5549). This is the foundation package every channel (Responses, Invocations, Activity Protocol, Teams, Telegram, Entra link) imports from.

Description

Adds the new agent-framework-hosting package (python/packages/hosting/) with:

  • Channel protocol — the contract every channel package implements (name, contribute(), optional on_init/on_close lifecycle hooks).
  • AgentFrameworkHost — Starlette ASGI app that wires together a target (Agent, Workflow, or callable) with one or more channels, handles isolation-key threading, identity normalization, and run hooks per-channel.
  • ChannelRequest / ChannelResponse envelopes — the uniform structure each channel transforms its protocol into before invoking the target.
  • ChannelSession + isolation_key plumbing for per-conversation history bucketing.
  • Optional workflow-target adapter so a Workflow can be the host's target alongside Agent.
  • tests/ with test_host.py, test_types.py, and shared fixtures.

Workspace registration + lockfile entry so the package builds in CI.

Stack

PR-2 of 9 — the foundation. All channel PRs (PR-3..PR-7) and the samples PR (PR-8) depend on this landing first.

# Branch Status
PR-1 refactor/foundry-hosted-agent-history-provider independent
PR-2 feat/hosting-core this PR — needed by PR-3..PR-8
PR-3..7 per-channel packages block on this
PR-8 feat/hosting-samples blocks on this + PR-3..7

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No — new package.

@moonbox3 moonbox3 added documentation Improvements or additions to documentation python labels May 5, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 3 | Confidence: 90%

✓ Correctness

This PR introduces a well-structured multi-channel hosting package for the Python Agent Framework. After verifying the key integration points against the core framework source (Workflow.run signatures, ResponseStream behavior, Message constructor, FileCheckpointStorage, RunerContext.has_checkpointing), I found no correctness bugs. The checkpoint handling, stream bridging, response delivery routing, and isolation middleware all use the framework APIs correctly. Edge cases (double-finalization in workflow streams, idempotent ExitStack close, session caching) are properly handled.

✓ Test Coverage

The new agent-framework-hosting package introduces significant functionality in three modules (_host.py, _isolation.py, _types.py) with tests in test_host.py and test_types.py. Test coverage for host wiring, agent invocation, sessions, workflow targets, checkpointing, and delivery routing is solid. However, there are three notable gaps: (1) _isolation.py has zero dedicated tests — the IsolationKeys dataclass, the contextvar get/set/reset helpers, and critically the _FoundryIsolationASGIMiddleware that lifts HTTP headers into the contextvar are all untested; (2) apply_run_hook (exported utility for channels to invoke run hooks supporting both sync and async callables) has no test; (3) _wrap_input is tested only for the str input path but the Message and list[Message] branches (which mutate additional_properties on existing objects) are not exercised.

✗ Design Approach

I found two design-level issues that warrant changes before this host API is safe to build channels on. First, the host reuses a single live Workflow object for every request, but core workflows explicitly preserve in-memory state across run() calls, so new conversations can inherit prior conversation state whenever there is no per-conversation restore to overwrite it. Second, ResponseTarget.active is resolved from _active, but the host updates _active during run()/run_stream() before channels can call deliver_response(), which makes the current inbound channel look active and breaks the intended cross-channel routing behavior.


Automated review by eavanvalkenburg's agents

Comment thread python/packages/hosting/tests/test_types.py
New ``agent-framework-hosting`` package implementing ADR 0026 / SPEC-002:
the channel-neutral host that lets a single ``Agent`` (or ``Workflow``)
fan out across multiple wire protocols ("channels") behind one Starlette
ASGI app.

Surface (re-exported from ``agent_framework_hosting``):

- ``AgentFrameworkHost`` — wraps a hostable target, mounts channels onto
  an ASGI app, owns per-isolation-key ``AgentSession`` reuse, threads
  request context (``response_id`` / ``previous_response_id``) into
  context providers via an ``ExitStack`` of ``bind_request_context``
  calls, and exposes an opt-in Hypercorn ``serve()`` helper (extra
  ``[serve]``).
- ``Channel`` protocol + ``ChannelContribution`` — the surface a channel
  package implements (routes, lifespans, identity hooks, …).
- ``ChannelRequest`` / ``ChannelSession`` / ``ChannelIdentity`` /
  ``ChannelPush`` / ``ChannelCommand[Context]`` / ``ChannelRunHook`` /
  ``ChannelStreamTransformHook`` / ``DeliveryReport`` /
  ``HostedRunResult`` / ``ResponseTarget`` / ``ResponseTargetKind`` /
  ``apply_run_hook`` — channel-side dataclasses + helpers.
- ``IsolationKeys`` + ``ISOLATION_HEADER_USER`` / ``..._CHAT`` +
  ``get/set/reset_current_isolation_keys`` — the host's ASGI middleware
  reads the ``x-agent-{user,chat}-isolation-key`` headers off each
  inbound request and exposes them to the agent stack via a
  ``ContextVar`` so storage-side providers (e.g.
  ``FoundryHostedAgentHistoryProvider``) can apply per-tenant
  partitioning without channels having to forward anything.

Includes 45 unit tests covering the host, channel contributions,
isolation contextvar, and shared types. Registers the package in
``python/pyproject.toml`` ``[tool.uv.sources]`` and adds the matching
pyright ``executionEnvironments`` entry for tests.

Hypercorn is an optional dependency (``[serve]`` extra); the soft import
in ``serve()`` is annotated for pyright since it isn't on the default
install.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread python/packages/hosting/agent_framework_hosting/_host.py Outdated
Comment thread python/packages/hosting/agent_framework_hosting/_host.py Outdated
Comment thread python/packages/hosting/agent_framework_hosting/_host.py
Comment thread python/packages/hosting/agent_framework_hosting/_host.py
Comment thread python/packages/hosting/agent_framework_hosting/_host.py
Comment thread python/packages/hosting/agent_framework_hosting/_host.py
Comment thread python/packages/hosting/agent_framework_hosting/_host.py
Comment thread python/packages/hosting/agent_framework_hosting/_host.py
Comment thread python/packages/hosting/agent_framework_hosting/_isolation.py
Comment thread python/packages/hosting/agent_framework_hosting/_types.py
Source-code changes
- _suppress_already_consumed: narrow contract — RuntimeError now logs
  at WARNING with exc_info; non-RuntimeError still logs at exception().
  Docstring clarifies that any non-clean teardown is observable.
- _BoundResponseStream: add aclose() and route __await__ through
  get_final_response() so the binding is always released — fixes
  contextvar leak when channels abandon the stream or use the
  await-the-stream convenience.
- Lifespan: aggregate startup/shutdown callback errors; every callback
  runs, all failures are logged with their qualname, and the first
  error is re-raised so Starlette still aborts boot.
- _build_run_kwargs: switch session-cache write to dict.setdefault so
  concurrent racers cannot orphan a session if create_session ever
  yields.
- _deliver_response: introduce DeliveryReport.failed for push outages
  vs explicit "no link" drops; an outage no longer triggers an
  originating fallback so the channel can decide degraded behaviour.

Test additions
- tests/test_isolation.py (new): full coverage of IsolationKeys, the
  contextvar helpers, header constants, and end-to-end ASGI
  middleware lift / reset / passthrough.
- tests/test_host.py: TestBindRequestContext, TestBoundResponseStream
  (aclose / __await__ / __getattr__ forwarding / double-close
  idempotency), TestWrapInputListMessages (list[Message] LAST
  precedence), TestLifespanAggregation (startup + shutdown).
- tests/test_types.py: TestApplyRunHook (sync/async/None), and
  TestDeliveryReport (new failed field).
- Updated test_push_exception_marks_skipped ->
  test_push_exception_lands_in_failed_no_fallback to match the new
  delivery contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants