Skip to content

Conversation

@javiercn
Copy link
Contributor

@javiercn javiercn commented Oct 29, 2025

Summary

This PR implements Phase 1 of AG-UI protocol support in the .NET Agent Framework, enabling text streaming capabilities for both consuming AG-UI-compliant servers and exposing .NET agents via the AG-UI protocol.

Motivation and goals

The AG-UI (Agent-User Interaction) protocol is an open, lightweight, event-based protocol that standardizes how AI agents connect to user-facing applications. It addresses key challenges in agentic applications:

  • Streaming support: Agents are long-running and stream intermediate work across multi-turn sessions
  • Event-driven architecture: Supports nondeterministic agent behavior with real-time UI updates
  • Protocol interoperability: Complements MCP (tool/context) and A2A (agent-to-agent) protocols in the AI ecosystem

Without AG-UI support, .NET agents cannot interoperate with the growing ecosystem of AG-UI-compatible frontends and agent frameworks (LangGraph, CrewAI, Pydantic AI, etc.).

This implementation enables:

  • .NET developers to consume AG-UI servers from any framework
  • .NET agents to be accessible from any AG-UI-compatible client (web, mobile, CLI)
  • Standardized streaming communication patterns for agentic applications

In scope

  1. Client-side AG-UI consumption (Microsoft.Agents.AI.AGUI package)

    • AGUIAgent class for connecting to remote AG-UI servers
    • AGUIAgentThread for managing conversation threads
    • HTTP/SSE streaming support
    • Event-to-framework type conversion
  2. Server-side AG-UI hosting (Microsoft.Agents.AI.Hosting.AGUI.AspNetCore package)

    • MapAGUIAgent extension method for ASP.NET Core
    • Server-Sent Events (SSE) response formatting using SseFormatter
    • Framework-to-event type conversion
    • Agent factory pattern for per-request agent instantiation
  3. Text streaming events (Phase 1)

    • Lifecycle events: RunStarted, RunFinished, RunError
    • Text message events: TextMessageStart, TextMessageContent, TextMessageEnd
    • Bidirectional event/update conversion
    • Thread and run ID management via ConversationId and ResponseId properties
  4. Testing and samples

    • Unit tests for the core protocol
    • Integration tests for end-to-end scenarios
    • AGUIClientServer sample demonstrating both client and server usage

Out of scope

The following AG-UI features are intentionally deferred to future PRs:

  • Tool call events (ToolCallStart, ToolCallArgs, ToolCallEnd, ToolCallResult)
  • State management events (StateSnapshot, StateDelta, MessagesSnapshot)
  • Step events (StepStarted, StepFinished)

Examples

Server: Exposing a .NET agent via AG-UI

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Map an AG-UI endpoint at /agent
app.MapAGUIAgent("/", (messages) =>
{
    var chatClient = new AzureOpenAIClient(endpoint, credential)
        .GetChatClient(deploymentName);
    return chatClient.CreateAIAgent(name: "AGUIAssistant");
});

app.Run();

Client: Consuming an AG-UI server

using var httpClient = new HttpClient();
var agent = new AGUIAgent(
    id: "agui-client",
    description: "AG-UI Client Agent",
    httpClient: httpClient,
    endpoint: "http://localhost:5100");

var thread = agent.GetNewThread();
var messages = new List<ChatMessage>
{
    new(ChatRole.System, "You are a helpful assistant."),
    new(ChatRole.User, "What is the capital of France?")
};

// Stream responses in real-time
await foreach (var update in agent.RunStreamingAsync(messages, thread))
{
    ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
    
    // Access thread and run IDs from first update
    if (chatUpdate.ConversationId != null)
    {
        Console.WriteLine($"Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}");
    }
    
    foreach (var content in update.Contents)
    {
        if (content is TextContent text)
        {
            Console.Write(text.Text); // Display streaming text
        }
        else if (content is ErrorContent error)
        {
            Console.WriteLine($"Error: {error.Message}");
        }
    }
}

SSE Event Stream Format

When a client sends a request:

POST /agent
{
  "threadId": "thread_123",
  "runId": "run_456",
  "messages": [{"role": "user", "content": "Hello"}]
}

