A2UI Integration
Overviewβ
The CAIPE UI includes a custom implementation of the A2UI (Agent-to-Agent UI) specification, enabling AI agents to generate rich, declarative user interfaces that render safely across the web interface without executing arbitrary code.
Status: The A2UI renderer is currently dormant infrastructure - fully implemented and tested, but not yet actively used in the main rendering pipeline. It's ready to be activated when agents need to generate declarative UIs beyond plain text responses.
What is A2UI?β
A2UI is a protocol developed by Google for agent-driven interfaces that solves the problem: "How can AI agents safely send rich UIs across trust boundaries?"
Core Principlesβ
From the official A2UI specification:
- Secure by Design: Declarative data format, not executable code. Agents can only use pre-approved componentsβno UI injection attacks.
- LLM-Friendly: Flat, streaming JSON structure designed for easy generation by language models.
- Framework-Agnostic: One agent response works everywhere (web, mobile, desktop).
- Progressive Rendering: Stream UI updates as they're generated in real-time.
How It Worksβ
ββββββββββββ ββββββββββββ ββββββββββββ
β Agent βββββββββββ A2UI βββββββββββ Client β
β β Sends β Messages β Renders β (React) β
β (LLM) βββββββββββ (JSON) βββββββββββ (UI) β
ββββββββββββ ββββββββββββ ββββββββββββ
β β β
β 1. Generate A2UI β β
β components β β
ββββββββββββββββββββββ> β
β β β
β β 2. Parse & map β
β β to widgets β
β ββββββββββββββββββββββ>
β β β
β β 3. Render native β
β β components β
β β β
CAIPE's A2UI Implementationβ
Architectureβ
The CAIPE UI implements a custom React renderer that follows the A2UI v0.8 specification:
ui/src/components/a2a/
βββ A2UIRenderer.tsx # Main renderer component
βββ widgets/
β βββ ButtonWidget.tsx # Button component
β βββ FormWidget.tsx # Form component
β βββ CardWidget.tsx # Card component
β βββ ListWidget.tsx # List component
β βββ TableWidget.tsx # Table component
β βββ ProgressWidget.tsx # Progress bars
β βββ SelectWidget.tsx # Dropdowns
β βββ InputWidget.tsx # Text inputs
βββ WidgetCatalog.tsx # Widget registry
Protocol Typesβ
The implementation defines A2UI protocol types that match the official specification:
// A2UI Messages
interface A2UIMessage {
surfaceUpdate?: A2UISurfaceUpdate;
dataModelUpdate?: A2UIDataModelUpdate;
beginRendering?: { surfaceId: string };
deleteSurface?: { surfaceId: string };
}
// Surface: Container for UI components
interface A2UISurfaceUpdate {
surfaceId: string; // Unique surface identifier
components: A2UIComponent[]; // Components to render
}
// Component: Individual UI element
interface A2UIComponent {
id: string; // Component ID
component: Record<string, unknown>; // Component spec
position?: { x: number; y: number }; // Optional positioning
}
// Data Model: Reactive data binding
interface A2UIDataModelUpdate {
contents: Record<string, unknown>; // Data updates
}
Message Flowβ
// Agent sends A2UI message
const a2uiMessage: A2UIMessage = {
surfaceUpdate: {
surfaceId: "main-surface",
components: [
{
id: "deploy-button",
component: {
Button: {
child: {
Text: { text: { literalString: "Deploy to Production" } }
},
action: {
name: "deploy",
context: { app: "my-app", env: "prod" }
}
}
}
}
]
}
};
// A2UIRenderer converts to CAIPE widgets
// Renders as React component with proper styling
Supported Componentsβ
The A2UI renderer supports the standard A2UI component catalog:
1. Buttonβ
Interactive button with action handlers.
A2UI Spec:
{
"id": "action-btn",
"component": {
"Button": {
"child": {
"Text": { "text": { "literalString": "Execute" } }
},
"action": {
"name": "execute_task",
"context": { "taskId": "123" }
}
}
}
}
Rendered: Material design button with click handler that sends action back to agent.
2. Textβ
Text display with optional usage hints (heading, paragraph, etc.).
A2UI Spec:
{
"id": "title",
"component": {
"Text": {
"text": { "literalString": "Application Status" },
"usageHint": "h1"
}
}
}
Rendered: Styled heading or text element based on usageHint.
3. Formβ
Input form with fields and submission.
A2UI Spec:
{
"id": "deploy-form",
"component": {
"Form": {
"title": "Deployment Configuration",
"fields": [
{
"name": "app_name",
"label": "Application",
"type": "text",
"required": true
},
{
"name": "environment",
"label": "Environment",
"type": "select",
"options": ["dev", "staging", "prod"]
}
],
"submitAction": {
"name": "deploy",
"context": {}
}
}
}
}
Rendered: Complete form with validation, styled inputs, and submit button.
4. Listβ
Ordered or unordered list with optional status indicators.
A2UI Spec:
{
"id": "tasks",
"component": {
"List": {
"title": "Deployment Steps",
"ordered": true,
"items": [
{ "id": "1", "text": "Build image", "status": "completed" },
{ "id": "2", "text": "Push to registry", "status": "in_progress" },
{ "id": "3", "text": "Deploy to cluster", "status": "pending" }
]
}
}
}
Rendered: Styled list with status icons (β, β³, β).
5. Tableβ
Data table with headers and rows.
A2UI Spec:
{
"id": "apps-table",
"component": {
"Table": {
"title": "ArgoCD Applications",
"headers": ["Name", "Status", "Sync", "Health"],
"rows": [
["app-1", "Active", "Synced", "Healthy"],
["app-2", "Active", "OutOfSync", "Degraded"]
]
}
}
}
Rendered: Responsive table with hover effects and proper spacing.
6. Progressβ
Progress bar or indicator.
A2UI Spec:
{
"id": "progress",
"component": {
"Progress": {
"value": 75,
"max": 100,
"label": "Deployment Progress"
}
}
}
Rendered: Animated progress bar with percentage display.
7. Selectβ
Dropdown selection menu.
A2UI Spec:
{
"id": "env-select",
"component": {
"Select": {
"label": "Environment",
"options": [
{ "value": "dev", "label": "Development" },
{ "value": "prod", "label": "Production" }
],
"action": {
"name": "environment_changed",
"context": {}
}
}
}
}
Rendered: Styled dropdown with change handler.
8. Inputβ
Text input field.
A2UI Spec:
{
"id": "app-input",
"component": {
"Input": {
"label": "Application Name",
"placeholder": "my-app",
"type": "text",
"required": true,
"action": {
"name": "input_changed",
"context": {}
}
}
}
}
Rendered: Labeled input with validation indicators.
Implementation Detailsβ
Component Translationβ
The a2uiToWidget function maps A2UI components to CAIPE's widget system:
function a2uiToWidget(component: A2UIComponent): Widget | null {
const { id, component: spec } = component;
// Detect component type from A2UI spec
if ("Button" in spec) {
const btn = spec.Button as {
child?: { Text?: { text?: { literalString?: string } } };
action?: { name: string; context?: Record<string, unknown> };
};
return {
id,
type: "button",
props: {
label: btn.child?.Text?.text?.literalString || "Button",
},
actions: btn.action ? [{ name: btn.action.name, context: btn.action.context }] : [],
};
}
// ... similar mappings for other components
return null; // Unknown component type
}
Surface Managementβ
Surfaces are containers that hold components:
export function A2UIRenderer({ messages, onAction }: A2UIRendererProps) {
const [surfaces, setSurfaces] = useState<Map<string, A2UIComponent[]>>(new Map());
const [dataModel, setDataModel] = useState<Record<string, unknown>>({});
const [activeSurface, setActiveSurface] = useState<string | null>(null);
useEffect(() => {
for (const msg of messages) {
// Handle surface updates
if (msg.surfaceUpdate) {
const { surfaceId, components } = msg.surfaceUpdate;
setSurfaces((prev) => {
const updated = new Map(prev);
const existing = updated.get(surfaceId) || [];
// Merge components (update existing or add new)
const merged = [...existing];
for (const comp of components) {
const idx = merged.findIndex((c) => c.id === comp.id);
if (idx >= 0) {
merged[idx] = comp; // Update
} else {
merged.push(comp); // Add
}
}
updated.set(surfaceId, merged);
return updated;
});
setActiveSurface(surfaceId);
}
// Handle data model updates
if (msg.dataModelUpdate) {
setDataModel((prev) => ({ ...prev, ...msg.dataModelUpdate!.contents }));
}
// Handle rendering lifecycle
if (msg.beginRendering) {
setActiveSurface(msg.beginRendering.surfaceId);
}
if (msg.deleteSurface) {
setSurfaces((prev) => {
const updated = new Map(prev);
updated.delete(msg.deleteSurface!.surfaceId);
return updated;
});
}
}
}, [messages]);
// Render active surface
// ...
}
Progressive Renderingβ
Supports incremental updates as agents generate UI:
// Agent sends initial surface
{
surfaceUpdate: {
surfaceId: "main",
components: [{ id: "title", component: { Text: { ... } } }]
}
}
// Agent adds more components (progressive rendering)
{
surfaceUpdate: {
surfaceId: "main",
components: [{ id: "table", component: { Table: { ... } } }]
}
}
// Agent updates existing component
{
surfaceUpdate: {
surfaceId: "main",
components: [{ id: "table", component: { Table: { ...updated } } }]
}
}
Current Statusβ
Why Dormant?β
The A2UI renderer is fully implemented but not currently active in the rendering pipeline for several reasons:
-
A2A Protocol Artifacts: CAIPE agents currently use A2A protocol artifacts (like
streaming_result,tool_notification_start) which provide sufficient UI feedback for most use cases. -
Text-based Responses: Most agent responses are text/markdown based, which render well with the existing markdown renderer.
-
Widget System: CAIPE has its own widget system for execution plans (TODO lists) and tool notifications that work well for the current use cases.
-
Agent Implementation: Agents would need to be updated to generate A2UI messages instead of plain text artifacts.
Test Coverageβ
Current test coverage: 0% (dormant code)
The implementation exists and was validated during development but is not covered by automated tests since it's not in the active code path.
Ready for Activationβ
The A2UI renderer can be activated when:
- Agents generate A2UI messages: Update agent prompts to emit A2UI components instead of plain text.
- Integration in message flow: Add A2UI message detection in the main rendering pipeline.
- Use case demand: Specific use cases that benefit from rich declarative UI (forms, tables, etc.).
Activating A2UIβ
Step 1: Update Agent Promptsβ
Modify agent system prompts to include A2UI generation instructions:
AGENT_SYSTEM_PROMPT = """
You are an AI assistant that helps with platform engineering tasks.
When presenting structured data, you can generate A2UI components:
**Tables**: Use Table component for tabular data
{
"artifact": {
"name": "a2ui_update",
"a2ui": {
"surfaceUpdate": {
"surfaceId": "main",
"components": [{
"id": "data-table",
"component": {
"Table": {
"title": "Results",
"headers": ["Column1", "Column2"],
"rows": [["value1", "value2"]]
}
}
}]
}
}
}
}
**Forms**: Use Form component for user input
**Lists**: Use List component for task lists
...
"""
Step 2: Integrate in Rendering Pipelineβ
Update A2AStreamPanel.tsx or ChatPanel.tsx to detect A2UI messages:
import { A2UIRenderer } from './A2UIRenderer';
// In message rendering
function renderArtifact(artifact: Artifact) {
// Check for A2UI artifact
if (artifact.name === 'a2ui_update' && artifact.a2ui) {
return (
<A2UIRenderer
messages={[artifact.a2ui]}
onAction={(action) => {
// Send action back to agent
sendMessage({
action: action.name,
context: action.context
});
}}
/>
);
}
// Existing rendering logic
// ...
}
Step 3: Test with Sample Use Caseβ
Create a test agent that generates A2UI components:
# Example: ArgoCD agent returns table instead of text
def list_applications(self):
apps = self.get_applications()
# Generate A2UI table
a2ui_message = {
"surfaceUpdate": {
"surfaceId": "argocd-apps",
"components": [{
"id": "apps-table",
"component": {
"Table": {
"title": "ArgoCD Applications",
"headers": ["Name", "Status", "Sync", "Health"],
"rows": [[app.name, app.status, app.sync, app.health]
for app in apps]
}
}
}]
}
}
return {
"artifact": {
"name": "a2ui_update",
"a2ui": a2ui_message
}
}
Integration with A2A Protocolβ
A2UI messages can be embedded in A2A artifacts:
{
"jsonrpc": "2.0",
"method": "task/artifact-update",
"params": {
"task_id": "task_123",
"artifact": {
"name": "a2ui_surface",
"description": "Interactive deployment form",
"a2ui": {
"surfaceUpdate": {
"surfaceId": "deploy-surface",
"components": [
{
"id": "deploy-form",
"component": {
"Form": { ... }
}
}
]
}
}
}
}
}
Comparison with A2A Artifactsβ
| Feature | A2A Artifacts | A2UI Components |
|---|---|---|
| Format | Text/markdown | Declarative UI JSON |
| Use Case | Progress updates, logs | Interactive forms, tables |
| Streaming | Append text chunks | Update components |
| Interactivity | Read-only | User actions (buttons, forms) |
| Styling | Markdown rendering | Native UI components |
| Complexity | Simple | Rich/structured |
When to use each:
- A2A Artifacts: Progress updates, tool notifications, streaming text responses
- A2UI Components: Data tables, input forms, interactive dashboards, multi-step wizards
Benefits of A2UIβ
Securityβ
- No Code Execution: Only declarative data, not executable JavaScript
- Sandboxed: Agents can't inject malicious UI
- Pre-approved Components: Only allowed components render
Developer Experienceβ
- Type-safe: Full TypeScript types for all components
- Consistent: Same UI patterns across all agents
- Testable: Components can be unit tested
User Experienceβ
- Rich Interactions: Forms, tables, buttons vs plain text
- Progressive: UI builds incrementally as agent generates
- Responsive: Components adapt to screen size
Relationship to Official A2UIβ
Compliance with v0.8 Specificationβ
CAIPE's A2UI implementation follows the official v0.8 specification:
β Core Concepts:
- Surfaces (UI containers)
- Components (declarative widgets)
- Data Model (reactive binding)
- Progressive rendering
β Message Types:
surfaceUpdatedataModelUpdatebeginRenderingdeleteSurface
β Component Spec:
- Standard components (Button, Text, Form, List, Table, Progress)
- Action handling
- Data binding
Custom Implementationβ
CAIPE uses a custom React renderer rather than an official A2UI library because:
- Full Control: Complete control over styling and behavior
- CAIPE Integration: Seamless integration with existing widget system
- Performance: No unnecessary abstractions
- Bundle Size: Smaller production builds
- Flexibility: Easy to extend with CAIPE-specific components
Future Alignmentβ
As the A2UI specification evolves, CAIPE's implementation will:
- Track major version updates (v0.9, v1.0)
- Adopt new component types
- Maintain backward compatibility
- Contribute feedback to the A2UI community
Resourcesβ
Official A2UIβ
- Website: https://a2ui.org/
- Specification: v0.8 Stable
- GitHub: google/A2UI
- Composer: Try A2UI Composer
CAIPE Implementationβ
- Renderer:
ui/src/components/a2a/A2UIRenderer.tsx - Widget Catalog:
ui/src/components/a2a/widgets/ - Types:
ui/src/types/a2a.ts - Constitution:
.specify/memory/constitution.md
Related Documentationβ
- UI Features - Overview of UI capabilities
- CAIPE Architecture - Architecture and A2A protocol details
- Development Guide - Contributing to the UI
FAQβ
Why isn't A2UI active by default?β
The current A2A artifact system (streaming_result, tool_notification_start, etc.) provides sufficient UI feedback for most use cases. A2UI will be activated when agents need richer interactive UIs (forms, complex tables, dashboards).
How does A2UI differ from the widget system?β
CAIPE's widget system is specific to execution plans (TODO lists) and tool notifications. A2UI is a general-purpose protocol for any UI component that agents want to render.
Can I use A2UI with other protocols?β
Yes! A2UI is transport-agnostic. While CAIPE embeds A2UI in A2A artifacts, you can use it with other message transports.
Is this compatible with CopilotKit/AG-UI?β
The implementation follows similar patterns but is custom-built for CAIPE. CopilotKit and AG-UI libraries are installed for reference but not actively used.
What's next for A2UI in CAIPE?β
Future plans include:
- Activating A2UI for specific use cases (deployment dashboards, incident forms)
- Adding more component types (charts, graphs)
- Agent prompt templates for A2UI generation
- Integration with multi-agent workflows
Status: Documentation current as of 2026-01-27 | A2UI v0.8 | CAIPE UI v0.1.0