Leveraging AG-UI with A2UI Payloads in CopilotKit: A 2026 Guide to Production-Grade Agentic UI
Learn how to combine the AG-UI pipeline streaming protocol with declarative A2UI JSON payloads using CopilotKit to build secure, multi-step interactive workflows.
In the fast-moving field of Generative UI, building interfaces that feel interactive, secure, and instant remains a primary engineering challenge. By mid-2026, the architecture of advanced AI applications has stabilized around a clean separation of concerns:
- The Transport Layer (The Pipe): Managing the real-time, streaming connection between the agent and the client.
- The Presentation Layer (The Water): The standardized format describing the visual UI itself.
This article provides a complete hands-on guide on how to integrate AG-UI (the standard transport pipeline protocol) with A2UI (the declarative presentation spec) inside the popular CopilotKit orchestration framework.

The Concept: AG-UI vs. A2UI
To understand this architecture, use the analogy of plumbing: AG-UI is the water pipe, and A2UI is the water flowing through it.
- AG-UI (Agentic Graphical User Interface Protocol): Manages lifecycle events. It defines when a canvas should open, how to route state changes, and how to handle step-by-step human-in-the-loop approvals.
- A2UI (Agent-to-User Interface Specification): A pure JSON schema defining what the interface visually looks like and what data models bind to it.
By splitting these responsibilities, developers avoid coupling their UI framework to their LLM orchestrator. The same A2UI schema can render as native React components on the web or native Jetpack Compose/SwiftUI views on mobile, while AG-UI handles the stream routing across WebSocket or SSE channels.
System Architecture
In a standard CopilotKit setup, the LLM determines when it needs to present a custom form or component to the user. Rather than streaming raw JSX or HTML (which is slow, hard to parse, and presents high security risks), the agent streams AG-UI instructions containing A2UI payloads.
┌─────────────┐ ┌────────────┐ ┌──────────────┐
│ AI Agent │ ──(SSE)──> │ CopilotKit │ ──(JSON)─> │ A2UI Engine │
│ (Backend) │ [AG-UI] │ Runtime │ [Payload] │ (Native UI) │
└─────────────┘ └────────────┘ └──────────────┘
- Agent Backend: Calls tool, streams
surfaceUpdateinstruction. - CopilotKit: Intercepts the tool call, handles transport, and acts as the state synchronization manager.
- A2UI Client-side Renderer: Takes the clean JSON payload, references the trusted local registry, and mounts the component.
Hands-On Integration
Let’s build a multi-step booking widget where an AI agent helps a user select a flight, presents an A2UI approval card, and executes the transaction after a user confirmation click.
Step 1: Defining the Trusted A2UI Component (Client-side)
First, register the native component inside your client-side React code. This component is pre-compiled, keeping it secure and safe from XSS injections.
// components/FlightApprovalCard.tsx
import React from 'react';
interface FlightApprovalProps {
flightNumber: string;
price: number;
onApprove: () => void;
}
export const FlightApprovalCard: React.FC<FlightApprovalProps> = ({
flightNumber,
price,
onApprove,
}) => {
return (
<div className="p-4 border rounded-xl bg-slate-900 text-white shadow-lg">
<h3 className="text-lg font-bold">Approve Flight booking</h3>
<p className="text-sm text-slate-400">Flight: {flightNumber}</p>
<div className="flex justify-between items-center mt-4">
<span className="text-xl font-bold">${price}</span>
<button
onClick={onApprove}
className="px-4 py-2 bg-indigo-600 rounded-lg hover:bg-indigo-500 transition-colors"
>
Confirm Book
</button>
</div>
</div>
);
};
Register this card inside the A2UI Registry:
// registry/a2ui-registry.ts
import { FlightApprovalCard } from '../components/FlightApprovalCard';
export const a2uiRegistry = {
'FlightApprovalCard': FlightApprovalCard,
// Other components...
};
Step 2: Streaming AG-UI Instructions with A2UI Payload (Backend)
On the backend, write your CopilotKit agent action. We use useCopilotAction to listen for the flight search request, then stream an AG-UI surfaceUpdate command containing the A2UI payload.
// api/copilot/route.ts
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend";
const copilotRuntime = new CopilotRuntime();
export const POST = async (req: Request) => {
return copilotRuntime.handleRequest(req, new OpenAIAdapter({
actions: [
{
name: "bookFlight",
description: "Initiates the flight booking flow.",
parameters: [
{ name: "flightNumber", type: "string" },
{ name: "price", type: "number" }
],
handler: async ({ flightNumber, price }, { write }) => {
// Stream AG-UI Surface Update containing A2UI Payload
write(JSON.stringify({
instruction: "surfaceUpdate",
surfaceId: "booking-flow",
components: [
{
id: "card-1",
component: {
type: "FlightApprovalCard",
props: {
flightNumber,
price
}
}
}
]
}));
// Signal the start of rendering
write(JSON.stringify({
instruction: "beginRendering",
surfaceId: "booking-flow"
}));
return "Awaiting user approval...";
}
}
]
}));
};
Step 3: Handling Streamed Interaction (Client-side)
Inside your React page, use CopilotKit to render the active surface dynamically. The A2UI renderer intercepts incoming JSON structures and mounts the component registered in a2uiRegistry.
// app/page.tsx
'use client';
import { useCopilotContext } from "@copilotkit/react-core";
import { useA2UISurface } from "@a2ui/react";
import { a2uiRegistry } from "../registry/a2ui-registry";
export default function App() {
const { lastAction } = useCopilotContext();
// Use the A2UI hook to read the streamed surface state from AG-UI pipe
const { renderSurface } = useA2UISurface({
registry: a2uiRegistry,
surfaceId: "booking-flow"
});
return (
<main className="max-w-xl mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">Travel Assistant</h1>
{/* Streamed generative interface rendered here */}
<div className="my-6">
{renderSurface()}
</div>
<div className="bg-slate-100 p-4 rounded-lg">
<p className="text-slate-600">
Ask: "Help me book flight AA-342" to trigger the flow.
</p>
</div>
</main>
);
}
Why This Wins in 2026
By decoupling transport (CopilotKit & AG-UI) from description (A2UI JSON), your stack achieves:
- Multi-Platform Ready: The agent code remains completely unchanged if you decide to deploy a Flutter mobile app. You only write a Flutter A2UI renderer that consumes the same
FlightApprovalCardpayload. - Ultra-low Latency: Instead of waiting for heavy HTML or React code to compile on-the-fly, A2UI streams minimal data (under 1KB) which is mounted instantly on the client.
- Strict Security Boundaries: The client strictly permits rendering predefined classes of components. A malicious prompt injection targeting the LLM cannot inject harmful
<script>tags, preventing XSS and phishing.
To understand more about the security implications, check out our Component Registry Firewall Deep Dive or review our A2UI vs. Vercel AI SDK comparison.