The server streams back SSE events:

data: {"type":"run_started","threadId":"thread_123","runId":"run_456"}

data: {"type":"text_message_start","messageId":"msg_1","role":"assistant"}

data: {"type":"text_message_content","messageId":"msg_1","delta":"Hello"}

data: {"type":"text_message_content","messageId":"msg_1","delta":" there!"}

data: {"type":"text_message_end","messageId":"msg_1"}

data: {"type":"run_finished","threadId":"thread_123","runId":"run_456"}

Detailed design

Architecture

Package Structure

Microsoft.Agents.AI.AGUI/
├── AGUIAgent.cs                          // Public: Client agent implementation
├── AGUIAgentThread.cs                    // Public: Thread management
├── AGUIHttpService.cs                    // Internal: HTTP/SSE communication
└── Shared/                               // Internal: Protocol types
    ├── BaseEvent.cs
    ├── RunStartedEvent.cs
    ├── TextMessageStartEvent.cs
    ├── AGUIMessage.cs
    ├── RunAgentInput.cs
    └── [conversion extensions]

Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/
├── AGUIEndpointRouteBuilderExtensions.cs // Public: MapAGUIAgent
├── AGUIServerSentEventsResult.cs         // Internal: SSE formatting
└── Shared/                               // Internal: Protocol types & converters

Key Design Decisions

1. Event Models as Internal Types

  • AG-UI event types (e.g., RunStartedEvent, TextMessageContentEvent) are internal
  • Conversion happens at boundaries via extension methods
  • Public API uses framework-native types (AgentRunResponseUpdate, ChatMessage)
  • Rationale: Protects consumers from protocol changes; maintains framework abstractions

2. No Custom Content Types

  • Run lifecycle communicated through ChatResponseUpdate.ConversationId and ResponseId properties
  • Errors use standard ErrorContent type
  • First update in a stream contains ConversationId and ResponseId indicating run started
  • Last update with FinishReason indicates run finished
  • Rationale: Avoids introducing non-standard content types; uses existing framework abstractions

3. Agent Factory Pattern in MapAGUIAgent

  • Factory function creates agent per request: (messages) => AIAgent
  • Allows request-specific agent configuration (e.g., different models per user)
  • Rationale: Supports multi-tenancy and request customization without global state

4. Bidirectional Conversion Architecture

  • Server: AgentRunResponseUpdate → AG-UI events
  • Client: AG-UI events → AgentRunResponseUpdate
  • Extension methods in Shared/ namespace (compiled in both packages)
  • Rationale: Symmetric conversion logic; shared code between client and server

5. Thread Management

  • AGUIAgentThread stores only ThreadId (string)
  • Thread ID communicated via ConversationId property on updates
  • No automatic thread persistence; applications manage storage
  • Rationale: AG-UI spec defines threads by ID only; persistence is application-specific

6. SSE Formatting

  • Uses ASP.NET Core's SseFormatter for Server-Sent Events
  • Standard SSE format with data: prefix and double newline delimiter
  • Rationale: Leverages framework infrastructure for SSE compliance

Event Conversion Logic

Text Message Streaming (Server → Client):

// Server side: Framework updates → AG-UI events
IAsyncEnumerable<AgentRunResponseUpdate> updates = agent.RunStreamingAsync(...);
IAsyncEnumerable<BaseEvent> events = updates.AsAGUIEventStreamAsync(threadId, runId);

// Emits: RunStarted
//        TextMessageStart (on role/messageId change)
//        TextMessageContent (per text chunk)
//        TextMessageEnd (on message completion)
//        RunFinished

Text Message Streaming (Client → Framework):

// Client side: AG-UI events → Framework updates
IAsyncEnumerable<BaseEvent> events = httpService.PostRunAsync(...);
IAsyncEnumerable<AgentRunResponseUpdate> updates = events.AsAgentRunResponseUpdatesAsync();

// Converts: RunStarted → Update with ConversationId/ResponseId
//           TextMessageStart → (tracks current message)
//           TextMessageContent → ChatResponseUpdate with text delta
//           TextMessageEnd → (closes message)
//           RunFinished → Update with FinishReason
//           RunError → Update with ErrorContent

