Blog Post

Microsoft Developer Community Blog
18 MIN READ

Building Interactive Agent UIs with AG-UI and Microsoft Agent Framework

pratikpanda's avatar
pratikpanda
Icon for Microsoft rankMicrosoft
Jan 29, 2026

Introduction

Picture this: You've built an AI agent that analyzes financial data. A user uploads a quarterly report and asks: "What are the top three expense categories?" Behind the scenes, your agent parses the spreadsheet, aggregates thousands of rows, and generates visualizations. All in 20 seconds. But the user? They see a loading spinner. Nothing else. No "reading file" message, no "analyzing data" indicator, no hint that progress is being made. They start wondering: Is it frozen? Should I refresh?

The problem isn't the agent's capabilities - it's the communication gap between the agent running on the backend and the user interface. When agents perform multi-step reasoning, call external APIs, or execute complex tool chains, users deserve to see what's happening. They need streaming updates, intermediate results, and transparent progress indicators. Yet most agent frameworks force developers to choose between simple request/response patterns or building custom solutions to stream updates to their UIs.

This is where AG-UI comes in. AG-UI is a fairly new event-based protocol that standardizes how agents communicate with user interfaces. Instead of every framework and development team inventing their own streaming solution, AG-UI provides a shared vocabulary of structured events that work consistently across different agent implementations. When an agent starts processing, calls a tool, generates text, or encounters an error, the UI receives explicit, typed events in real time.

The beauty of AG-UI is its framework-agnostic design. While this blog post demonstrates integration with Microsoft Agent Framework (MAF), the same AG-UI protocol works with LangGraph, CrewAI, or any other compliant framework. Write your UI code once, and it works with any AG-UI-compliant backend. (Note: MAF supports both Python and .NET - this blog post focuses on the Python implementation.)

TL;DR

The Problem: Users don't get real-time updates while AI agents work behind the scenes - no progress indicators, no transparency into tool calls, and no insight into what's happening.

The Solution: AG-UI is an open, event-based protocol that standardizes real-time communication between AI agents and user interfaces. Instead of each development team and framework inventing custom streaming solutions, AG-UI provides a shared vocabulary of structured events (like TOOL_CALL_START, TEXT_MESSAGE_CONTENT, RUN_FINISHED) that work across any compliant framework.

Key Benefits:

  • Framework-agnostic - Write UI code once, works with LangGraph, Microsoft Agent Framework, CrewAI, and more
  • Real-time observability - See exactly what your agent is doing as it happens
  • Server-Sent Events - Built on standard HTTP for universal compatibility
  • Protocol-managed state - No manual conversation history tracking

In This Post: You'll learn why AG-UI exists, how it works, and build a complete working application using Microsoft Agent Framework with Python - from server setup to client implementation.

What You'll Learn

This blog post walks through:

  • Why AG-UI exists - how agent-UI communication has evolved and what problems current approaches couldn't solve
  • How the protocol works - the key design choices that make AG-UI simple, reliable, and framework-agnostic
  • Protocol architecture - the generic components and how AG-UI integrates with agent frameworks
  • Building an AG-UI application - a complete working example using Microsoft Agent Framework with server, client, and step-by-step setup
  • Understanding events - what happens under the hood when your agent runs and how to observe it
  • Thinking in events - how building with AG-UI differs from traditional APIs, and what benefits this brings
  • Making the right choice - when AG-UI is the right fit for your project and when alternatives might be better

Estimated reading time: 15 minutes

Who this is for: Developers building AI agents who want to provide real-time feedback to users, and teams evaluating standardized approaches to agent-UI communication

To appreciate why AG-UI matters, we need to understand the journey that led to its creation. Let's trace how agent-UI communication has evolved through three distinct phases.

The Evolution of Agent-UI Communication

AI agents have become more capable over time. As they evolved, the way they communicated with user interfaces had to evolve as well. Here's how this evolution unfolded.

Phase 1: Simple Request/Response

In the early days of AI agent development, the interaction model was straightforward: send a question, wait for an answer, display the result. This synchronous approach mirrored traditional API calls and worked fine for simple scenarios.

