Skip to content

Python: add agent-framework-hosting-invocations channel#5640

Open
eavanvalkenburg wants to merge 6 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-channel-invocations
Open

Python: add agent-framework-hosting-invocations channel#5640
eavanvalkenburg wants to merge 6 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-channel-invocations

Conversation

@eavanvalkenburg
Copy link
Copy Markdown
Member

Motivation and Context

Implements §7 ("Built-in channels — Invocations") of SPEC-002 (merged via #5549). Pairs with ResponsesChannel to make an AgentFrameworkHost runtime-compatible with the Foundry Hosted Agents platform (per ADR §11).

Description

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

  • InvocationsChannel — mounts POST /invocations/invoke (configurable), accepts the host-native JSON envelope used by Foundry Hosted Agents, and routes to the host's target.
  • Streaming + non-streaming variants.
  • Tests covering channel construction and end-to-end invocation handling.

Stack

PR-4 of 9. Depends on #PR-2 (feat/hosting-core). Independent of PR-3.

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: 82% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage


Automated review by eavanvalkenburg's agents

@eavanvalkenburg eavanvalkenburg force-pushed the feat/hosting-channel-invocations branch from 218bf5b to 835d95e Compare May 5, 2026 09:00
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>
@eavanvalkenburg eavanvalkenburg force-pushed the feat/hosting-channel-invocations branch from 835d95e to 09f789a Compare May 5, 2026 09:10
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>
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 Outdated
eavanvalkenburg and others added 4 commits May 7, 2026 16:09
- Refactor workflow checkpoint restoration into shared helpers
  (_restore_workflow_checkpoint for blocking; the streaming sibling
  drains the rehydration stream) so the blocking and streaming paths
  rehydrate identically — clarifies the previously inline _maybe_restore
  by hoisting the pattern next to the blocking call site.
- Document that blocking workflow output is text-only by design;
  richer modalities ride the streaming AgentResponseUpdate channel,
  which preserves all content parts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These review comments were filed on PR-4 (microsoft#5640) but target lines that
live in the hosting-core package (PR-2 / microsoft#5638), so the fixes land here
and PR-4's stack will pick them up on rebase.

- _suppress_already_consumed: narrow the RuntimeError catch to the two
  documented benign messages (`Inner stream not available`, `Event loop
  is closed`); any other RuntimeError now logs at ERROR with a full
  traceback so executor bugs / runner-context state errors / checkpoint
  RuntimeErrors during the post-run flush no longer masquerade as
  benign cleanup noise. Still no propagation (we're in an
  async-generator finally during teardown) — see the docstring.
- _restore_workflow_checkpoint{,_streaming}: log a WARNING when a
  non-None latest checkpoint drains to zero events, so a stale or
  partially-written checkpoint_id surfaces as an operator signal
  instead of a silent state-loss.

(The `deliver_response` "no destinations resolvable" vs "every
destination errored" concern raised in 3198268038 is already addressed
by the existing `failed` vs `skipped` distinction surfaced through
`DeliveryReport.failed` — see lines 1080-1102 and the
`DeliveryReport` docstring.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New ``agent-framework-hosting-invocations`` package implementing the
"Invocations" HTTP channel for the Hosting framework -- a lightweight
JSON-over-HTTP shape (``POST /invocations``) for callers that want a
single request/response without committing to the full OpenAI Responses
envelope. Mounts onto an ``AgentFrameworkHost`` like any other channel.

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

- ``InvocationsChannel`` -- concrete ``Channel`` implementation. Owns
  the Starlette route, parses inbound JSON into a ``ChannelRequest``
  (``input`` / ``session`` / ``metadata`` / ``options``), runs the
  optional ``ChannelRunHook``, calls back into the ``ChannelContext``
  to invoke the agent target, and returns a flat JSON envelope (or an
  SSE stream when ``stream=true``).
- 8 unit tests covering route wiring, isolation-key passthrough, hook
  composition, sync vs streaming paths, and ack-only behaviour for
  non-originating ``DeliveryReport``s.

Registers the package in ``python/pyproject.toml`` ``[tool.uv.sources]``
and adds the matching pyright ``executionEnvironments`` entry.

Independent of PR-3 (Responses); both depend only on PR-2 (Hosting
core).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- expand `_stream` docstring to call out the HTTP-200 + `event: error`
  SSE contract (status committed before generator runs; hard failures
  surface as the first SSE frame, not an HTTP code)
- split chunked text on full-line terminators via `splitlines()` so
  embedded `\r` / `\r\n` no longer leak into `data:` framing on the
  wire, breaking EventSource consumers
- on `get_final_response()` failure, emit `event: error` instead of
  silently swallowing — finalize is what triggers
  history-provider persistence on the agent side, so a 5xx /
  disk-full / context-provider error must reach the client
- add tests covering `stream_transform_hook` (rewrite, drop, async),
  CRLF-in-chunk framing, and the finalize-error → no-`[DONE]` contract

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eavanvalkenburg eavanvalkenburg force-pushed the feat/hosting-channel-invocations branch from 09f789a to ef2e880 Compare May 7, 2026 14:21
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