AG-UI Protocol Support¶
Hector implements AG-UI streaming events as an optional output format alongside the native A2A protocol. This enables compatibility with modern agentic UIs that have adopted this standardized streaming format.
Implementation Scope
Hector implements AG-UI Streaming Events Layer - the 16 standardized event types for real-time UI updates. The full AG-UI specification includes additional features (bidirectional interaction, state management, human-in-the-loop) which are future considerations.
Overview¶
What is AG-UI?¶
AG-UI (Agent User Interaction) is a standardized protocol for agent-to-UI communication. The core component we implement is the streaming events layer - 16 event types that provide fine-grained updates for UI rendering.
Implementation Level¶
┌─────────────────────────────────────────────┐
│ AG-UI Streaming Events (✅ Implemented) │
├─────────────────────────────────────────────┤
│ • 16 event types │
│ • Lifecycle management (start/delta/stop) │
│ • SSE/WebSocket transport │
│ • A2A → AG-UI conversion │
└─────────────────────────────────────────────┘
↓ Future considerations
┌─────────────────────────────────────────────┐
│ AG-UI Full Spec (Not Implemented) │
├─────────────────────────────────────────────┤
│ • Bidirectional interaction │
│ • State management │
│ • Human-in-the-loop workflows │
│ • Multi-agent handoff │
└─────────────────────────────────────────────┘
Key Principles¶
- A2A First: Hector is 100% A2A compatible by default. A2A is the native protocol.
- AG-UI as Output Layer: AG-UI is an optional streaming format for UI compatibility
- Opt-in at Runtime: Clients choose format via Accept header
- No Configuration: Works out-of-the-box with any agent
Event Types (16 Total)¶
Message Events (3)¶
message_start: Message beginsmessage_delta: Incremental message textmessage_stop: Message ends
Content Block Events (3)¶
content_block_start: Content section begins (with blockId, blockType)content_block_delta: Incremental content textcontent_block_stop: Content section ends
Tool Call Events (3)¶
tool_call_start: Tool invocation begins (with toolName, input)tool_call_delta: Streaming tool parameters (optional)tool_call_stop: Tool execution completes (with result, error)
Thinking Events (3)¶
thinking_start: Agent reasoning beginsthinking_delta: Incremental reasoning textthinking_stop: Reasoning complete
Task Events (4)¶
task_start: Task createdtask_update: Task status changetask_complete: Task finished successfullytask_error: Task failed
AG-UI Metadata Schema¶
Hector enriches all A2A protocol parts with AG-UI metadata to make them natively compatible with AG-UI-based user interfaces while remaining 100% A2A compliant. This is achieved by embedding AG-UI event type hints in the A2A Part.metadata field, which is explicitly designed for protocol extensions.
Why AG-UI Metadata in A2A Parts?¶
The A2A (Agent-to-Agent) Protocol is Hector's native protocol and intentionally does not prescribe specific contextual types like "thinking", "tool_call", or "task" in its core specification. Instead, A2A provides an optional metadata field on Part messages for custom extensions.
AG-UI (Agent User Interaction) Protocol is a standardized streaming event format for agent UIs that defines specific event types for rich user experiences (thinking blocks, tool calls, tasks, etc.).
By embedding AG-UI metadata hints in A2A parts, Hector achieves:
- A2A Native: All parts remain valid A2A messages
- AG-UI Ready: AG-UI clients can read metadata hints for proper UI rendering
- Zero Configuration: No special configuration needed - AG-UI is a default capability
- Backward Compatible: A2A-only clients simply ignore unfamiliar metadata
Core Metadata Fields¶
All AG-UI-enriched A2A parts include these optional metadata fields:
| Field | Type | Description |
|---|---|---|
agui_event_type |
string | The AG-UI event type: "content_block", "thinking", "tool_call", "task", "error", or "message" |
agui_block_type |
string | For content blocks: "text", "thinking", or "code" |
agui_block_id |
string | Unique identifier for this content block |
agui_block_index |
integer | Sequential index of this block within the message |
Tool Call Metadata Fields¶
For tool call and tool result parts:
| Field | Type | Description |
|---|---|---|
agui_tool_call_id |
string | Unique identifier for the tool call |
agui_tool_name |
string | Name of the tool being called |
agui_is_error |
boolean | Whether this tool result represents an error |
Example A2A Parts with AG-UI Metadata¶
Text Content Part:
{
"text": "Hello! How can I help you today?",
"metadata": {
"agui_event_type": "content_block",
"agui_block_type": "text",
"agui_block_id": "block-1234",
"agui_block_index": 0
}
}
Thinking Block Part:
{
"text": "[Thinking: Analyzing the user's request...]\n",
"metadata": {
"agui_event_type": "thinking",
"agui_block_type": "thinking",
"agui_block_id": "think-5678",
"agui_block_index": 1
}
}
Tool Call Part:
{
"data": {
"data": {
"id": "call-9abc",
"name": "search_code",
"arguments": {
"query": "authentication logic"
}
}
},
"metadata": {
"agui_event_type": "tool_call",
"agui_tool_call_id": "call-9abc",
"agui_tool_name": "search_code"
}
}
Enabling AG-UI Format¶
AG-UI is enabled by default - no configuration required!
Method 1: Accept Header (Recommended)¶
curl -N http://localhost:8080/v1/agents/assistant/message:stream \
-H "Content-Type: application/json" \
-H "Accept: application/x-agui-events" \
-d '{"message": {"parts": [{"text": "Hello"}]}}'
Supported Accept values:
- application/x-agui-events ⭐ (recommended)
- application/agui+json
Method 2: Query Parameter¶
curl -N "http://localhost:8080/v1/agents/assistant/message:stream?format=agui" \
-H "Content-Type: application/json" \
-d '{"message": {"parts": [{"text": "Hello"}]}}'
Supported Endpoints¶
✅ REST SSE: /v1/agents/{agent}/message:stream
✅ JSON-RPC Streaming: /v1/agents/{agent}/stream (POST with message/stream method)
Protocol Comparison¶
A2A Native Format (Default)¶
curl -N http://localhost:8080/v1/agents/assistant/message:stream \
-H "Content-Type: application/json" \
-d '{"message": {"parts": [{"text": "Hello"}]}}'
Response:
event: message
data: {"message":{"messageId":"msg-123","parts":[{"text":"Hello"}]}}
event: message
data: {"message":{"messageId":"msg-123","parts":[{"text":" there!"}]}}
event: done
data: {}
AG-UI Format (Opt-in)¶
curl -N http://localhost:8080/v1/agents/assistant/message:stream \
-H "Accept: application/x-agui-events" \
-d '{"message": {"parts": [{"text": "Hello"}]}}'
Response:
event: task_start
data: {"eventId":"evt-1","type":"AGUI_EVENT_TYPE_TASK_START","timestamp":"2025-11-04T10:52:22Z","taskStart":{"taskId":"task-123"}}
event: message_start
data: {"eventId":"evt-2","type":"AGUI_EVENT_TYPE_MESSAGE_START","timestamp":"2025-11-04T10:52:22Z","messageStart":{"messageId":"msg-123","role":"agent"}}
event: content_block_start
data: {"eventId":"evt-3","type":"AGUI_EVENT_TYPE_CONTENT_BLOCK_START","timestamp":"2025-11-04T10:52:22Z","contentBlockStart":{"blockId":"blk-1","blockType":"text"}}
event: content_block_delta
data: {"eventId":"evt-4","type":"AGUI_EVENT_TYPE_CONTENT_BLOCK_DELTA","timestamp":"2025-11-04T10:52:22Z","contentBlockDelta":{"blockId":"blk-1","delta":"Hello"}}
event: content_block_delta
data: {"eventId":"evt-5","type":"AGUI_EVENT_TYPE_CONTENT_BLOCK_DELTA","timestamp":"2025-11-04T10:52:22Z","contentBlockDelta":{"blockId":"blk-1","delta":" there!"}}
event: content_block_stop
data: {"eventId":"evt-6","type":"AGUI_EVENT_TYPE_CONTENT_BLOCK_STOP","timestamp":"2025-11-04T10:52:22Z","contentBlockStop":{"blockId":"blk-1"}}
event: message_stop
data: {"eventId":"evt-7","type":"AGUI_EVENT_TYPE_MESSAGE_STOP","timestamp":"2025-11-04T10:52:22Z","messageStop":{"messageId":"msg-123"}}
event: task_complete
data: {"eventId":"evt-8","type":"AGUI_EVENT_TYPE_TASK_COMPLETE","timestamp":"2025-11-04T10:52:22Z","taskComplete":{"taskId":"task-123"}}
Event Mapping¶
A2A → AG-UI Conversion¶
Hector automatically converts A2A events to AG-UI events at the transport layer:
| A2A Event | AG-UI Events | Details |
|---|---|---|
| Task (SUBMITTED) | task_start → message_start |
Task creation triggers message lifecycle |
| Message (text part) | content_block_start → content_block_delta → content_block_stop |
Text wrapped in content blocks |
| Message (tool_call part) | tool_call_start → tool_call_stop |
Tool invocation lifecycle |
| Message (thinking part) | thinking_start → thinking_delta → thinking_stop |
Reasoning process exposed |
| Task (WORKING) | task_update |
Status change notification |
| Task (COMPLETED) | message_stop → task_complete |
Message and task completion |
| StatusUpdate | task_update |
Incremental status changes |
| ArtifactUpdate | Content blocks | Artifacts as content |
Example: Tool Call¶
A2A Format:
{
"message": {
"parts": [{
"data": {
"data": {"name": "web_search", "input": "{\"query\":\"weather\"}"}
},
"metadata": {"part_type": "tool_call", "tool_call_id": "tc-123"}
}]
}
}
AG-UI Format:
{
"eventId": "evt-1",
"type": "AGUI_EVENT_TYPE_TOOL_CALL_START",
"timestamp": "2025-11-04T10:52:22Z",
"toolCallStart": {
"toolCallId": "tc-123",
"toolName": "web_search",
"input": {"query": "weather"}
}
}
Example: Thinking¶
When show_thinking: true in reasoning config:
A2A Format:
{
"message": {
"parts": [{
"text": "Let me analyze this step by step...",
"metadata": {"part_type": "thinking", "title": "Planning"}
}]
}
}
AG-UI Format:
[
{
"type": "AGUI_EVENT_TYPE_THINKING_START",
"thinkingStart": {"thinkingId": "think-123", "title": "Planning"}
},
{
"type": "AGUI_EVENT_TYPE_THINKING_DELTA",
"thinkingDelta": {"thinkingId": "think-123", "delta": "Let me analyze this step by step..."}
},
{
"type": "AGUI_EVENT_TYPE_THINKING_STOP",
"thinkingStop": {"thinkingId": "think-123"}
}
]
Agent Card Declaration¶
AG-UI is advertised in the agent card as an optional extension:
curl http://localhost:8080/v1/agents/assistant/ | jq
{
"name": "Assistant",
"capabilities": {
"streaming": true,
"extensions": [
{
"uri": "https://ag-ui.org/protocol/v1",
"description": "AG-UI Protocol - Standardized streaming event format for agent UIs. Clients can opt-in via Accept header 'application/x-agui-events' or query parameter 'format=agui'.",
"required": false
}
]
},
"defaultOutputModes": [
"text/plain",
"application/json",
"application/x-agui-events"
]
}
The required: false indicates that AG-UI is optional - clients can use A2A or AG-UI.
Configuration¶
No configuration required! AG-UI is enabled by default as an optional output format.
Every agent automatically supports both A2A and AG-UI with these default output modes:
- text/plain
- application/json
- application/x-agui-events ✨
Simply use any existing agent configuration:
agents:
assistant:
name: "Assistant"
llm: default-llm
reasoning:
engine: "chain-of-thought"
enable_streaming: true
show_thinking: true # Thinking will be converted to AG-UI thinking events
tools:
- web_search
Clients choose their preferred format at runtime via Accept header or query parameter.
UI Implementation Guide¶
Building an AG-UI Compatible Client¶
This section provides guidance for implementing AG-UI clients that consume Hector's AG-UI streaming events.
Architecture Overview¶
┌─────────────────────────────────────────────────────────┐
│ Client Application │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ Event │ State │ UI │ │
│ │ Handler │ Management │ Rendering │ │
│ └──────┬───────┴──────┬───────┴──────┬───────┘ │
│ │ │ │ │
│ └──────────────┴──────────────┘ │
│ Event Stream │
└───────────────────────────┬───────────────────────────────┘
│
┌───────────────────────────▼───────────────────────────────┐
│ Hector AG-UI Event Stream │
│ (SSE: application/x-agui-events) │
└─────────────────────────────────────────────────────────────┘
Step 1: Connect to Event Stream¶
JavaScript/TypeScript:
const eventSource = new EventSource(
'http://localhost:8080/v1/agents/assistant/message:stream',
{
headers: {
'Content-Type': 'application/json',
'Accept': 'application/x-agui-events' // Opt-in to AG-UI
}
}
);
Python:
import requests
import json
url = "http://localhost:8080/v1/agents/assistant/message:stream"
headers = {
"Content-Type": "application/json",
"Accept": "application/x-agui-events" # Opt-in to AG-UI
}
data = {"message": {"parts": [{"text": "Hello"}]}}
response = requests.post(url, headers=headers, json=data, stream=True)
Step 2: Handle Event Types¶
Message Lifecycle:
interface MessageState {
messageId: string;
role: 'user' | 'agent';
blocks: Map<string, ContentBlock>;
thinking: Map<string, ThinkingBlock>;
toolCalls: Map<string, ToolCall>;
}
const messageState = new Map<string, MessageState>();
eventSource.addEventListener('message_start', (e) => {
const event = JSON.parse(e.data);
const msgId = event.messageStart.messageId;
messageState.set(msgId, {
messageId: msgId,
role: event.messageStart.role,
blocks: new Map(),
thinking: new Map(),
toolCalls: new Map()
});
// Render new message container in UI
createMessageContainer(msgId);
});
eventSource.addEventListener('message_stop', (e) => {
const event = JSON.parse(e.data);
const msgId = event.messageStop.messageId;
// Finalize message rendering
finalizeMessage(msgId);
});
Content Blocks:
eventSource.addEventListener('content_block_start', (e) => {
const event = JSON.parse(e.data);
const blockId = event.contentBlockStart.blockId;
const blockType = event.contentBlockStart.blockType;
// Create new content block in UI
createContentBlock(blockId, blockType);
});
eventSource.addEventListener('content_block_delta', (e) => {
const event = JSON.parse(e.data);
const blockId = event.contentBlockDelta.blockId;
const delta = event.contentBlockDelta.delta;
// Append delta to content block (streaming text)
appendToBlock(blockId, delta);
});
eventSource.addEventListener('content_block_stop', (e) => {
const event = JSON.parse(e.data);
const blockId = event.contentBlockStop.blockId;
// Mark block as complete
finalizeBlock(blockId);
});
Tool Calls:
eventSource.addEventListener('tool_call_start', (e) => {
const event = JSON.parse(e.data);
const toolCallId = event.toolCallStart.toolCallId;
const toolName = event.toolCallStart.toolName;
const input = event.toolCallStart.input;
// Show tool indicator in UI
showToolCall(toolCallId, toolName, input);
});
eventSource.addEventListener('tool_call_stop', (e) => {
const event = JSON.parse(e.data);
const toolCallId = event.toolCallStop.toolCallId;
const result = event.toolCallStop.result;
const error = event.toolCallStop.error;
// Update tool call with result
updateToolCall(toolCallId, result, error);
});
Thinking Blocks:
eventSource.addEventListener('thinking_start', (e) => {
const event = JSON.parse(e.data);
const thinkingId = event.thinkingStart.thinkingId;
const title = event.thinkingStart.title;
// Show thinking block (collapsible by default)
showThinkingBlock(thinkingId, title);
});
eventSource.addEventListener('thinking_delta', (e) => {
const event = JSON.parse(e.data);
const thinkingId = event.thinkingDelta.thinkingId;
const delta = event.thinkingDelta.delta;
// Append thinking text
appendThinking(thinkingId, delta);
});
eventSource.addEventListener('thinking_stop', (e) => {
const event = JSON.parse(e.data);
const thinkingId = event.thinkingStop.thinkingId;
// Finalize thinking block
finalizeThinking(thinkingId);
});
Task Status:
eventSource.addEventListener('task_start', (e) => {
const event = JSON.parse(e.data);
const taskId = event.taskStart.taskId;
// Show task indicator
showTaskIndicator(taskId);
});
eventSource.addEventListener('task_update', (e) => {
const event = JSON.parse(e.data);
const taskId = event.taskUpdate.taskId;
const status = event.taskUpdate.status;
// Update task status
updateTaskStatus(taskId, status);
});
eventSource.addEventListener('task_complete', (e) => {
const event = JSON.parse(e.data);
const taskId = event.taskComplete.taskId;
// Mark task as complete
completeTask(taskId);
});
eventSource.addEventListener('task_error', (e) => {
const event = JSON.parse(e.data);
const taskId = event.taskError.taskId;
const error = event.taskError.error;
// Show error
showTaskError(taskId, error);
});
Step 3: State Management¶
Maintain state for each message to properly render streaming content:
class AGUIStateManager {
private messages = new Map<string, MessageState>();
handleEvent(eventType: string, eventData: any) {
switch (eventType) {
case 'message_start':
this.startMessage(eventData);
break;
case 'content_block_delta':
this.appendContent(eventData);
break;
case 'tool_call_start':
this.startToolCall(eventData);
break;
// ... handle all event types
}
}
getMessageState(messageId: string): MessageState | undefined {
return this.messages.get(messageId);
}
}
Step 4: UI Rendering Best Practices¶
- Progressive Rendering: Update UI incrementally as deltas arrive
- Visual Indicators: Show loading states for tool calls and thinking
- Error Handling: Display errors from
tool_call_stopandtask_errorevents - Collapsible Thinking: Make thinking blocks collapsible by default
- Tool Call Visualization: Show tool name, input, and result clearly
- Task Status: Display task progress and status updates
Step 5: Complete Example¶
React Component Example:
import React, { useEffect, useState } from 'react';
interface AGUIEvent {
eventId: string;
type: string;
timestamp: string;
[key: string]: any;
}
export function AGUIChat() {
const [messages, setMessages] = useState<Map<string, any>>(new Map());
useEffect(() => {
const eventSource = new EventSource(
'http://localhost:8080/v1/agents/assistant/message:stream',
{
headers: {
'Accept': 'application/x-agui-events'
}
}
);
eventSource.addEventListener('message_start', (e) => {
const event: AGUIEvent = JSON.parse(e.data);
setMessages(prev => {
const next = new Map(prev);
next.set(event.messageStart.messageId, {
id: event.messageStart.messageId,
role: event.messageStart.role,
content: '',
toolCalls: [],
thinking: []
});
return next;
});
});
eventSource.addEventListener('content_block_delta', (e) => {
const event: AGUIEvent = JSON.parse(e.data);
setMessages(prev => {
const next = new Map(prev);
const msg = next.get(event.contentBlockDelta.blockId);
if (msg) {
msg.content += event.contentBlockDelta.delta;
}
return next;
});
});
// ... handle other events
return () => eventSource.close();
}, []);
return (
<div className="chat-container">
{Array.from(messages.values()).map(msg => (
<div key={msg.id} className={`message ${msg.role}`}>
<div className="content">{msg.content}</div>
{msg.toolCalls.map(tc => (
<div key={tc.id} className="tool-call">
🔧 {tc.name}: {tc.result}
</div>
))}
</div>
))}
</div>
);
}
Using A2A Format with AG-UI Metadata¶
If you prefer A2A format but want AG-UI metadata hints:
// A2A parts include AG-UI metadata automatically
function extractAGUIMetadata(part) {
if (!part.metadata) return null;
return {
eventType: part.metadata.agui_event_type,
blockType: part.metadata.agui_block_type,
blockId: part.metadata.agui_block_id,
toolCallId: part.metadata.agui_tool_call_id,
toolName: part.metadata.agui_tool_name
};
}
// Use metadata to render parts appropriately
function renderPart(part) {
const metadata = extractAGUIMetadata(part);
if (metadata?.eventType === 'thinking') {
return renderThinkingBlock(part.text);
} else if (metadata?.eventType === 'tool_call') {
return renderToolCall(part.data.data);
} else {
return renderTextContent(part.text);
}
}
Client Implementation Examples¶
JavaScript/TypeScript Example¶
const eventSource = new EventSource(
'http://localhost:8080/v1/agents/assistant/message:stream',
{
headers: {
'Content-Type': 'application/json',
'Accept': 'application/x-agui-events' // Opt-in to AG-UI
}
}
);
// Handle different event types
eventSource.addEventListener('message_start', (e) => {
const event = JSON.parse(e.data);
console.log('Message started:', event.messageStart.messageId);
});
eventSource.addEventListener('content_block_delta', (e) => {
const event = JSON.parse(e.data);
const delta = event.contentBlockDelta.delta;
// Render delta to UI
appendToMessage(delta);
});
eventSource.addEventListener('tool_call_start', (e) => {
const event = JSON.parse(e.data);
const toolName = event.toolCallStart.toolName;
// Show tool indicator
showToolIndicator(toolName);
});
eventSource.addEventListener('thinking_delta', (e) => {
const event = JSON.parse(e.data);
const thinkingText = event.thinkingDelta.delta;
// Show thinking process
renderThinking(thinkingText);
});
eventSource.addEventListener('task_complete', (e) => {
const event = JSON.parse(e.data);
console.log('Task completed:', event.taskComplete.taskId);
eventSource.close();
});
Python Example¶
import requests
import json
url = "http://localhost:8080/v1/agents/assistant/message:stream"
headers = {
"Content-Type": "application/json",
"Accept": "application/x-agui-events" # Opt-in to AG-UI
}
data = {"message": {"parts": [{"text": "Hello"}]}}
response = requests.post(url, headers=headers, json=data, stream=True)
for line in response.iter_lines():
if line:
line_str = line.decode('utf-8')
if line_str.startswith('event:'):
event_type = line_str.split(':', 1)[1].strip()
elif line_str.startswith('data:'):
event_data = json.loads(line_str.split(':', 1)[1].strip())
if event_type == 'content_block_delta':
delta = event_data['contentBlockDelta']['delta']
print(delta, end='', flush=True)
elif event_type == 'tool_call_start':
tool_name = event_data['toolCallStart']['toolName']
print(f"\n🔧 {tool_name}", flush=True)
Testing¶
✅ Implementation tested and verified!
Start any Hector agent (AG-UI is always available):
# Zero-config mode (easiest)
./hector serve --tools
# Or use any existing config
./hector serve --config configs/coding.yaml
Test with curl:
# A2A format (default) - ✅ Tested
curl -N http://localhost:8080/v1/agents/assistant/message:stream \
-H "Content-Type: application/json" \
-d '{"message": {"parts": [{"text": "Hello"}]}}'
# AG-UI format - ✅ Tested
curl -N http://localhost:8080/v1/agents/assistant/message:stream \
-H "Content-Type: application/json" \
-H "Accept: application/x-agui-events" \
-d '{"message": {"parts": [{"text": "Hello"}]}}'
# Check agent card - ✅ Tested
curl http://localhost:8080/v1/agents/assistant/ | jq '.capabilities.extensions'
Expected Results:
- A2A format returns event: message with A2A Message structure
- AG-UI format returns event: message_start, event: content_block_delta, etc.
- Agent card shows AG-UI extension at https://ag-ui.org/protocol/v1
Benefits¶
For UI Developers¶
- Standardized Events: Use the same event handlers across different agent platforms
- Fine-Grained Control: Separate events for different UI elements (thinking, tools, content)
- Progressive Rendering: Stream content as it's generated with delta events
- Better UX: Show tool calls, thinking process, and task status separately
For Hector Users¶
- Backward Compatibility: Existing A2A clients continue to work
- Future-Proof: Compatible with emerging agentic UI frameworks
- No Configuration: Works out of the box with opt-in mechanism
- Dual Protocol: Support both A2A agents and AG-UI UIs simultaneously
Architecture¶
Conversion Layer¶
The conversion from A2A to AG-UI happens at the transport layer (REST gateway):
┌─────────────────────────────────────────────────────────┐
│ Client Request │
│ (Accept: application/x-agui-events or ?format=agui) │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ REST Gateway │
│ • Detects AG-UI format preference │
│ • Creates restStreamWrapper with useAGUI=true │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ Agent (A2A Native) │
│ • Generates A2A StreamResponse events │
│ • Task, Message, StatusUpdate, ArtifactUpdate │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ restStreamWrapper.sendAsAGUI() │
│ • Converts A2A events to AG-UI events │
│ • Maintains state (message, blocks, tools, thinking) │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ SSE Response │
│ event: message_start │
│ data: {...} │
│ │
│ event: content_block_delta │
│ data: {...} │
└──────────────────────────────────────────────────────────┘
Key Components¶
pkg/agui/proto/agui.proto: Protocol buffer definition for AG-UI eventspkg/agui/converter.go: A2A Part → AG-UI events converterpkg/agui/events.go: AG-UI event builder functionspkg/transport/rest_gateway.go: HTTP/SSE handler with AG-UI supportpkg/cli/agui_handler.go: Reference implementation for displaying AG-UI events
Limitations & Future Work¶
What's Implemented ✅¶
- ✅ All 16 AG-UI streaming event types
- ✅ SSE transport with proper event names
- ✅ A2A → AG-UI conversion at transport layer
- ✅ Opt-in mechanism (Accept header / query param)
- ✅ Agent card declaration
- ✅ Zero configuration
- ✅ AG-UI metadata in A2A parts
What's Not Implemented (AG-UI Full Spec) ⚠️¶
The full AG-UI specification includes features beyond streaming events:
- ❌ Bidirectional Communication: Client → Agent tool invocations, interrupts
- ❌ State Management: Structured state, incremental updates, snapshots
- ❌ Human-in-the-Loop: Pause/modify/approve workflows (Note: Hector implements HITL via A2A protocol)
- ❌ Multi-Agent Handoff: Agent-to-agent collaboration protocol
- ❌ Client-Defined Tools: Front-end defines tools for agent to call
These features are future considerations and would require: 1. Extended A2A protocol support 2. Bidirectional WebSocket transport 3. State synchronization mechanisms 4. Workflow management APIs
Current Implementation Scope¶
We implement AG-UI Streaming Events - the event format layer that enables compatibility with AG-UI-aware display UIs. This is valuable for:
- UI Framework Compatibility: Works with streaming chat UIs (Vercel AI SDK, etc.)
- Progressive Rendering: Fine-grained events for smooth UX
- Tool/Thinking Visibility: Separate events for different interaction types
- Standardization: Common format across agent platforms
For full AG-UI spec features, consider: - Using A2A protocol directly (bidirectional, state management) - MCP for client-defined tools - A2A multi-agent collaboration features
Summary¶
✅ A2A Native: Default protocol, always works
✅ AG-UI Optional: Opt-in streaming event format
✅ Zero Configuration: Works out-of-the-box
✅ Full Compatibility: Both protocols coexist seamlessly
✅ Tested & Verified: All features working correctly
✅ UI Implementation Guide: Complete guidance for building AG-UI clients
The implementation successfully provides AG-UI streaming event compatibility while maintaining 100% A2A protocol fidelity.
When to Use Each Protocol
- Use A2A for: Full-featured agent integration, bidirectional communication, state management
- Use AG-UI for: Streaming UI compatibility, display-only clients, framework integration
- Use Both for: Maximum compatibility with diverse clients