# Simple, but limiting
response = agent.run("What's the weather in Paris?")
display(response)  # User waits... and waits...

Works for: Quick queries that complete in seconds, simple Q&A interactions where immediate feedback and interactivity aren't critical.

Breaks down: When agents need to call multiple tools, perform multi-step reasoning, or process complex queries that take 30+ seconds. Users see nothing but a loading spinner, with no insight into what's happening or whether the agent is making progress. This creates a poor user experience and makes it impossible to show intermediate results or allow user intervention.

Recognizing these limitations, development teams began experimenting with more sophisticated approaches.

Phase 2: Custom Streaming Solutions

As agents became more sophisticated, teams recognized the need for incremental feedback and interactivity. Rather than waiting for the complete response, they implemented custom streaming solutions to show partial results as they became available.

# Every team invents their own format
for chunk in agent.stream("What's the weather?"):
    display(chunk)  # But what about tool calls? Errors? Progress?

This was a step forward for building interactive agent UIs, but each team solved the problem differently. Also, different frameworks had incompatible approaches - some streamed only text tokens, others sent structured JSON, and most provided no visibility into critical events like tool calls or errors.

The problem:

  • No standardization across frameworks - client code that works with LangGraph won't work with Crew AI, requiring separate implementations for each agent backend
  • Each implementation handles tool calls differently - some send nothing during tool execution, others send unstructured messages
  • Complex state management - clients must track conversation history, manage reconnections, and handle edge cases manually

The industry needed a better solution - a common protocol that could work across all frameworks while maintaining the benefits of streaming.

Phase 3: Standardized Protocol (AG-UI)

AG-UI emerged as a response to the fragmentation problem. Instead of each framework and development team inventing their own streaming solution, AG-UI provides a shared vocabulary of events that work consistently across different agent implementations.

# Standardized events everyone understands
async for event in agent.run_stream("What's the weather?"):
    if event.type == "TEXT_MESSAGE_CONTENT":
        display_text(event.delta)
    elif event.type == "TOOL_CALL_START":
        show_tool_indicator(event.tool_name)
    elif event.type == "TOOL_CALL_RESULT":
        show_tool_result(event.result)

The key difference is structured observability. Rather than guessing what the agent is doing from unstructured text, clients receive explicit events for every stage of execution: when the agent starts, when it generates text, when it calls a tool, when that tool completes, and when the entire run finishes.

What's different: A standardized vocabulary of event types, complete observability into agent execution, and framework-agnostic clients that work with any AG-UI-compliant backend. You write your UI code once, and it works whether the backend uses Microsoft Agent Framework, LangGraph, or any other framework that speaks AG-UI.

Now that we've seen why AG-UI emerged and what problems it solves, let's examine the specific design decisions that make the protocol work. These choices weren't arbitrary - each one addresses concrete challenges in building reliable, observable agent-UI communication.

The Design Decisions Behind AG-UI

Why Server-Sent Events (SSE)?

AspectWebSocketsSSE (AG-UI)
ComplexityBidirectionalUnidirectional (simpler)
Firewall/ProxySometimes blockedStandard HTTP
ReconnectionManual implementationBuilt-in browser support
Use caseReal-time games, chatAgent responses (one-way)

For agent interactions, you typically only need server→client communication, making SSE a simpler choice.

SSE solves the transport problem - how events travel from server to client. But once connected, how does the protocol handle conversation state across multiple interactions?

Why Protocol-Managed Threads?

# Without protocol threads (client manages):
conversation_history = []
conversation_history.append({"role": "user", "content": message})
response = agent.complete(conversation_history)
conversation_history.append({"role": "assistant", "content": response})
# Complex, error-prone, doesn't work with multiple clients

# With AG-UI (protocol manages):
thread = agent.get_new_thread()  # Server creates and manages thread
agent.run_stream(message, thread=thread)  # Server maintains context
# Simple, reliable, shareable across clients

With transport and state management handled, the final piece is the actual messages flowing through the connection. What information should the protocol communicate, and how should it be structured?

Why Standardized Event Types?

