Skip to content

.NET: [Bug]: CompactionProvider re-adds previously persisted request messages to InMemoryChatHistoryProvider across session round-trips, even when summarization never triggers #4924

@Kipiqid

Description

@Kipiqid

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 CompactionProvider is 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_compaction state in AgentSession looks expected.
  • The duplicated entries appear in InMemoryChatHistoryProvider.messages.
  • From source reading, this may be caused by interaction between CompactionProvider state restoration and ChatHistoryPersistingChatClient persistence markers / chat-history attribution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions