-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Open
Labels
Description
Description
When ChatClientAgent uses both InMemoryChatHistoryProvider and CompactionProvider(new SummarizationCompactionStrategy(...)), previously persisted user messages can be appended into chat history again after the session is serialized and loaded for the next turn.
This happens even when the compaction trigger is configured so summarization never runs. Without CompactionProvider, the same flow does not duplicate history.
Observed result after two turns:
Expected:
- user: hello
- assistant: hello, I am a test assistant
- user: who are you
- assistant: I am a test assistant
Actual:
- user: hello
- assistant: hello, I am a test assistant
- user: hello
- user: who are you
- assistant: I am a test assistant
Code Sample
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
using Moq;
using Xunit;
public class Repro
{
[Fact]
public async Task CompactionProvider_ShouldNotDuplicateHistory_AcrossRoundTrips()
{
var mainChatClient = new Mock<IChatClient>();
mainChatClient
.SetupSequence(c => c.GetResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new ChatResponse([new ChatMessage(ChatRole.Assistant, "hello, I am a test assistant")]))
.ReturnsAsync(new ChatResponse([new ChatMessage(ChatRole.Assistant, "I am a test assistant")]));
var summarizer = new Mock<IChatClient>();
summarizer
.Setup(c => c.GetResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new ChatResponse([new ChatMessage(ChatRole.Assistant, "summary")]));
var agent = (ChatClientAgent)mainChatClient.Object.AsBuilder().BuildAIAgent(
new ChatClientAgentOptions
{
Name = "TestAgent",
ChatHistoryProvider = new InMemoryChatHistoryProvider(new()),
AIContextProviders =
[
new CompactionProvider(
new SummarizationCompactionStrategy(
summarizer.Object,
CompactionTriggers.TokensExceed(int.MaxValue),
minimumPreservedGroups: 4,
summarizationPrompt: null,
target: CompactionTriggers.TokensBelow(int.MaxValue - 1)),
"test_compaction",
loggerFactory: null!)
]
});
var session = await agent.CreateSessionAsync();
await agent.RunAsync([new ChatMessage(ChatRole.User, "hello")], session);
// simulate app persistence / reload between requests
var roundTripped = JsonSerializer.Deserialize<AgentSession>(
JsonSerializer.Serialize(session))!;
await agent.RunAsync([new ChatMessage(ChatRole.User, "who are you")], roundTripped);
Assert.True(roundTripped.TryGetInMemoryChatHistory(out var messages));
var texts = messages
.Select(m => m.Text)
.Where(t => !string.IsNullOrWhiteSpace(t))
.ToArray();
Assert.Equal(
[
"hello",
"hello, I am a test assistant",
"who are you",
"I am a test assistant"
], texts);
}
}Error Messages / Stack Traces
Package Versions
Microsoft.Agents.AI.OpenAI: 1.0.0-rc4, Microsoft.Agents.AI: 1.0.0-rc4, Microsoft.Extensions.AI: 10.3.0
.NET Version
.NET 10
Additional Context
Additional notes:
- The issue reproduces only when
CompactionProvideris enabled. - The issue still reproduces when compaction is configured to effectively never trigger:
CompactionTriggers.TokensExceed(int.MaxValue)target: CompactionTriggers.TokensBelow(int.MaxValue - 1)
- Without
CompactionProvider, the same two-turn round-trip flow behaves correctly. conversation_summary_compactionstate inAgentSessionlooks expected.- The duplicated entries appear in
InMemoryChatHistoryProvider.messages. - From source reading, this may be caused by interaction between
CompactionProviderstate restoration andChatHistoryPersistingChatClientpersistence markers / chat-history attribution.
Reactions are currently unavailable