Instead of parsing unstructured text, clients get typed events:

  • RUN_STARTED - Agent begins (start loading UI)
  • TEXT_MESSAGE_CONTENT - Text chunk (stream to user)
  • TOOL_CALL_START - Tool invoked (show "searching...", "calculating...")
  • TOOL_CALL_RESULT - Tool finished (show result, update UI)
  • RUN_FINISHED - Complete (hide loading)

This lets UIs react intelligently without custom parsing logic.

Now that we understand the protocol's design choices, let's see how these pieces fit together in a complete system.

Architecture Overview

Here's how the components interact:

The communication between these layers relies on a well-defined set of event types. Here are the core events that flow through the SSE connection:

Core Event Types

AG-UI provides a standardized set of event types to describe what's happening during an agent's execution:

  • RUN_STARTED - agent begins execution
  • TEXT_MESSAGE_STARTTEXT_MESSAGE_CONTENTTEXT_MESSAGE_END - streaming segments of text
  • TOOL_CALL_STARTTOOL_CALL_ARGSTOOL_CALL_ENDTOOL_CALL_RESULT - tool execution events
  • RUN_FINISHED - agent has finished execution
  • RUN_ERROR - error information

This model lets the UI update as the agent runs, rather than waiting for the final response.

The generic architecture above applies to any AG-UI implementation. Now let's see how this translates to Microsoft Agent Framework.

AG-UI with Microsoft Agent Framework

While AG-UI is framework-agnostic, this blog post demonstrates integration with Microsoft Agent Framework (MAF) using Python. MAF is available in both Python and .NET, giving you flexibility to build AG-UI applications in your preferred language. Understanding how MAF implements the protocol will help you build your own applications or work with other compliant frameworks.

Integration Architecture

The Microsoft Agent Framework integration involves several specialized layers that handle protocol translation and execution orchestration:

Understanding each layer:

  • FastAPI Endpoint - Handles HTTP requests and establishes SSE connections for streaming
  • AgentFrameworkAgent - Protocol wrapper that translates between AG-UI events and Agent Framework operations
  • Orchestrators - Manage execution flow, coordinate tool calling sequences, and handle state transitions
  • ChatAgent - Your agent implementation with instructions, tools, and business logic
  • ChatClient - Interface to the underlying language model (Azure OpenAI, OpenAI, or other providers)

The good news? When you call add_agent_framework_fastapi_endpoint, all the middleware layers are configured automatically. You simply provide your ChatAgent, and the integration handles protocol translation, event streaming, and state management behind the scenes.

Now that we understand both the protocol architecture and the Microsoft Agent Framework integration, let's build a working application.

Hands-On: Building Your First AG-UI Application

This section demonstrates how to build an AG-UI server and client using Microsoft Agent Framework and FastAPI.

Prerequisites

Before building your first AG-UI application, ensure you have:

  • Python 3.10 or later installed
  • Basic understanding of async/await patterns in Python
  • Azure CLI installed and authenticated (az login)
  • Azure OpenAI service endpoint and deployment configured (setup guide)
  • Cognitive Services OpenAI Contributor role for your Azure OpenAI resource

You'll also need to install the AG-UI integration package:

pip install agent-framework-ag-ui --pre

This automatically installs agent-framework-corefastapi, and uvicorn as dependencies.

With your environment configured, let's create the server that will host your agent and expose it via the AG-UI protocol.

Building the Server

Let's create a FastAPI server that hosts an AI agent and exposes it via AG-UI:

# server.py
import os
from typing import Annotated
from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import Field
from agent_framework import ChatAgent, ai_function
from agent_framework.azure import AzureOpenAIChatClient
from agent_framework_ag_ui import add_agent_framework_fastapi_endpoint
from azure.identity import DefaultAzureCredential

# Load environment variables from .env file
load_dotenv()

# Validate environment configuration
openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
model_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")

if not openai_endpoint:
    raise RuntimeError("Missing required environment variable: AZURE_OPENAI_ENDPOINT")
if not model_deployment:
    raise RuntimeError("Missing required environment variable: AZURE_OPENAI_DEPLOYMENT_NAME")

# Define tools the agent can use
@ai_function
def get_order_status(
    order_id: Annotated[str, Field(description="The order ID to look up (e.g., ORD-001)")]
) -> dict:
    """Look up the status of a customer order.
    
    Returns order status, tracking number, and estimated delivery date.
    """
    # Simulated order lookup
    orders = {
        "ORD-001": {"status": "shipped", "tracking": "1Z999AA1", "eta": "Jan 25, 2026"},
        "ORD-002": {"status": "processing", "tracking": None, "eta": "Jan 23, 2026"},
        "ORD-003": {"status": "delivered", "tracking": "1Z999AA3", "eta": "Delivered Jan 20"},
    }
    return orders.get(order_id, {"status": "not_found", "message": "Order not found"})

# Initialize Azure OpenAI client
chat_client = AzureOpenAIChatClient(
    credential=DefaultAzureCredential(),
    endpoint=openai_endpoint,
    deployment_name=model_deployment,
)

# Configure the agent with custom instructions and tools
agent = ChatAgent(
    name="CustomerSupportAgent",
    instructions="""You are a helpful customer support assistant. 
    
    You have access to a get_order_status tool that can look up order information.
    
    IMPORTANT: When a user mentions an order ID (like ORD-001, ORD-002, etc.), 
    you MUST call the get_order_status tool to retrieve the actual order details.
    Do NOT make up or guess order information.
    
    After calling get_order_status, provide the actual results to the user in a friendly format.""",
    chat_client=chat_client,
    tools=[get_order_status],
)

# Initialize FastAPI application
app = FastAPI(
    title="AG-UI Customer Support Server",
    description="Interactive AI agent server using AG-UI protocol with tool calling"
)

# Mount the AG-UI endpoint
add_agent_framework_fastapi_endpoint(app, agent, path="/chat")

def main():
    """Entry point for the AG-UI server."""
    import uvicorn
    print("Starting AG-UI server on http://localhost:8000")
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")

# Run the application
if __name__ == "__main__":
    main()

What's happening here:

  • We define a tool: get_order_status with the AI​_function decorator
  • Use Annotated and Field for parameter descriptions to help the agent understand when and how to use the tool
  • We create an Azure OpenAI chat client with credential authentication
  • The ChatAgent is configured with domain-specific instructions and the tools parameter
  • add_agent_framework_fastapi_endpoint automatically handles SSE streaming and tool execution
  • The server exposes the agent at the /chat endpoint

Note: This example uses Azure OpenAI, but AG-UI works with any chat model. You can also integrate with Azure AI Foundry's model catalog or use other LLM providers. Tool calling is supported by most modern LLMs including GPT-4, GPT-4o, and Claude models.

To run this server:

# Set your Azure OpenAI credentials
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o"

# Start the server
python server.py

With your server running and exposing the AG-UI endpoint, the next step is building a client that can connect and consume the event stream.

Streaming Results to Clients

With the server running, clients can connect and stream events as the agent processes requests. Here's a Python client that demonstrates the streaming capabilities:

# client.py
import asyncio
import os
from dotenv import load_dotenv
from agent_framework import ChatAgent, FunctionCallContent, FunctionResultContent
from agent_framework_ag_ui import AGUIChatClient

# Load environment variables from .env file
load_dotenv()

