Skip to content

.NET: Avoid duplicate AG-UI conversation history - #7936

Draft
Javier Calvarro Nelson (javiercn) wants to merge 3 commits into
mainfrom
javiercn-fix-ag-ui-duplicate-messages
Draft

.NET: Avoid duplicate AG-UI conversation history#7936
Javier Calvarro Nelson (javiercn) wants to merge 3 commits into
mainfrom
javiercn-fix-ag-ui-duplicate-messages

Conversation

@javiercn

@javiercn Javier Calvarro Nelson (javiercn) commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

AG-UI clients send the complete conversation transcript on each turn. When MapAGUIServer restores a ChatClientAgent session, that prior history is already available through either the local ChatHistoryProvider or a provider-managed conversation. Forwarding the full transcript again duplicates earlier messages, increasing token usage and potentially degrading model responses.

Description & Review Guide

  • What are the major changes? The endpoint now detects history-backed ChatClientAgent sessions and narrows incoming AG-UI transcripts to the new suffix. For readable in-memory history, it verifies in one backward pass that stored message IDs are an ordered prefix of the incoming transcript; for provider-managed conversations, it keeps messages after the final assistant boundary. First turns, incremental-only requests, reordered/non-prefix history, and unknown custom agents remain unchanged. Regression tests cover complete ordered history, non-prefix history, repeated user text, local and provider client-tool-result continuations, and custom agents.
  • What is the impact of these changes? Subsequent AG-UI turns no longer replay conversation messages already managed by Agent Framework or the provider. Client tool results in the new turn remain available to the agent. No public API changes are introduced.
  • What do you want reviewers to focus on? Please focus on the conservative ordered-prefix matching and the final-assistant boundary used when provider history is not locally readable.

Related Issue

Fixes #7930

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require stored history to remain an ordered prefix of the incoming AG-UI transcript, avoiding quadratic overlap searches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents AG-UI clients from replaying conversation history already stored locally or by providers.

Changes:

  • Filters stored history using message IDs.
  • Uses the final assistant boundary for provider-managed conversations.
  • Adds focused regression tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
AGUIEndpointRouteBuilderExtensions.cs Filters incoming AG-UI transcripts before agent execution.
AGUIEndpointRouteBuilderExtensionsTests.cs Tests local, provider-managed, incremental, and custom-agent cases.
Suppressed comments (1)

dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs:213

  • This fallback makes deduplication fail whenever the stored tail has no MessageId, which is a normal supported provider path: ChatClientAgent deliberately passes through null IDs, while AGUI.Server 0.0.5 generates a wire-only ID for such updates. On the next turn the client sends that generated ID, but the persisted assistant message still has null here, so the complete transcript is returned and duplicated again. Persist/synchronize the emitted AG-UI ID with stored history, or add a conservative fallback match for ID-less stored messages, and cover the endpoint flow with provider updates that omit IDs.
        if (incomingMessages.Count <= storedMessages.Count)
        {
            return incomingMessages;
        }

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

return incomingMessages;
}

if (chatClientAgent.ChatHistoryProvider is InMemoryChatHistoryProvider historyProvider &&
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (1 commit(s)): 869da0fb235f
Model: gpt-5.6-sol

Overview

The review found 2 verified inline finding(s).

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 1 file. Details are attached to the affected lines below.

Affected areas: dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs

}

if (chatClientAgent.ChatHistoryProvider is InMemoryChatHistoryProvider historyProvider &&
session.TryGetInMemoryChatHistory(out List<ChatMessage>? storedMessages, historyProvider.StateKeys[0]) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads restored history with AgentSessionExtensions' default serializer options, even when the InMemoryChatHistoryProvider was configured with custom JsonSerializerOptions. A session containing custom AIContent can deserialize successfully through the provider but throw here on every subsequent AG-UI request. Please retrieve the messages through the configured provider (or pass its serializer options) and cover a serialized/restored session using provider-specific options.

ctx.Input.ThreadId = threadId;

var session = await hostAgent.GetOrCreateSessionAsync(threadId, cancellationToken).ConfigureAwait(false);
var messages = GetMessagesForRun(aiAgent, session, ctx.Messages);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGUIEndpointRouteBuilderExtensions.cs:148-155,198-200 restores only the thread’s current provider session and trims every provider-backed transcript after its last assistant message without consulting ParentRunId, so branching from an earlier run executes against the current tip and loses the branch history; a safe fix must distinguish true branches from ordinary ParentRunId continuations so existing history deduplication is preserved.

注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

.NET Usage: [问题, PRs], Target: .Net

项目

None yet

Development

Successfully merging this pull request may close these issues.

.NET: [Bug]: AG-UI endpoint sends duplicate conversation messages to providers

2 participants