AG-UI explained: what it is and how it differs from MCP
AG-UI is an open event protocol for streaming an AI agent to a frontend. What it is, how a run looks on the wire, how it differs from MCP, and when to skip it.
- Author
- Kamran Khan
- Published
- Reading time
- 20 min read
On this page
AG-UI is a documented list of typed events that an AI agent backend streams to a frontend: run started, text arriving, a tool being called, state changing, run finished. It is not an alternative to MCP. MCP connects an agent to tools and data, AG-UI carries the agent's output to the screen, and the project's README calls the two complementary. You can build the same stream yourself, and the Vercel AI SDK has a format of its own. What the protocol saves you is inventing and maintaining the vocabulary.
I read the primary sources on 15 October 2026. Most are the project's own pages, so a flattering sentence is the project's claim, and where something is my judgement I say "in my view". The client example below ran against a small local server, printed in full so you can repeat it.
Transcript
One scripted reply from my mock server reached the screen as fourteen separate events. What were they? One JSON reply can't show text arriving, a tool running, or state changing live. A stream can. A G U I is an open protocol that names those events. Run started. Text message content, in small pieces. Tool call start, state delta, and run finished. The request is a plain POST. The answer usually streams back as server-sent events, each a line of JSON with its name in a type field. A G U I's own docs list three layers: M C P for tools, A to A for other agents, and A G U I for you. You can build this yourself, and the Vercel AI SDK has its own stream format. I'd say a protocol saves you inventing the names. It started at CopilotKit, a vendor, and I found no foundation behind it. So that reply wasn't one message. It was fourteen named events.
Watch the Short on YouTube: https://youtube.com/shorts/RgGxh-0st1E.
Watch the 4-minute explainer on YouTube: https://youtu.be/5eokYVtLG_g.
What AG-UI is
AG-UI's docs call it an open, lightweight, event-based protocol for connecting AI agents to user-facing applications. An agent does more than produce one answer. In a few seconds it may write text, decide to call a tool, wait for the result, change some state the page displays and write more text, and a single JSON reply shows none of that until everything has finished. Request and response is fine, and AG-UI uses it: the client sends one POST and the answer comes back as a stream. Teams that want to show progress end up streaming typed messages anyway, each with its own names (the loop behind this is covered in how AI agents actually work). AG-UI is a naming and ordering agreement for that stream.
It is MIT licensed, and the code lives in the ag-ui-protocol GitHub organisation. The project grew out of CopilotKit's work with LangChain and CrewAI, and CopilotKit, a vendor, still hosts the docs. I found no foundation behind it, so "open" means an open specification and licence, not a standards body.
You do not need CopilotKit to use it. The docs list CopilotKit as the first-party client, but the client library on npm is @ag-ui/client, and the example further down uses nothing else.
Packages came first: @ag-ui/client on npm and the first PyPI upload both date from 30 April 2025, and CopilotKit's launch post followed on 12 May 2025. Version 1.0.0 of the packages went out on 17 September 2026, CopilotKit announced 1.0 on 30 September, and the latest @ag-ui/client is 1.0.2. The protocol is over a year old, while parts of the 1.0 event set are newer: reasoning, subagent and meta events.
CopilotKit's announcement says 1.0 comes with JSON Schema for the event fields and a separate spec for event order and run start, end and error. It promises that the 1.0 spec will not change and that 0.x agents and 1.0 clients keep working together. That is the maintainers' promise, about two weeks old on the day I read it, and I have not tested it.
On GitHub the repository shows 16.6k stars and 4,735 commits on main, and the npm registry counted 1,519,177 downloads of @ag-ui/client for the week of 6 to 12 October. Downloads include CI runs and mirrors, so they say little about how many products run AG-UI in production.
On support, the AG-UI project lists Microsoft Agent Framework, Google ADK, AWS Strands Agents, Mastra, Pydantic AI, Agno, LlamaIndex and AG2 as first-party integrations, LangChain and CrewAI as partnerships, and the Claude Agent SDK and Cloudflare Agents as community ones. The OpenAI Agent SDK is listed as in progress. "First-party" is the project's label, and I did not check Microsoft's or Google's own docs for the same claim, so the list is what AG-UI says it supports.
What AG-UI is not
It does not replace MCP or A2A. The AG-UI docs show three layers:
| Connection | Protocol |
|---|---|
| Agent to tools and data | MCP |
| Agent to agent | A2A |
| Agent to user | AG-UI |
That picture is AG-UI's. MCP's own page describes a standard for connecting AI applications to external systems, and the A2A documentation frames MCP as agents using capabilities and A2A as agents partnering on tasks, without mentioning AG-UI or user interfaces. For what MCP is for, see AGENTS.md, Skills or MCP.
AG-UI is also not A2UI, a JSON format created by Google that describes the widgets an agent wants shown; its README called it an early stage public preview on 15 October 2026. A2UI's transports page calls it transport-agnostic and lists AG-UI and A2A as stable transports, and the AG-UI introduction says the two work well together. Nor is AG-UI an agent framework: running the model, choosing tools and drawing the interface stay with your framework and your frontend.
What the stream looks like on the wire
To see the shape, here is a mock server that plays one scripted run: some text, one tool call and a state update. It is about 30 lines of Node with no dependencies. Save it as server.mjs and start it with node server.mjs.
import http from "node:http";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
http.createServer(async (req, res) => {
if (req.method !== "POST" || req.url !== "/agent") return res.writeHead(404).end();
let body = "";
for await (const chunk of req) body += chunk;
const { threadId, runId } = JSON.parse(body);
res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" });
const send = async (type, fields) => {
res.write(`data: ${JSON.stringify({ type, ...fields })}\n\n`);
await sleep(20);
};
await send("RUN_STARTED", { threadId, runId });
await send("TEXT_MESSAGE_START", { messageId: "msg-1", role: "assistant" });
for (const delta of ["Checking ", "the weather ", "in Reading."])
await send("TEXT_MESSAGE_CONTENT", { messageId: "msg-1", delta });
await send("TEXT_MESSAGE_END", { messageId: "msg-1" });
await send("TOOL_CALL_START", { toolCallId: "tc-1", toolCallName: "get_weather",
parentMessageId: "msg-1" });
await send("TOOL_CALL_ARGS", { toolCallId: "tc-1", delta: '{"city":' });
await send("TOOL_CALL_ARGS", { toolCallId: "tc-1", delta: '"Reading"}' });
await send("TOOL_CALL_END", { toolCallId: "tc-1" });
await send("TOOL_CALL_RESULT", { messageId: "msg-2", toolCallId: "tc-1",
content: '{"tempC":14}', role: "tool" });
await send("STATE_SNAPSHOT", { snapshot: { city: "Reading", tempC: null } });
await send("STATE_DELTA", { delta: [{ op: "replace", path: "/tempC", value: 14 }] });
await send("RUN_FINISHED", { threadId, runId });
res.end();
}).listen(8787);Then ask it for a run with curl:
curl -sN -X POST http://localhost:8787/agent \
-H "Content-Type: application/json" -H "Accept: text/event-stream" \
-d '{"threadId":"t-1","runId":"r-1","messages":[]}'It answers with 14 frames, nine of them shown here, with ... where frames are left out:
data: {"type":"RUN_STARTED","threadId":"t-1","runId":"r-1"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg-1","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg-1","delta":"Checking "}
...
data: {"type":"TOOL_CALL_START","toolCallId":"tc-1","toolCallName":"get_weather","parentMessageId":"msg-1"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"tc-1","delta":"{\"city\":"}
...
data: {"type":"TOOL_CALL_RESULT","messageId":"msg-2","toolCallId":"tc-1","content":"{\"tempC\":14}","role":"tool"}
data: {"type":"STATE_SNAPSHOT","snapshot":{"city":"Reading","tempC":null}}
data: {"type":"STATE_DELTA","delta":[{"op":"replace","path":"/tempC","value":14}]}
data: {"type":"RUN_FINISHED","threadId":"t-1","runId":"r-1"}Each frame is data: plus the JSON, and a blank line ends the message, as MDN describes for server-sent events. There is no event: line; the event name is the type field inside the JSON. Text arrives as a start, content deltas and an end. The tool call has the same shape, with its arguments split across two fragments and the result as its own event. The snapshot sets the state, the delta patches it, and RUN_FINISHED closes the run. It is a mock, so it shows the shape of the frames and nothing about how a real agent behaves.
The frames above came from curl. Chrome can show the same stream in its Network panel: since Chrome 123 the EventStream tab lists each message of a fetch() request whose response is text/event-stream, where before it only listed EventSource messages (Chrome 123 release notes). Each row shows the message's data as it arrives, and the tab has a Clear button and a filter that accepts a regular expression. DevTools has to be open, with the Network panel recording, when the request fires. I opened it first and did not test opening it afterwards.
I recorded this in Chrome 155.0.8059.40, driven over the DevTools Protocol by Puppeteer in headless mode. A page on one local port made the fetch() POST to the mock on another, so the request was cross-origin and Chrome sent a preflight first. That leaves two agent rows in the Network list, the preflight and the POST; open the second. The EventStream tab filled with all 14 messages, 350 ms apart, and their data matched the curl output above byte for byte. The ID column was empty and the Type column read message on every row, since the mock sends no event: line, so the AG-UI event name is only in the Data column. A run through HttpAgent from the same page filled the tab the same way.
The server for the Chrome run is the one above with four changes, none of them to the events. It sends CORS headers and answers the browser's OPTIONS preflight, because the page was on another origin. It waits 350 ms between frames instead of 20, so the rows arrive slowly enough to watch. It has a GET /events route for an EventSource comparison. And it frames the events with @ag-ui/encoder instead of by hand. The first two are all the server printed above needs before a page on another origin can call it, with the OPTIONS line before the 404 check:
// next to the imports
const CORS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Accept",
};
// first line inside the request handler, before the 404 check
if (req.method === "OPTIONS") return res.writeHead(204, CORS).end();
// add CORS to the 200 response
res.writeHead(200, { ...CORS, "Content-Type": "text/event-stream", "Cache-Control": "no-cache" });
// inside send(), slow the frames down
await sleep(350); // was sleep(20)Now the client. Install it with npm install @ag-ui/client, put this in a .mjs file and run it while the server is up. I used Node 24 and @ag-ui/client 1.0.2.
import { HttpAgent } from "@ag-ui/client";
const agent = new HttpAgent({ url: "http://localhost:8787/agent" });
agent.messages = [{ id: "u1", role: "user", content: "Weather?" }];
agent.subscribe({
onTextMessageContentEvent: ({ event, textMessageBuffer }) =>
console.log(textMessageBuffer + event.delta),
onRunFinishedEvent: ({ result }) => console.log("done", result),
});
await agent.runAgent({ tools: [] });It prints:
Checking
Checking the weather
Checking the weather in Reading.
done undefinedOn textMessageBuffer: the subscriber docs do not say whether it includes the current delta. In my run it holds the text before it, so the handler adds event.delta; without that the first line is empty and the last one misses "in Reading.". The final done undefined means the server sent no result: RUN_FINISHED in the mock has no result field, and when I added one the same line printed that value. Setting agent.messages before the run is how the user message gets sent, and the client generates threadId and runId itself.
How it works
Events, tool calls, state and transport are the parts to know if you build against it.
Events: 31 types in eight families
Ask the docs how many events there are and you get three answers. The architecture page still says 16, a CopilotKit post from November 2025 says 17, and the 1.0 spec page lists 31 values in EventType. The events page shows more names again, because it still lists five deprecated THINKING_* events and a draft meta event. I use the spec page's 31.
| Family (count) | Events |
|---|---|
| Runs and steps (5) | RUN_STARTED, RUN_FINISHED, RUN_ERROR, STEP_STARTED, STEP_FINISHED |
| Text (4) | TEXT_MESSAGE_START, _CONTENT, _END, _CHUNK |
| Tool calls (5) | TOOL_CALL_START, _ARGS, _END, _CHUNK, _RESULT |
| Reasoning (7) | REASONING_START, _END, _MESSAGE_START, _MESSAGE_CONTENT, _MESSAGE_END, _MESSAGE_CHUNK, REASONING_ENCRYPTED_VALUE |
| State (3) | STATE_SNAPSHOT, STATE_DELTA, MESSAGES_SNAPSHOT |
| Activity (2) | ACTIVITY_SNAPSHOT, ACTIVITY_DELTA |
| Subagents (3) | SUBAGENT_STARTED, _FINISHED, _ERROR |
| Passthrough (2) | RAW, CUSTOM |
Every implementation must support the event model, but the spec treats everything beyond the run lifecycle as optional for a producer to emit. A run normally starts with RUN_STARTED and ends with RUN_FINISHED or RUN_ERROR, though the Runs and Steps page allows a little more: a stream may open with RUN_ERROR, a late RUN_ERROR may follow RUN_FINISHED (the consumer then treats the run as failed), and after a RUN_ERROR only a new RUN_STARTED may follow. On unknown events, the spec's Processing Model page says a consumer must drop an event of an unrecognised type without aborting the run, and should emit a warning naming the type. The CopilotKit 1.0 post describes this as skipping unknown events with a warning. TEXT_MESSAGE_CHUNK is a convenience that expands to start, content and end. In my view that makes 31 less daunting than it sounds, since a basic chat can get by with run start and finish plus the three text events.
Field names below come from the Zod schemas in @ag-ui/core 1.0.2, not the docs' schema page, and they match what the capture shows. Every event also has a required type and optional timestamp, rawEvent and metadata.
| Event | Required fields |
|---|---|
RUN_STARTED | threadId, runId |
RUN_FINISHED | threadId, runId (optional: result, outcome, usage) |
RUN_ERROR | message (optional: code, usage) |
TEXT_MESSAGE_START | messageId (optional: role) |
TEXT_MESSAGE_CONTENT | messageId, delta |
TOOL_CALL_START | toolCallId, toolCallName |
TOOL_CALL_ARGS | toolCallId, delta |
TOOL_CALL_RESULT | messageId, toolCallId, content |
STATE_SNAPSHOT | snapshot |
STATE_DELTA | delta |
RUN_ERROR carries a message, an optional code string with no fixed vocabulary, and an optional usage array of token counts, but it does not repeat threadId or runId. RUN_FINISHED has the same optional usage. The delta in TOOL_CALL_ARGS is a fragment of JSON text, not an object: join the fragments and parse once, as in {"city": plus "Reading"}. The delta in STATE_DELTA is an array of RFC 6902 operations.
Tool calls and human approval
RunAgentInput carries a tools list, and that list holds only client-provided tools; backend tools stay in the backend. When the agent calls a frontend tool, the frontend runs it and sends the result back as a role: "tool" message. The tools page uses this for human approval: define a frontend tool such as confirmAction, show a dialog when it is called, and return the answer as the tool result.
Interrupts are the second route. There the run ends with a RUN_FINISHED whose outcome is an interrupt, and the client then starts a new run on the same thread with a resume array. The resume must cover every open interrupt, and the page says partial resumes are not supported. So an interrupt is not a connection held open while a person thinks; the run is over, and a later run picks up the answer. The 1.0 spec covers this on its own Interrupts and Resume page, and its changelog lists the interrupt outcome and resume entries among the 1.0 changes. Denials go inside the payload, not in a separate status. This example is from the Concepts interrupts page, read on 15 October 2026:
{"type":"RUN_FINISHED","threadId":"thread-1","runId":"run-1",
"outcome":{"type":"interrupt","interrupts":[{"id":"int-abc123","reason":"tool_call",
"message":"Send email to a@b.com with subject 'Hi'?","toolCallId":"tc-001",
"responseSchema":{"type":"object","properties":{"approved":{"type":"boolean"}},"required":["approved"]}}]}}
{"threadId":"thread-1","runId":"run-2",
"resume":[{"interruptId":"int-abc123","status":"resolved","payload":{"approved":true}}]}Shared state
STATE_SNAPSHOT carries the whole state and is the baseline or recovery point. STATE_DELTA carries JSON Patch operations, applied in order. The request carries state too, as RunAgentInput.state, so state travels both ways; the state page calls it a bidirectional channel. A delta only works if the frontend holds the correct prior state, which is what snapshots are for. The page also warns against putting sensitive data in shared state, and it gives no conflict-resolution strategy, so if two sides edit the same field you design the rule.
Transport
A request is a POST to the agent URL with Content-Type: application/json and Accept: text/event-stream. The body is one RunAgentInput. Per the @ag-ui/core 1.0.2 schema its required fields are threadId, runId and messages, tools and context default to empty lists, and optional fields include state, forwardedProps and resume. The response is a 200 with Content-Type: text/event-stream. The docs say each data payload is exactly one event and that consumers must ignore other SSE fields such as event:, id: and retry:, which the mock above respects by sending only data.
Besides SSE there is an HTTP binary protocol aimed at performance. The docs also say AG-UI does not mandate a transport and name webhooks and WebSockets as possibilities. My reading is "can", not "ships": the architecture page gives no WebSocket or webhook specifics, so if you need WebSockets, expect to write the glue.
HttpAgent takes a headers option and forwards whatever you set. If one of those headers would carry an API key, read how an AI key ends up in your frontend bundle first.
Do you need it?
In my view, probably not if you have one frontend, one backend and a chat box. Plain SSE with event names of your own covers that, and so does the Vercel AI SDK stream if your frontend already uses the AI SDK. AG-UI starts to pay when one frontend has to sit over several agent frameworks, or when you want typed tool calls, state deltas and interrupts without designing them yourself.
Thoughtworks supplies the independent view: its Technology Radar, Volume 34 (April 2026), moved AG-UI from Assess to Trial. The entry says the need for a separate UI protocol layer such as AG-UI is being questioned, since MCP-based apps can package HTML or UI widgets in MCP servers or skills, tied to adjacent standards such as MCP-UI, and it tells teams to assess AG-UI's role given the trend towards consolidating tool logic and UI in MCP. It still calls AG-UI a solid choice for decoupling the front-end experience from back-end orchestration.
Alternatives are your own SSE events or the Vercel AI SDK UI stream. The table sets what the AI SDK page documents beside what AG-UI documents. It compares features and does not rank them, and the pages I read do not compare the two. Check the current AI SDK page before you rely on a part name; I read it on 15 October 2026.
| AI SDK UI stream | AG-UI | |
|---|---|---|
| Names defined by | AI SDK docs | AG-UI spec |
| Transport | SSE | SSE or binary |
| Text | text-start, text-delta, text-end | TEXT_MESSAGE_START, _CONTENT, _END |
| Tool calls | tool-input-*, tool-output-available | TOOL_CALL_START, _ARGS, _END, _RESULT |
| Approval | tool-approval-request, tool-approval-response | frontend tool, or interrupt and resume |
| Custom data | data-*, custom | CUSTOM, RAW, ACTIVITY_* |
| Shared state | no state part | STATE_SNAPSHOT, STATE_DELTA |
| Run ids | message start and finish | RUN_STARTED with threadId, runId |
With plain SSE you define every row yourself. The AI SDK stream is identified by the x-vercel-ai-ui-message-stream: v1 header, and its data-* parts carry custom structured data. Pydantic AI's docs show both formats through separate adapters, AGUIAdapter and VercelAIAdapter. My reading is that one agent can sit behind either, though the page does not compare them. Both put JSON events on an SSE stream, so I would expect a backend in any language to produce either.
Both protocols are still moving: MCP dropped sessions and the handshake in its 2026-07-28 spec (what changed), and AG-UI's 1.0 promise was about two weeks old when I read it.
If you want to try it without touching your product:
- Log your current agent stream for a day and count the event kinds you invented. If it is three, keep your own.
- Open DevTools before your own streaming request fires, then read the EventStream tab (Chrome 123 or later).
- Point
HttpAgentat the mock above in a throwaway repo, or open the AG-UI Dojo, the project's demo apps, which the 1.0 post counts as 30 live integrations.
What I did not check
- The resume request of an interrupt. I saw the interrupt outcome reach the client in a larger mock than the one above, but I did not run the follow-up request. The Concepts interrupts page has no wording on whether the feature is stable. The 1.0 spec has its own Interrupts and Resume page and lists the interrupt outcome as a 1.0 change, so I would expect the 1.0 promise to cover it; that is my reading, and I have not tested it.
- Chrome beyond version 155. I tested the EventStream tab in Chrome 155.0.8059.40 only, with DevTools open before the request. I did not test the AG-UI binary protocol, and from reading Chrome's DevTools source I expect its responses not to fill the tab, which is keyed on
text/event-stream. - A2UI over AG-UI. I did not run it, and no AG-UI spec page I read says which events carry A2UI messages. What the post says about the pairing is what the A2UI and AG-UI docs say.
- Performance, latency and security. The AG-UI pages I read give no guidance on them, and the 1.0 post does not discuss them either.
- Independent criticism. Besides Thoughtworks, I found one secondary page, a research note by a single author, Ry Walker, with figures dated June 2026 and no publication date. It says AG-UI has no neutral foundation home and that developers flag soft lock-in to CopilotKit's commercial stack, citing Hacker News comments. I treat that as one person's opinion, not an established fact, and I did not verify the lock-in claim. I found no critics on the number of events.
Sources
All read on 15 October 2026.
- AG-UI, introduction
- AG-UI, architecture
- AG-UI, events
- AG-UI, 1.0 spec, basic
- AG-UI, 1.0 spec, HTTP and SSE transport
- AG-UI, 1.0 spec, Runs and Steps
- AG-UI, 1.0 spec, Processing Model
- AG-UI, 1.0 spec, Interrupts and Resume
- AG-UI, 1.0 spec, Key Changes (changelog)
- AG-UI, tools
- AG-UI, state
- AG-UI, interrupts
- AG-UI, types (RunAgentInput)
- AG-UI, HttpAgent
- AG-UI, subscriber
- AG-UI on GitHub
- A2UI, home page and transports
- A2UI on GitHub, whose README gives the preview status; the older google/A2UI address redirects there
- AG-UI Dojo
- CopilotKit, Introducing AG-UI: The Protocol Where Agents Meet Users, 12 May 2025
- CopilotKit, Master the 17 AG-UI Event Types, 3 November 2025
- CopilotKit, AG-UI 1.0 announcement, 30 September 2026
- npm registry, @ag-ui/client and weekly downloads
- PyPI, ag-ui-protocol
- Thoughtworks Technology Radar, AG-UI protocol, Volume 34, April 2026
- Ry Walker, AG-UI research, a secondary single-author page, figures dated June 2026
- AI SDK, UI stream protocol
- Pydantic AI, UI integrations
- Chrome DevTools, Network reference
- Chrome DevTools, What's new in DevTools (Chrome 123)
- MDN, Using server-sent events
- Model Context Protocol, introduction
- A2A, A2A and MCP
Frequently asked questions
What is AG-UI?
AG-UI is an open, event-based protocol that standardises how an AI agent backend connects to a user-facing application. The frontend sends one HTTP POST and gets back a stream of typed events such as text chunks, tool calls and state changes. It is MIT licensed and started at CopilotKit. The 1.0 spec lists 31 event types.
What is the difference between AG-UI and MCP?
They do different jobs and do not compete. MCP connects an agent to tools and data. AG-UI streams the agent's output and state to a user interface. The AG-UI docs describe three layers: MCP for agent to tools, A2A for agent to agent, and AG-UI for agent to user. That picture is AG-UI's own, not MCP's.
What is the difference between AG-UI and A2UI?
They sit at different layers. A2UI is a JSON format, created by Google, that describes the interface an agent wants shown; its README called it an early stage public preview on 15 October 2026. AG-UI is the event protocol that streams agent output to the app. A2UI's docs list AG-UI as one transport. I did not run the pairing.
What is the difference between AG-UI and the Vercel AI SDK stream protocol?
Both stream typed events from a backend to a UI over SSE. The AI SDK documents its own parts, such as text-delta and tool-input-available, and a header named x-vercel-ai-ui-message-stream. AG-UI documents its own event names, such as TEXT_MESSAGE_CONTENT, and a wider set including state deltas. Pydantic AI ships adapters for both, and the pages I read do not rank them.
Do I need AG-UI?
Probably not if you have one frontend, one backend and a chat box. Plain SSE or the AI SDK stream already covers that. AG-UI earns its place when one frontend must talk to more than one agent framework, or when you want typed tool calls, state deltas and interrupts without inventing them. That is my view, not a documented rule.
Do I need CopilotKit to use AG-UI?
No. AG-UI is a protocol with its own client library, @ag-ui/client on npm. CopilotKit started the project and is listed as its first-party UI client, but the example in this post uses only @ag-ui/client against a plain Node server.
Does AG-UI use WebSockets?
Not out of the box, as far as the docs show. They say AG-UI does not mandate a transport and mention SSE, webhooks and WebSockets as options, but they document HTTP with SSE and an HTTP binary protocol. The architecture page gives no WebSocket specifics, so expect to write the glue yourself.