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)?
| Aspect | WebSockets | SSE (AG-UI) |
|---|---|---|
| Complexity | Bidirectional | Unidirectional (simpler) |
| Firewall/Proxy | Sometimes blocked | Standard HTTP |
| Reconnection | Manual implementation | Built-in browser support |
| Use case | Real-time games, chat | Agent 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_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END - streaming segments of text
- TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, TOOL_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-core, fastapi, 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:
- RUN_STARTED - Agent begins processing the request
- TEXT_MESSAGE_START - First message starts (will contain tool calls)
- TOOL_CALL_START - Agent invokes the get_order_status tool
- Multiple TOOL_CALL_ARGS events - Arguments stream incrementally as JSON chunks ({"order_id":"ORD-001"})
- TOOL_CALL_END - Tool invocation structure complete
- TOOL_CALL_RESULT - Tool execution finished with result data
- TEXT_MESSAGE_START - Second message starts (the final response)
- Multiple TEXT_MESSAGE_CONTENT events - Response text streams word-by-word
- TEXT_MESSAGE_END - Response message complete
- 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
| Aspect | Traditional REST | Custom Streaming | AG-UI |
|---|---|---|---|
| Connection Model | Request/Response | Varies | Server-Sent Events |
| State Management | Manual | Manual | Protocol-managed |
| Tool Calling | Invisible | Custom format | Standardized events |
| Framework | Varies | Framework-locked | Framework-agnostic |
| Browser Support | Universal | Varies | Universal |
| Implementation | Simple | Complex | Moderate |
| Ecosystem | N/A | Isolated | Growing |
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
- Getting Started with AG-UI (Microsoft Learn) - Official tutorial
- AG-UI Integration Overview - Architecture and concepts
- AG-UI Protocol Specification - Official protocol documentation
- Backend Tool Rendering - Adding function tools
- Security Considerations - Production security guidance
- Microsoft Agent Framework Documentation - Framework overview
- AG-UI Dojo Examples - Live demonstrations
UI Components & Integration
- CopilotKit for Microsoft Agent Framework - React component library
Community & Support
- Microsoft Q&A - Community support
- Agent Framework GitHub - Source code and issues
Related Technologies
- Azure AI Foundry Documentation - Azure AI platform
- FastAPI Documentation - Web framework
- Server-Sent Events (SSE) Specification - Protocol standard
This blog post introduces AG-UI with Microsoft Agent Framework, focusing on fundamental concepts and building your first interactive agent application.