async def interactive_chat():
    """Interactive chat session with streaming responses."""
    
    # Connect to the AG-UI server
    base_url = os.getenv("AGUI_SERVER_URL", "http://localhost:8000/chat")
    print(f"Connecting to: {base_url}\n")
    
    # Initialize the AG-UI client
    client = AGUIChatClient(endpoint=base_url)
    
    # Create a local agent representation
    agent = ChatAgent(chat_client=client)
    
    # Start a new conversation thread
    conversation_thread = agent.get_new_thread()
    
    print("Chat started! Type 'exit' or 'quit' to end the session.\n")
    
    try:
        while True:
            # Collect user input
            user_message = input("You: ")
            
            # Handle empty input
            if not user_message.strip():
                print("Please enter a message.\n")
                continue
            
            # Check for exit commands
            if user_message.lower() in ["exit", "quit", "bye"]:
                print("\nGoodbye!")
                break
            
            # Stream the agent's response
            print("Agent: ", end="", flush=True)
            
            # Track tool calls to avoid duplicate prints
            seen_tools = set()
            
            async for update in agent.run_stream(user_message, thread=conversation_thread):
                # Display text content
                if update.text:
                    print(update.text, end="", flush=True)
                
                # Display tool calls and results
                for content in update.contents:
                    if isinstance(content, FunctionCallContent):
                        # Only print each tool call once
                        if content.call_id not in seen_tools:
                            seen_tools.add(content.call_id)
                            print(f"\n[Calling tool: {content.name}]", flush=True)
                    elif isinstance(content, FunctionResultContent):
                        # Only print each result once
                        result_id = f"result_{content.call_id}"
                        if result_id not in seen_tools:
                            seen_tools.add(result_id)
                            result_text = content.result if isinstance(content.result, str) else str(content.result)
                            print(f"[Tool result: {result_text}]", flush=True)
            
            print("\n")  # New line after response completes
            
    except KeyboardInterrupt:
        print("\n\nChat interrupted by user.")
    except ConnectionError as e:
        print(f"\nConnection error: {e}")
        print("Make sure the server is running.")
    except Exception as e:
        print(f"\nUnexpected error: {e}")

def main():
    """Entry point for the AG-UI client."""
    asyncio.run(interactive_chat())

if __name__ == "__main__":
    main()

Key features:

  • The client connects to the AG-UI endpoint using AGUIChatClient with the endpoint parameter
  • run_stream() yields updates containing text and content as they arrive
  • Tool calls are detected using FunctionCallContent and displayed with [Calling tool: ...]
  • Tool results are detected using FunctionResultContent and displayed with [Tool result: ...]
  • Deduplication logic (seen_tools set) prevents printing the same tool call multiple times as it streams
  • Thread management maintains conversation context across messages
  • Graceful error handling for connection issues

To use the client:

# Optional: specify custom server URL
export AGUI_SERVER_URL="http://localhost:8000/chat"

# Start the interactive chat
python client.py

Example Session:

Connecting to: http://localhost:8000/chat

Chat started! Type 'exit' or 'quit' to end the session.

You: What's the status of order ORD-001?
Agent: 
[Calling tool: get_order_status]
[Tool result: {"status": "shipped", "tracking": "1Z999AA1", "eta": "Jan 25, 2026"}]
Your order ORD-001 has been shipped! 

- Tracking Number: 1Z999AA1
- Estimated Delivery Date: January 25, 2026

You can use the tracking number to monitor the delivery progress.

You: Can you check ORD-002?
Agent: 
[Calling tool: get_order_status]
[Tool result: {"status": "processing", "tracking": null, "eta": "Jan 23, 2026"}]
Your order ORD-002 is currently being processed.

- Status: Processing
- Estimated Delivery: January 23, 2026

Your order should ship soon, and you'll receive a tracking number once it's on the way.

You: exit

Goodbye!

The client we just built handles events at a high level, abstracting away the details. But what's actually flowing through that SSE connection? Let's peek under the hood.

Event Types You'll See

As the server streams back responses, clients receive a series of structured events. If you were to observe the raw SSE stream (e.g., using curl), you'd see events like:

curl -N http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"messages": [{"role": "user", "content": "What'\''s the status of order ORD-001?"}]}'

Sample event stream (with tool calling):

data: {"type":"RUN_STARTED","threadId":"eb4d9850-14ef-446c-af4b-23037acda9e8","runId":"chatcmpl-xyz"}

data: {"type":"TEXT_MESSAGE_START","messageId":"e8648880-a9ff-4178-a17d-4a6d3ec3d39c","role":"assistant"}

data: {"type":"TOOL_CALL_START","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","toolCallName":"get_order_status","parentMessageId":"e8648880-a9ff-4178-a17d-4a6d3ec3d39c"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"{\""}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"order"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"_id"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"\":\""}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"ORD"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"-"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"001"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","delta":"\"}"}

data: {"type":"TOOL_CALL_END","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y"}

data: {"type":"TOOL_CALL_RESULT","messageId":"f048cb0a-a049-4a51-9403-a05e4820438a","toolCallId":"call_GTWj2N3ZyYiiQIjg3fwmiQ8y","content":"{\"status\": \"shipped\", \"tracking\": \"1Z999AA1\", \"eta\": \"Jan 25, 2026\"}","role":"tool"}

data: {"type":"TEXT_MESSAGE_START","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","role":"assistant"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":"Your"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":" order"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":" ORD"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":"-"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":"001"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":" has"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":" been"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":" shipped"}

data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf","delta":"!"}

... (additional TEXT_MESSAGE_CONTENT events streaming the response) ...

data: {"type":"TEXT_MESSAGE_END","messageId":"8215fc88-8cb6-4ce4-8bdb-a8715dcd26cf"}

data: {"type":"RUN_FINISHED","threadId":"eb4d9850-14ef-446c-af4b-23037acda9e8","runId":"chatcmpl-xyz"}

Understanding the flow:

  1. RUN_STARTED - Agent begins processing the request
  2. TEXT_MESSAGE_START - First message starts (will contain tool calls)
  3. TOOL_CALL_START - Agent invokes the get_order_status tool
  4. Multiple TOOL_CALL_ARGS events - Arguments stream incrementally as JSON chunks ({"order_id":"ORD-001"})
  5. TOOL_CALL_END - Tool invocation structure complete
  6. TOOL_CALL_RESULT - Tool execution finished with result data
  7. TEXT_MESSAGE_START - Second message starts (the final response)
  8. Multiple TEXT_MESSAGE_CONTENT events - Response text streams word-by-word
  9. TEXT_MESSAGE_END - Response message complete
  10. RUN_FINISHED - Entire run completed successfully

This granular event model enables rich UI experiences - showing tool execution indicators ("Searching...", "Calculating..."), displaying intermediate results, and providing complete transparency into the agent's reasoning process.

Seeing the raw events helps, but truly working with AG-UI requires a shift in how you think about agent interactions. Let's explore this conceptual change.

The Mental Model Shift

Traditional API Thinking

# Imperative: Call and wait
response = agent.run("What's 2+2?")
print(response)  # "The answer is 4"

Mental model: Function call with return value

AG-UI Thinking

# Reactive: Subscribe to events
async for event in agent.run_stream("What's 2+2?"):
    match event.type:
        case "RUN_STARTED":
            show_loading()
        case "TEXT_MESSAGE_CONTENT":
            display_chunk(event.delta)
        case "RUN_FINISHED":
            hide_loading()

Mental model: Observable stream of events

This shift feels similar to:

  • Moving from synchronous to async code
  • Moving from REST to event-driven architecture
  • Moving from polling to pub/sub

This mental shift isn't just philosophical - it unlocks concrete benefits that weren't possible with request/response patterns.

What You Gain

Observability

# You can SEE what the agent is doing
TOOL_CALL_START: "get_order_status"
TOOL_CALL_ARGS: {"order_id": "ORD-001"}
TOOL_CALL_RESULT: {"status": "shipped", "tracking": "1Z999AA1", "eta": "Jan 25, 2026"}
TEXT_MESSAGE_START: "Your order ORD-001 has been shipped..."

Interruptibility

# Future: Cancel long-running operations
async for event in agent.run_stream(query):
    if user_clicked_cancel:
        await agent.cancel(thread_id, run_id)
        break

Transparency

# Users see the reasoning process
"Looking up order ORD-001..."
"Order found: Status is 'shipped'"
"Retrieving tracking information..."
"Your order has been shipped with tracking number 1Z999AA1..."

To put these benefits in context, here's how AG-UI compares to traditional approaches across key dimensions:

AG-UI vs. Traditional Approaches

AspectTraditional RESTCustom StreamingAG-UI
Connection ModelRequest/ResponseVariesServer-Sent Events
State ManagementManualManualProtocol-managed
Tool CallingInvisibleCustom formatStandardized events
FrameworkVariesFramework-lockedFramework-agnostic
Browser SupportUniversalVariesUniversal
ImplementationSimpleComplexModerate
EcosystemN/AIsolatedGrowing

