Skip to content

.NET: [Bug]: AGUIChatClient client-tool loop duplicates persisted messages with MapAGUIServer + AgentSessionStore #7811

Description

@mip1983

Description

When an AG-UI server owns durable conversation history through an AgentSessionStore, a client-side tool invocation causes the request user message and function call to be persisted twice.
My repo contains a focused live integration test.

The server owns cross-turn conversation history:

builder.AddAIAgent(agentName, (_, _) => agent)
    .WithSessionStore((sp, agentName) =>
        sp.GetKeyedService<CosmosAgentSessionStore>(agentName)!);

agentHost.MapAGUIServer(agentName, "/ag-ui");

The client sends only the current user message for each user-initiated turn. It creates a fresh local AgentSession for that turn only, retaining it only long enough for the internal client-tool loop to complete.

var session = await agent.CreateSessionAsync(cancellationToken);

await foreach (var update in agent.RunStreamingAsync(
    [new ChatMessage(ChatRole.User, "Where am I?")],
    session,
    cancellationToken: cancellationToken))
{
    // consume stream
}

The client registers a tool such as:

AIFunctionFactory.Create(
    () => "51.3967°N, -1.3172°E",
    name: "get_user_location")

Actual behavior

After one client-tool turn completes, the persisted server history is:

user:      Where am I?
assistant: functionCall get_user_location (callId: X)

user:      Where am I?                         <-- duplicate
assistant: functionCall get_user_location (X)  <-- duplicate

tool:      functionResult (X)
assistant: final text response

The duplicate user message retains the same messageId, and the duplicate function call retains the same callId. The function result is persisted only once.

The issue is visible when a conversation is reloaded from durable history: each user request appears twice, while each tool result appears once.

Expected behavior

A single client-tool turn should persist:

user:      Where am I?
assistant: functionCall get_user_location (callId: X)
tool:      functionResult (X)
assistant: final text response

Investigation

The focused regression test reads the raw serialized hosted session directly and fails because the user message occurs twice.
The apparent cause is that AGUIChatClient’s automatic client-tool continuation reuses its accumulated temporary local history when submitting the follow-up request. That request contains the original user message and function call alongside the tool result. The hosted AG-UI server then appends those resent messages to the durable AgentSessionStore history.
This does not appear to be caused by application-managed cross-turn continuation:
• The application only provides ThreadId and ParentRunId on later user-initiated turns.
• The duplicate occurs within one user turn and its automatic client-tool follow-up.
• No explicit messages override is supplied through RawRepresentationFactory.
• Disabling the Foundry Memory provider does not affect reproduction.

Question

What is the supported AGUIChatClient client-tool continuation pattern when MapAGUIServer owns durable transcript state via AgentSessionStore?
Specifically, should the client-tool resume request be treated as a delta by the server, or is there a supported client API for sending only the tool result rather than accumulated local messages?

Code Sample

Repo here: https://github.com/ecoDriverltd/FoundryAgentsExperiment/tree/session-store-only

Test: 'AGUIClientToolContinuationPersistsEachProtocolMessageOnce' in:

https://github.com/ecoDriverltd/FoundryAgentsExperiment/blob/session-store-only/FoundryAgentsExperiment.IntegrationTests/AgentChatTests.cs

Error Messages / Stack Traces

Package Versions

Microsoft.Agents.AI.Hosting.AGUI.AspNetCore 1.18.0-preview.260818.1, AGUI.Client 1.18.0-preview.260818.1

.NET Version

.NET 10

Additional Context

No response

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .Netag-uiUsage: [Issues, PRs], Target: AG-UI protocol integrationhostingUsage: [Issues, PRs], Target: all hosting related solutionsreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions