AG-UI Metadata Schema¶
Overview¶
Hector enriches all A2A protocol parts with AG-UI (Agent User Interaction) 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
AG-UI Metadata Fields¶
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"
}
}
Tool Result Part¶
{
"data": {
"data": {
"tool_call_id": "call-9abc",
"content": "Found 5 files matching 'authentication'...",
"error": ""
}
},
"metadata": {
"agui_event_type": "tool_call",
"agui_tool_call_id": "call-9abc",
"agui_is_error": false
}
}
Implementation Details¶
Creating AG-UI-Enriched Parts¶
Hector provides helper functions in pkg/protocol/agui_metadata.go for creating AG-UI-enriched parts:
// Create a text content part with AG-UI metadata
part := protocol.CreateTextPartWithAGUI(
text, // "Hello world"
blockID, // "block-uuid" or "" for auto-generation
blockIndex // 0, 1, 2, ...
)
// Create a thinking block part
thinkingPart := protocol.CreateThinkingPart(
thinkingText, // "[Thinking: reasoning process...]"
blockID, // "" for auto-generation
blockIndex // index within message
)
// Create a tool call part (automatically includes AG-UI metadata)
toolCallPart := protocol.CreateToolCallPart(&protocol.ToolCall{
ID: "call-123",
Name: "search",
Args: map[string]interface{}{"query": "example"},
})
// Create a tool result part (automatically includes AG-UI metadata)
toolResultPart := protocol.CreateToolResultPart(&protocol.ToolResult{
ToolCallID: "call-123",
Content: "Search results...",
Error: "",
})
Reading AG-UI Metadata¶
Both A2A and AG-UI clients can read metadata hints:
// Check if a part is a thinking block
if protocol.IsThinkingPart(part) {
// Display as thinking block in UI
}
// Get AG-UI event type
eventType := protocol.GetAGUIEventType(part)
// Returns: "thinking", "tool_call", "content_block", etc.
// Get AG-UI block ID
blockID := protocol.GetAGUIBlockID(part)
// Get AG-UI block type
blockType := protocol.GetAGUIBlockType(part)
// Returns: "text", "thinking", "code", etc.
AG-UI Converter¶
When clients opt-in to AG-UI streaming (via Accept: application/x-agui-events header or format=agui query parameter), Hector's AG-UI converter (pkg/agui/converter.go) reads these metadata hints to generate proper AG-UI events:
// The converter reads AG-UI metadata hints
aguiEventType := part.Metadata.Fields["agui_event_type"].GetStringValue()
aguiBlockType := part.Metadata.Fields["agui_block_type"].GetStringValue()
aguiBlockID := part.Metadata.Fields["agui_block_id"].GetStringValue()
// And generates appropriate AG-UI events
if aguiEventType == "thinking" {
events = append(events,
NewThinkingStartEvent(aguiBlockID, ""),
NewThinkingDeltaEvent(aguiBlockID, text),
NewThinkingStopEvent(aguiBlockID, ""),
)
}
Client-Side Handling¶
A2A Native UI (index.html)¶
Hector's built-in A2A UI recognizes AG-UI metadata to provide rich contextual display:
// Check if part is a thinking block
function isThinkingPart(part) {
if (!part || !part.metadata) return false;
// Check AG-UI metadata
return part.metadata.agui_event_type === 'thinking' ||
part.metadata.agui_block_type === 'thinking';
}
// Extract text, excluding thinking blocks
function extractTextFromParts(parts) {
return parts
.filter(part => !isThinkingPart(part))
.map(part => part.text || '')
.join('\n');
}
// Extract thinking text separately
function extractThinkingFromParts(parts) {
return parts
.filter(isThinkingPart)
.map(part => part.text || '')
.join('\n');
}
// Extract tool calls with AG-UI metadata
function extractToolCallsFromParts(parts) {
return parts
.filter(part => {
if (!part.metadata) return false;
// Check AG-UI metadata (tool calls don't have agui_is_error)
return part.metadata.agui_event_type === 'tool_call' &&
!part.metadata.hasOwnProperty('agui_is_error');
})
.map(part => {
const data = part.data?.data || {};
const metadata = part.metadata || {};
return {
id: metadata.agui_tool_call_id || data.id,
name: metadata.agui_tool_name || data.name,
args: data.arguments || {}
};
});
}
AG-UI Native UIs¶
External AG-UI-native UIs can opt-in to receive AG-UI events directly:
# Request AG-UI format via Accept header
curl -H "Accept: application/x-agui-events" \
-X POST http://localhost:8080/v1/agents/assistant/stream \
-d '{"request": {"role": "user", "parts": [{"text": "Hello"}]}}'
# Or via query parameter
curl -X POST 'http://localhost:8080/v1/agents/assistant/stream?format=agui' \
-d '{"request": {"role": "user", "parts": [{"text": "Hello"}]}}'
Theresponse will be AG-UI events generated from the A2A parts' metadata hints:
event: message_start
data: {"messageId":"msg-123","contextId":"ctx-456","role":"agent"}
event: thinking_start
data: {"thinkingId":"think-789"}
event: thinking_delta
data: {"thinkingId":"think-789","delta":"Analyzing request..."}
event: thinking_stop
data: {"thinkingId":"think-789"}
event: content_block_start
data: {"blockId":"block-abc","blockType":"text"}
event: content_block_delta
data: {"blockId":"block-abc","delta":"Hello! How can I help?"}
event: content_block_stop
data: {"blockId":"block-abc"}
event: message_stop
data: {"messageId":"msg-123"}
Benefits¶
For A2A Clients¶
- Semantic Context: Understand the meaning of parts (thinking vs. regular content)
- Better UX: Display thinking blocks, tool calls, and content distinctly
- Protocol Compliant: No special handling needed - just read metadata if available
For AG-UI Clients¶
- No Guesswork: Event types are explicit in metadata
- Simpler Conversion: AG-UI converter reads hints directly
- Consistent Experience: Same semantics whether A2A or AG-UI format
For Hector¶
- Single Source of Truth: Part semantics defined once at creation
- A2A Native: Core protocol remains A2A
- AG-UI Compatible: Trivial conversion to AG-UI events
- Zero Configuration: Works out of the box for all agents
Specification Compliance¶
A2A Compliance ✅¶
From the A2A specification, Section 6.5 (Part Union Type):
Part Object: -
metadata?: Record<string, any>- Optional metadata associated with this part
The A2A spec explicitly provides the metadata field as an extension point without prescribing its contents. Hector's use of AG-UI metadata is fully compliant with A2A.
AG-UI Event Types¶
Hector's AG-UI metadata hints map to the 16 standardized AG-UI event types:
- Message Events:
message_start,message_delta,message_stop - Content Block Events:
content_block_start,content_block_delta,content_block_stop - Tool Call Events:
tool_call_start,tool_call_stop - Thinking Events:
thinking_start,thinking_delta,thinking_stop - Task Events:
task_start,task_update,task_complete,task_error - Error Events:
error
Configuration¶
No configuration required! AG-UI metadata is automatically added to all A2A parts by default.
The only configuration is for clients opting-in to AG-UI event streaming:
# Via Accept header
Accept: application/x-agui-events
# Via query parameter
?format=agui
Summary¶
- A2A Native: Hector uses A2A as its core protocol
- AG-UI Compatible: AG-UI metadata hints are embedded in A2A parts'
metadatafield - Zero Configuration: AG-UI is a default capability for all agents
- Backward Compatible: A2A clients ignore unfamiliar metadata
- Specification Compliant: Fully compliant with A2A spec's extension mechanism
- Rich Semantics: Thinking blocks, tool calls, and content have explicit types
This approach makes Hector truly A2A, MCP, and AG-UI native without conflicting protocols or complex configuration.