You've now seen AG-UI's design principles, implementation details, and conceptual foundations. But the most important question remains: should you actually use it?

Conclusion: Is AG-UI Right for Your Project?

AG-UI represents a shift toward standardized, observable agent interactions. Before adopting it, understand where the protocol stands and whether it fits your needs.

Protocol Maturity

The protocol is stable enough for production use but still evolving:

Ready now: Core specification stable, Microsoft Agent Framework integration available, FastAPI/Python implementation mature, basic streaming and threading work reliably.

Choose AG-UI If You

  • Building new agent projects - No legacy API to maintain, want future compatibility with emerging ecosystem
  • Need streaming observability - Multi-step workflows where users benefit from seeing each stage of execution
  • Want framework flexibility - Same client code works with any AG-UI-compliant backend
  • Comfortable with evolving standards - Can adapt to protocol changes as it matures

Stick with Alternatives If You

  • Have working solutions - Custom streaming working well, migration cost not justified
  • Need guaranteed stability - Mission-critical systems where breaking changes are unacceptable
  • Build simple agents - Single-step request/response without tool calling or streaming needs
  • Risk-averse environment - Large existing implementations where proven approaches are required

Beyond individual project decisions, it's worth considering AG-UI's role in the broader ecosystem.

The Bigger Picture

While this blog post focused on Microsoft Agent Framework, AG-UI's true power lies in its broader mission: creating a common language for agent-UI communication across the entire ecosystem. As more frameworks adopt it, the real value emerges: write your UI once, work with any compliant agent framework.

Think of it like GraphQL for APIs or OpenAPI for REST - a standardization layer that benefits the entire ecosystem.

The protocol is young, but the problem it solves is real. Whether you adopt it now or wait for broader adoption, understanding AG-UI helps you make informed architectural decisions for your agent applications.

Ready to dive deeper? Here are the official resources to continue your AG-UI journey.

Resources

AG-UI & Microsoft Agent Framework

UI Components & Integration

Community & Support

Related Technologies

This blog post introduces AG-UI with Microsoft Agent Framework, focusing on fundamental concepts and building your first interactive agent application.

Updated Jan 21, 2026
Version 1.0

4 Comments

  • Can this be used with any regular frontend frameworks without changing code?

    • pratikpanda's avatar
      pratikpanda
      Icon for Microsoft rankMicrosoft

      You'll need some frontend code changes. AG-UI standardizes the protocol (what events flow between agent and UI), but you still need a client to handle those events.

      Your options:

      1. React/Next.js: Use CopilotKit - provides ready-made components and hooks. Minimal integration code needed.
      2. Other frameworks: AG-UI provides client SDKs for:
        • TypeScript/JavaScript (@ag-ui/client)
        • Kotlin, Java, Go, Dart, Rust (community SDKs)
        • React Native (in progress)
      3. Custom: For frameworks without SDKs (Vue, Angular, Svelte), you implement the SSE client yourself. The protocol is standardized, so it's straightforward - establish connection, parse ~16 event types, update UI.


      CopilotKit makes it easiest for React, other frameworks need light client implementation.

  • Excellent walkthrough. quick one- do you see AG-UI evolving into a broader cross-vendor standard (like  OpenAPI), or remaining primarily framework-driven?

    Compared to today’s callback-based streaming approaches, AG-UI feels much closer to a long-term interoperability layer — curious how you see versioning and backward compatibility playing out.

    • pratikpanda's avatar
      pratikpanda
      Icon for Microsoft rankMicrosoft

      Thank you for the excellent questions!

      On cross-vendor standardization:
       Based on current adoption, AG-UI is evolving toward broader standardization. The protocol now has integrations across multiple major frameworks - Microsoft Agent Framework (Python & .NET), LangGraph, CrewAI, Google ADK, Pydantic AI, and others - as well as frontend platforms like CopilotKit. It's moving beyond a framework-driven approach toward becoming a shared standard.

      On versioning: The event-based architecture provides natural extension points - new event types can be added without breaking existing clients (they ignore unknown events), and payloads can be versioned independently. As adoption grows, the protocol would benefit from explicit versioning semantics similar to OpenAPI's evolution.