Python: Fix WorkflowAgent not persisting response messages to session history#4319
Python: Fix WorkflowAgent not persisting response messages to session history#4319moonbox3 merged 1 commit intomicrosoft:mainfrom
Conversation
… history (microsoft#1694) WorkflowAgent._run_impl() and _run_stream_impl() did not set session_context._response before calling _run_after_providers(). This caused InMemoryHistoryProvider.after_run() to see context.response as None, so response messages were never stored in the session. On subsequent runs, the workflow only received prior user inputs without assistant responses, breaking multi-turn conversations. Fix: Set session_context._response to the workflow result before running after_run providers, matching the behavior of the regular Agent class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 3 | Confidence: 83%
✓ Correctness
The diff correctly fixes a bug where session_context._response was not set before running after_run providers, causing InMemoryHistoryProvider to miss persisting assistant responses in multi-turn conversations. Both the non-streaming and streaming paths are handled. The non-streaming path straightforwardly assigns the already-computed result. The streaming path collects all updates and reconstructs the response via AgentResponse.from_updates. The tests are well-structured and cover non-streaming, streaming, and serialization roundtrip scenarios. No correctness issues found.
✓ Security Reliability
This diff fixes multi-turn conversation history by setting session_context._response before running after_run providers. The change is straightforward and well-tested. There are no injection risks, secrets, or unsafe deserialization issues. The main reliability concern is an asymmetry between the streaming and non-streaming paths: when the streaming path yields zero updates, _response is never set, yet _run_after_providers still executes, which could cause downstream providers to silently skip persistence or raise an AttributeError if they expect _response to exist. This is a minor edge-case reliability gap, not a blocking issue.
✓ Test Coverage
The three new tests cover the core non-streaming and streaming multi-turn history persistence, plus a serialization roundtrip, which directly exercises the production-code changes. However, the streaming path has an explicit
if all_updates:guard for the empty-stream edge case that is never tested, the streaming test omits text-content assertions (only checking roles), and none of the tests verify that the_responseobject set onsession_contextactually matches theAgentResponsereturned to the caller.
Suggestions
- In the streaming path, consider whether the
if all_updates:guard could silently hide issues where the workflow produces no output. If an empty response is unexpected, logging a warning might help with debugging. - The
# type: ignore[assignment]comments suggest_responsemay not be part of the public typed interface ofsession_context. Consider adding a proper setter or typed attribute to avoid relying on private attribute assignment. - In the streaming path, consider setting session_context._response to a sentinel or empty AgentResponse even when all_updates is empty, so that after_run providers behave consistently with the non-streaming path and don't encounter a missing _response attribute.
- Add a test for the empty-stream edge case (stream that yields zero updates) to cover the
if all_updates:guard on the streaming path and verify that_responseis not set / remains None. - The streaming test (
test_multi_turn_session_stores_responses_streaming) only asserts on roles. Add text-content assertions (similar to the non-streaming variant) to confirm the actual message payloads survive the round-trip. - Consider adding an assertion that the
AgentResponsereturned byagent.run()matches what ends up persisted in the session history, to ensuresession_context._responseis set to the correct value and not a stale or mismatched object.
Automated review by moonbox3's agents
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Fixes WorkflowAgent session history persistence so multi-turn sessions include prior assistant responses (addressing #1694), by ensuring the completed response is available to after-run context providers (e.g., InMemoryHistoryProvider) in both non-streaming and streaming runs.
Changes:
- Set
session_context._responsebefore invoking_run_after_providersin the non-streaming execution path. - In streaming, collect emitted
AgentResponseUpdates and reconstruct a finalAgentResponseviaAgentResponse.from_updates(...)so after-run providers can persist assistant messages. - Add tests covering multi-turn history persistence for non-streaming, streaming, and session serialize/deserialize round-trips.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
python/packages/core/agent_framework/_workflows/_agent.py |
Ensures after-run providers can persist assistant outputs by populating SessionContext._response for both streaming and non-streaming workflow runs. |
python/packages/core/tests/workflow/test_workflow_agent.py |
Adds regression tests validating assistant responses are persisted across turns (including streaming and session round-trip). |
Motivation and Context
When using
WorkflowAgentwith multi-turn sessions, assistant responses were never stored in session history. This meant that on subsequent turns, the workflow only received prior user inputs—not prior assistant responses—breaking conversational context across turns (issue #1694).Fixes #1694
Description
The root cause was that
session_context._responsewas never set before calling_run_after_providers, soInMemoryHistoryProvider(and other after-run providers) had no response to persist. The fix setssession_context._responseto the completedAgentResponsein both the non-streaming and streaming paths ofWorkflowAgent—in the streaming case, by collecting all yieldedAgentResponseUpdates and reconstructing the final response viaAgentResponse.from_updates. Three new tests verify multi-turn history persistence for non-streaming, streaming, and serialize/deserialize round-trip scenarios.Contribution Checklist