Python: Fix ResponsesChannel session continuity and harden _result_to_text for workflows#6642
Open
Ashutosh0x wants to merge 8 commits into
Open
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…_text for workflows Fixes three issues flagged by automated review on PR microsoft#6580: 1. Session isolation key continuity (Bugs 2 & 3): When no external session is provided, the fallback isolation key now prefers `previous_response_id` over the current `response_id`. This ensures checkpoint storage, FileHistoryProvider, and other session-scoped stores find data written during the preceding turn instead of silently starting fresh. 2. Workflow result rendering (Bug 1): `_result_to_text` now handles WorkflowRunResult objects whose `get_outputs()` returns an empty list by falling back to `get_final_state()` before the generic `str()` cast. 3. Added tests for empty-output workflow results, final-state fallback, and plain-string rendering.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves the Python local hosting stack by ensuring Responses-session continuity across turns (so per-session stores like history/checkpoints can be reused) and by hardening non-streaming Responses rendering for workflow targets (so workflows without emitted outputs still produce a usable Responses envelope). It also introduces the initial agent-framework-hosting and agent-framework-hosting-responses packages with tests and local samples.
Changes:
- Fix ResponsesChannel session fallback to use
previous_response_id or response_idas the isolation key when no session is otherwise provided. - Harden
_result_to_textto fall back toget_final_state()when a workflow result has no outputs. - Add new hosting/hosting-responses packages, unit tests, and local samples demonstrating agent + workflow hosting.
Reviewed changes
Copilot reviewed 35 out of 37 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| python/uv.lock | Adds workspace entries and dependencies for new hosting packages (incl. diskcache). |
| python/pyproject.toml | Registers hosting packages in the workspace and pyright env list. |
| python/PACKAGE_STATUS.md | Marks new hosting packages as alpha. |
| python/samples/04-hosting/af-hosting/README.md | Documents the new local multi-channel hosting samples and their relationship to Foundry-hosted samples. |
| python/samples/04-hosting/af-hosting/local_responses/README.md | Documents the minimal Responses hosting sample and run hook behavior. |
| python/samples/04-hosting/af-hosting/local_responses/pyproject.toml | Sample project wiring for local Responses hosting sample. |
| python/samples/04-hosting/af-hosting/local_responses/call_server.py | Local client for exercising the Responses endpoint. |
| python/samples/04-hosting/af-hosting/local_responses/app.py | Starlette app for the minimal agent + ResponsesChannel sample. |
| python/samples/04-hosting/af-hosting/local_responses_workflow/storage/checkpoints/.gitkeep | Placeholder for sample checkpoint storage directory. |
| python/samples/04-hosting/af-hosting/local_responses_workflow/README.md | Documents workflow hosting sample with structured intake and checkpoints. |
| python/samples/04-hosting/af-hosting/local_responses_workflow/pyproject.toml | Sample project wiring for workflow hosting sample. |
| python/samples/04-hosting/af-hosting/local_responses_workflow/call_server.rest | REST examples for the workflow sample endpoint. |
| python/samples/04-hosting/af-hosting/local_responses_workflow/call_server.py | Python client for exercising workflow sample via OpenAI SDK. |
| python/samples/04-hosting/af-hosting/local_responses_workflow/app.py | Workflow sample Starlette app demonstrating typed intake + checkpointing. |
| python/packages/hosting/README.md | New package docs for channel-neutral hosting primitives and state/checkpoint behavior. |
| python/packages/hosting/pyproject.toml | New hosting package definition and dependency set. |
| python/packages/hosting/LICENSE | License file for the new hosting package. |
| python/packages/hosting/agent_framework_hosting/_types.py | Defines channel-neutral request/envelope types and hook protocols. |
| python/packages/hosting/agent_framework_hosting/_state_store.py | Implements disk-backed session-alias persistence using optional diskcache. |
| python/packages/hosting/agent_framework_hosting/_persistence.py | Adds shared persistence helpers (state_dir normalization + locking). |
| python/packages/hosting/agent_framework_hosting/_isolation.py | Adds isolation-key contextvar surface for Foundry-style headers. |
| python/packages/hosting/agent_framework_hosting/_host.py | Implements AgentFrameworkHost, middleware wiring, session caching, checkpoint wiring, and stream adapters. |
| python/packages/hosting/agent_framework_hosting/init.py | Exposes the hosting package public API surface. |
| python/packages/hosting/tests/hosting/test_types.py | Tests for channel-neutral envelope types. |
| python/packages/hosting/tests/hosting/test_isolation.py | Tests for isolation header/contextvar behavior and middleware reset semantics. |
| python/packages/hosting/tests/hosting/test_host.py | Large suite covering host routing, sessions, hooks, workflow bridging, and checkpoint path validation. |
| python/packages/hosting/tests/hosting/test_host_disk.py | Tests for state_dir behavior with optional disk-backed session aliasing. |
| python/packages/hosting/tests/hosting/conftest.py | Loads workflow fixtures for hosting tests via an import hook. |
| python/packages/hosting/tests/hosting/_workflow_fixtures.py | Workflow fixture builders used by host tests. |
| python/packages/hosting-responses/README.md | New channel package docs for the Responses-shaped surface. |
| python/packages/hosting-responses/pyproject.toml | New hosting-responses package definition and dependency set. |
| python/packages/hosting-responses/LICENSE | License file for the new hosting-responses package. |
| python/packages/hosting-responses/agent_framework_hosting_responses/_parsing.py | Parses Responses request bodies into AF Messages/options/session. |
| python/packages/hosting-responses/agent_framework_hosting_responses/_channel.py | Implements the ResponsesChannel HTTP endpoint, SSE streaming, and result rendering. |
| python/packages/hosting-responses/agent_framework_hosting_responses/init.py | Exposes the responses channel package public API surface. |
| python/packages/hosting-responses/tests/hosting_responses/test_parsing.py | Unit tests for request parsing helpers. |
| python/packages/hosting-responses/tests/hosting_responses/test_channel.py | End-to-end tests for ResponsesChannel behavior, streaming, and result text rendering. |
Comment on lines
+25
to
+28
| dependencies = [ | ||
| "agent-framework-core>=1.2.0,<2", | ||
| "starlette>=0.37", | ||
| ] |
Comment on lines
+25
to
+29
| dependencies = [ | ||
| "agent-framework-core>=1.2.0,<2", | ||
| "agent-framework-hosting==1.0.0a260424", | ||
| "openai>=1.99.0,<3", | ||
| ] |
Comment on lines
+121
to
+122
| else: | ||
| parts = [] |
Comment on lines
+141
to
+145
| try: | ||
| body = await request.json() | ||
| except Exception: | ||
| return JSONResponse({"error": "invalid json"}, status_code=400) | ||
|
|
Comment on lines
+40
to
+44
| previous_response_id=previous_response_id, | ||
| ) | ||
| print(f"User: {prompt}") | ||
| print(f"Agent: {response.output_text}") | ||
|
|
Comment on lines
+908
to
+914
| """The host walks ``target.context_providers``, descends one level | ||
| when a provider exposes a ``providers`` attribute, and calls | ||
| ``bind_request_context(response_id=..., previous_response_id=...)`` | ||
| on every provider that supports it. Foundry response-id chaining | ||
| plugs into this exact seam — a regression that mistypes the kwarg | ||
| name, drops the descent, or fails to keep the binding open across | ||
| the agent run silently breaks chained writes.""" |
Comment on lines
+64
to
+66
| """Return a deterministic weather report for a city.""" | ||
| high_temp = randint(5, 25) | ||
| reports = { |
…terministic sample, response.id print - Add body type validation in _channel.py (reject non-object JSON with 422) - Reject invalid message content types in _parsing.py instead of silent empty - Make lookup_weather deterministic via location hash instead of randint - Print response.id in call_server.py for multi-turn chain usability
This was referenced Jun 20, 2026
Open
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
Fixes three issues flagged by automated DevFlow review on #6580:
Session isolation key continuity - In local (non-Foundry) hosting, the first turn's checkpoint/history data is stored under the initial
response_id, but subsequent turns mint a newresponse_idas their isolation key. Checkpoint storage,FileHistoryProvider, and other session-scoped stores can't find data from the previous turn, silently dropping conversation state across turns.Non-streaming workflow result rendering -
ResponsesChannel._handlecalls_result_to_text(result.result)on line 225. When the target is a workflow,result.resultis aWorkflowRunResultwhoseget_outputs()may return an empty list. The current_result_to_textjoins the empty list into''', yielding a blank Responses envelope. Workflows that carry their final state onget_final_state()instead of through output executors are unrenderable.Local sample session bootstrap - The
local_responses/app.pyandlocal_responses_workflow/app.pysamples useFileHistoryProvider/ checkpoint storage respectively, but the first turn's session is bootstrapped with a per-requestresponse_idas isolation key.Description
_channel.py- Session continuity fix: When no externally-provided session exists, the fallback now usesprevious_response_id or response_idas the isolation key._channel.py-_result_to_texthardening: Whenget_outputs()returns an empty list, falls back toget_final_state()before the genericstr()cast.test_channel.py- New tests: Added 3 tests for empty-output workflow results, final-state fallback, and plain-string rendering.Contribution Checklist