microsoft / microsoft/agent-framework
.NET: CompactionProvider stamps input messages in-place with ChatHistory, causing DefaultExcludeChatHistoryFilter to drop the first user message
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
# [Bug]: CompactionProvider stamps input messages in-place with ChatHistory source type, causing DefaultExcludeChatHistoryFilter to drop the first user message on the next turn
## Description
When `CompactionProvider.InvokingCoreAsync` restores state from `ProviderSessionState`, it stamps all cached message objects with `AgentRequestMessageSourceType.ChatHistory` by modifying `AdditionalProperties` **in-place** (without cloning).
Because `AgentSessionStateBagValue` caches the deserialized object reference, the stamped `ChatMessage` instance is the **same object** referenced by `inputMessagesForChatClient` in `ChatClientAgent.RunCoreAsync`. When `NotifyProvidersOfNewMessagesAsync` later applies `DefaultExcludeChatHistoryFilter`, the first user message is silently excluded from `InMemoryChatHistoryProvider` storage. On the next turn, the message is permanently lost — both from the provider and from serialized sessions.
## To Reproduce
Key configuration that triggers the bug:
```csharp
var finalClient = baseAgent.ChatClient.AsBuilder()
.UseNonApprovalRequiredFunctionBypassing()
.UseFunctionInvocation()
.UseAIContextProviders(new CompactionProvider(new PipelineCompactionStrategy()))
.Build();
```
Steps:
1. Create a `ChatClientAgent` with `InMemoryChatHistoryProvider` and the pipeline above
2. Send a message that triggers two tool calls
3. Check the ChatHistoryProvider state — the first user message is missing
4. Send a second message — the agent has no record of the first message
## Observed Behavior
After Turn 1 with tool calls:
- ChatHistoryProvider stores only the response messages (assistant tool calls + tool results + text), **but not the first user message**
- The user message already has `src=ChatHistory` on the first call to the inner pipeline, because `CompactionProvider` stamped it in-place
- Serializing the session and loading it back loses the first message permanently
## Expected Behavior
The first user message should be preserved in `InMemoryChatHistoryProvider` across turns.
## Related
- PR #5149 fixed the opposite scenario (messages **missing** ChatHistory stamps, causing duplication), but the in-place mutation issue described here remains.
## Suggested Fix Direction
Clone `ChatMessage` objects before modifying `AdditionalProperties` in `CompactionProvider.InvokingCoreAsync`, specifically in both stamping blocks (around lines 110-118 and 135-143). A workaround wrapper `SafeCompactionProvider` that clones messages before delegating to the inner `CompactionProvider` is available on request.
## Environment
- Microsoft.Agents.AI: 1.12.0 / 1.13.0-preview
- .NET: 10
- OS: Windows
[BugRepro.zip](https://github.com/user-attachments/files/29755742/BugRepro.zip)
Contributor guide
Assessment
This issue has not been assessed yet.