JSON Serialization

  • Uses System.Text.Json.Serialization.JsonConverter for polymorphic events
  • BaseEventJsonConverter dispatches based on "type" field
  • Source-generated serialization context (AGUIJsonSerializerContext) for AOT compatibility
  • Rationale: Built-in STJ polymorphism requires discriminator as first property, which AG-UI protocol doesn't guarantee

Error Handling

  • HTTP errors (4xx, 5xx) throw HttpRequestException
  • AG-UI RunError events convert to ErrorContent in update stream
  • SSE parsing errors throw JsonException or InvalidOperationException
  • Connection failures propagate as OperationCanceledException when cancelled
  • Rationale: Standard .NET exception patterns; errors surface naturally through async streams

Drawbacks

  1. Custom JSON Converter: Required custom polymorphic deserialization instead of built-in STJ support

    • Mitigation: Internal implementation detail; doesn't affect public API
  2. Shared Code via Preprocessor Directives: Files in Shared/ use #if ASPNETCORE to compile into both packages

    • Mitigation: Necessary to avoid duplicating event definitions; contained to internal types

Considered alternatives

Alternative 1: Expose AG-UI Event Types Publicly

Rejected: We already have common abstractions for AI concepts. We can break AG-UI specific payloads into a separate assembly in the future and take a dependency on that if needed.

Alternative 2: Implement All Event Types Upfront

Rejected: Keeping the PR manageable and focused on text streaming. Tool calls and state management can be added incrementally.

Alternative 3: Custom AIContent Types for Lifecycle

Rejected after review: Initially proposed RunStartedContent, RunFinishedContent, and RunErrorContent. Feedback indicated these should use existing framework properties (ConversationId, ResponseId, ErrorContent) instead.

#Fixes #1775

Copilot AI review requested due to automatic review settings October 29, 2025 12:41
@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation .NET labels Oct 29, 2025
@javiercn javiercn requested a review from DeagleGross October 29, 2025 12:41
@github-actions github-actions bot changed the title AG-UI support for .NET .NET: AG-UI support for .NET Oct 29, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces AG-UI (Agent-User Interaction) protocol support for the Microsoft Agent Framework, enabling standardized client-server communication for AI agents. The implementation includes both server-side ASP.NET Core hosting capabilities and client-side consumption functionality.

Key changes:

  • Added AG-UI protocol implementation with server-sent events (SSE) streaming
  • Created ASP.NET Core hosting extensions for exposing agents via AG-UI endpoints
  • Implemented AGUIAgent client for consuming remote AG-UI services
  • Added comprehensive unit and integration tests covering protocol behavior

Reviewed Changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 29 comments.

