.NET: fix InMemoryChatHistoryProvider persisting when service stores history#7284
Merged
westey-m merged 4 commits intoJul 24, 2026
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a regression in .NET ChatClientAgent chat history handling so the default InMemoryChatHistoryProvider does not persist messages into the session when the backing service manages history server-side (e.g., via a returned ConversationId), avoiding double-storage and unintended history replay across turns.
Changes:
- Update
ChatClientAgent.ResolveChatHistoryProviderto consider bothsession.ConversationIdandchatOptions.ConversationIdwhen deciding to disengage chat history persistence for service-stored history scenarios. - Thread the session through internal provider notification/load call sites (
NotifyProvidersOfNewMessagesAsync,NotifyProvidersOfFailureAsync,LoadChatHistoryAsync) to make the resolution decision accurate on first turn. - Add regression tests covering non-streaming, streaming, and multi-turn behavior for service-stored history, plus an explicit-provider behavior pin.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs | Adjusts chat history provider resolution to prevent persisting local history when the service conversation id indicates server-side history management. |
| dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs | Adds regression tests ensuring the default in-memory provider is not populated when the service returns a conversation id, including streaming and multi-turn scenarios. |
peibekwe
approved these changes
Jul 23, 2026
rogerbarreto
approved these changes
Jul 24, 2026
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 & Context
When running against a service that stores chat history server-side (e.g. the OpenAI Responses API), the framework's default
InMemoryChatHistoryProviderwas still persisting messages into the session, so history ended up stored in two places. The session was expected to only carry the service conversation id in this case.The root cause was in
ChatClientAgent.ResolveChatHistoryProvider: it decided whether to disengage the chat history provider based solely onchatOptions.ConversationId. That value is only seeded at the start of a run (fromsession.ConversationId) and is never refreshed at end-of-run, whereas the service conversation id lands onsession.ConversationIdonly after the response. So on the first turn against a service-stored-history service, the provider stayed engaged and persisted the messages. This was a regression introduced by #4974, which changed the check fromsession.ConversationIdtochatOptions.ConversationId.Description & Review Guide
What are the major changes?
ResolveChatHistoryProvidernow takes the session and disengages any chat history provider when a service conversation id is present on either the session or the chat options (excluding the AG-UI provider and the per-service-call persistence path). ThechatOptions-only check is replaced by a uniformsession || optionscheck.NotifyProvidersOfNewMessagesAsync,NotifyProvidersOfFailureAsync,LoadChatHistoryAsync) now pass the session through, and the override-provider conflict branch keys off the same computed value.RunAsyncandRunStreamingAsync), a multi-turn service-stored-history test that also asserts prior turns are not replayed, and an explicit-provider test.What is the impact of these changes?
Throw/Clear/Warn OnChatHistoryProviderConflictallfalse) is now also disengaged when the service returns a conversation id (previously it persisted the first turn only). Conflict detection (Throw/Warn/Clear) is unchanged, as it runs before persistence.What do you want reviewers to focus on?
PerServiceCallChatHistoryPersistingChatClient: it calls back into these methods precisely when per-service-call persistence is active and stamps a sentinel ontosession.ConversationId, so the!RequiresPerServiceCallChatHistoryPersistencegate is load-bearing (verified: removing it breaks the per-service-call tests).Related Issue
Fixes #6120
PR #6209 also targets this issue. This PR differs by disengaging any provider uniformly when a service conversation id is present (on the session or the options) and does not introduce a
_usesImplicitDefaultChatHistoryProviderdistinction between the default and an explicitly-configured provider.Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.