Python: add agent-framework-hosting-invocations channel#5640
Open
eavanvalkenburg wants to merge 6 commits intomicrosoft:feature/python-hostingfrom
Open
Python: add agent-framework-hosting-invocations channel#5640eavanvalkenburg wants to merge 6 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg wants to merge 6 commits intomicrosoft:feature/python-hostingfrom
Conversation
218bf5b to
835d95e
Compare
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>
835d95e to
09f789a
Compare
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>
moonbox3
reviewed
May 7, 2026
- 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>
09f789a to
ef2e880
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
Implements §7 ("Built-in channels — Invocations") of SPEC-002 (merged via #5549). Pairs with
ResponsesChannelto make anAgentFrameworkHostruntime-compatible with the Foundry Hosted Agents platform (per ADR §11).Description
Adds the new
agent-framework-hosting-invocationspackage (python/packages/hosting-invocations/) with:InvocationsChannel— mountsPOST /invocations/invoke(configurable), accepts the host-native JSON envelope used by Foundry Hosted Agents, and routes to the host's target.Stack
PR-4 of 9. Depends on #PR-2 (
feat/hosting-core). Independent of PR-3.Contribution Checklist