Show a summary per file
File Description
Microsoft.Agents.AI.AGUI/AGUIAgent.cs Core client implementation for connecting to AG-UI servers
Microsoft.Agents.AI.AGUI/AGUIHttpService.cs HTTP service for SSE-based communication with AG-UI endpoints
Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs Extension methods for mapping AG-UI agents to ASP.NET endpoints
Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIServerSentEventsResult.cs IResult implementation for streaming AG-UI events via SSE
Shared/*.cs Protocol event types, serialization contexts, and message conversion utilities
Test files Comprehensive unit and integration tests for AG-UI functionality
samples/AGUIClientServer/* Sample client and server demonstrating AG-UI protocol usage

@markwallace-microsoft
Copy link
Member

@javiercn can you copy the information in the PR description to an ADR and add to this folder: https://github.com/microsoft/agent-framework/tree/main/docs/decisions

It will be much easier to find the information later and it will be with all of the other design decisions.

Copy link
Contributor Author

@javiercn javiercn left a comment

Choose a reason for hiding this comment

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

Addressed feedback:

  • I've gotten rid of the custom contents and mapped the info from the events as ResponseUpdate.
  • I switched to use the SseFormatter instead of the manual SSE Event serialization.
  • I've gotten rid of Context/Tools/ForwardedProps for now and will bring them in a separate PR.
  • I've mapped additional properties to response updates.
  • I removed some buffering when dealing with messages.
  • I've fixed the build dependencies and cleaned up some copilot leftover.

@javiercn
Copy link
Contributor Author

@javiercn can you copy the information in the PR description to an ADR and add to this folder: https://github.com/microsoft/agent-framework/tree/main/docs/decisions

It will be much easier to find the information later and it will be with all of the other design decisions.

Yep, I saw the ADRs, and was planning to do that, but wanted to check with you first. I'll include the ADRs as a last step as we work out through the details

@javiercn
Copy link
Contributor Author

javiercn commented Nov 5, 2025

https://github.com/microsoft/agent-framework/pull/1776/files#diff-805d443b87ef13f1f4e96d50c89e5dc5940af17c524a3f2412738d66c4adae33

I'll give it a go on the other PR that I have in flight as I'm touching the json serialization there.

@javiercn javiercn enabled auto-merge November 5, 2025 15:31
@javiercn javiercn disabled auto-merge November 5, 2025 15:31
@javiercn javiercn enabled auto-merge November 5, 2025 15:40
@javiercn javiercn added this pull request to the merge queue Nov 5, 2025
Merged via the queue into main with commit b03a4fb Nov 5, 2025
15 checks passed
rogerbarreto added a commit that referenced this pull request Nov 5, 2025
* .NET: [BREAKING] Simplify TextSearchProvider construction and improve Mem0Provider scoping. (#1905)

* Simplify TextSearchProvider construction and improve Mem0Provider scoping

* Fixing indentation.

* .NET: [Breaking Change] Moving MAAI.AzureAI V1 Package  -> MAAI.AzureAI.Persistent (V1) (#1902)

* Update AzureAI -> AzureAI.Persistent

* Fix sample reference

* Fix workflow lookup with AddAsAIAgent(name) when name differs from workflow name (#1925)

* .NET: AG-UI support for .NET (#1776)

* Initial plan

* Infrastructure setup

* Plan for minimal client

* Plan update

* Basic agentic chat

* cleanup

* Cleanups

* More cleanups

* Cleanups

* More cleanups

* Test plan

* Sample

* Fix streaming and error handling

* Fix notifications

* Cleanups

* cleanup sample

* Additional tests

* Additional tests

* Run dotnet format

* Remove unnecessary files

* Mark packages as non packable

* Fix build

* Address feedback

* Fix build

* Fix remaining warnings

* Feedback

* Feedback and cleanup

* Cleanup

* Cleanups

* Cleanups

* Cleanups

* Retrieve existing messages from the store to send them along the way and update the sample client

* Run dotnet format

* Add ADR for AG-UI

* Switch to use the SG and use a convention for run ids

* Cleanup MapAGUI API

* Fix formatting

* Fix solution

* Fix solution

* Python: Added parameter to disable agent cleanup in AzureAIAgentClient (#1882)

* Removed automatic agent cleanup in AzureAIAgentClient

* Revert "Removed automatic agent cleanup in AzureAIAgentClient"

This reverts commit 89846c7.

* Exposed boolean flag to control deletion behavior

* Update sample

* [BREAKING] refactor: Fix unintuitive binding of StreamAsync (#1930)

In #1551 we added a mechanism to open a Streaming workflow run without providing any input. This caused unintuitive behaviour when passing a string as input without providing a runId, resulting in the input being misinterpreted as the runId and the workflow not executing.

As well, the name caused confusion about why the Workflow was not running when using the input-less StreamAsync (since the Workflow cannot run without any messages to drive its execution).

* Python: fix missing packaging dependency (#1929)

* fix missing packaging dependency

* add aiohttp to azureai

* Bring back files

* Address merge conflict

* Address merge conflict+

---------

Co-authored-by: westey <[email protected]>
Co-authored-by: Jeff Handley <[email protected]>
Co-authored-by: Javier Calvarro Nelson <[email protected]>
Co-authored-by: Dmytro Struk <[email protected]>
Co-authored-by: Jacob Alber <[email protected]>
Co-authored-by: Eduard van Valkenburg <[email protected]>
@crickman crickman deleted the javiercn/ag-ui-support-for-net branch November 11, 2025 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation .NET

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET: [AG-UI] Setup infrastructure and enable text streaming

6 participants