ag-uiagentsstreamingssejson-patchai

AG-UI: Streaming Agent State to the Frontend as JSON Events

·10 min read·AI & JSON

The missing layer in the agent stack

By 2026 the agent protocol stack has a rough consensus. A2A handles agent-to-agent discovery and delegation. MCP handles agent-to-tool calls over JSON-RPC. But there was a conspicuous gap: how does a running agent talk to the user interface? Every team hand-rolled its own WebSocket messages to stream tokens, show tool calls, and update a shared state panel — and none of them agreed on a shape.

AG-UI fills that slot. Launched by CopilotKit in early 2026 and given native support in AWS Bedrock AgentCore that March, it is an open, event-based protocol for the agent-to-frontend runtime: a single stream of typed JSON events that any client can consume to render an agent live.

The layering is worth memorizing:

text
A2A     agent to agent   (discovery, delegation)
MCP     agent to tools   (JSON-RPC tool calls)
AG-UI   agent to UI      (streamed JSON events)   <- this post
A2UI    declarative UI the agent asks the client to render

One stream, many typed events

AG-UI's core idea is deliberately boring, which is why it works: an agent run is one ordered stream of JSON events, delivered by default over Server-Sent Events (see SSE with JSON) but transport-agnostic — it also runs over WebSockets or plain HTTP. Every event is a JSON object with a type discriminator, so a client is just a switch statement over event types.

The ~16 event types group into a few families:

  • Lifecycle: RUN_STARTED, RUN_FINISHED, RUN_ERROR, STEP_STARTED, STEP_FINISHED.
  • Assistant text (token streaming): TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END.
  • Tool calls: TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, TOOL_CALL_RESULT.
  • Shared state: STATE_SNAPSHOT, STATE_DELTA, MESSAGES_SNAPSHOT.
  • Escape hatches: RAW, CUSTOM.

A run, on the wire

Here is a trimmed stream for "book me a table," showing the shape a frontend actually receives:

json
{ "type": "RUN_STARTED", "threadId": "t_91", "runId": "r_1" }
{ "type": "TEXT_MESSAGE_START", "messageId": "m_1", "role": "assistant" }
{ "type": "TEXT_MESSAGE_CONTENT", "messageId": "m_1", "delta": "Let me find" }
{ "type": "TEXT_MESSAGE_CONTENT", "messageId": "m_1", "delta": " a restaurant." }
{ "type": "TEXT_MESSAGE_END", "messageId": "m_1" }
{ "type": "TOOL_CALL_START", "toolCallId": "tc_1", "toolCallName": "find_restaurant" }
{ "type": "TOOL_CALL_ARGS", "toolCallId": "tc_1", "delta": "{\"seats\":2" }
{ "type": "TOOL_CALL_ARGS", "toolCallId": "tc_1", "delta": ",\"area\":\"soho\"}" }
{ "type": "TOOL_CALL_END", "toolCallId": "tc_1" }
{ "type": "RUN_FINISHED", "threadId": "t_91", "runId": "r_1" }

Notice TOOL_CALL_ARGS arrives as string fragments. The tool arguments are themselves JSON, streamed a piece at a time — so the client is reassembling partial JSON as it arrives, exactly the pattern AG-UI standardizes rather than reinvents per app.

Shared state, synced with JSON Patch

The feature that sets AG-UI apart from "just stream tokens" is bidirectional shared state. The agent and the UI both see one state object — a form being filled, a plan being built, a document being edited. AG-UI keeps them in sync with two events:

  • STATE_SNAPSHOT — the full state object, sent once at the start (or to resync).
  • STATE_DELTA — an incremental update expressed as a [JSON Patch](/blog/json-patch-merge-patch) (RFC 6902) array of operations.
json
{ "type": "STATE_SNAPSHOT",
  "snapshot": { "booking": { "seats": 2, "area": null, "confirmed": false } } }

{ "type": "STATE_DELTA",
  "delta": [
    { "op": "replace", "path": "/booking/area", "value": "soho" },
    { "op": "replace", "path": "/booking/confirmed", "value": true }
  ] }

Sending a two-operation patch instead of the whole object keeps updates tiny and mergeable — the same reason APIs use JSON Patch for partial updates. You can build and test these patches with the JSON Patch generator.

Building and consuming AG-UI

On the server, your agent (LangGraph, CrewAI, a raw loop, or a hosted runtime) emits AG-UI events as it works — official SDKs exist for Python and TypeScript, and adapters wrap common frameworks so you rarely emit events by hand. The endpoint is an ordinary SSE handler that writes one JSON event per line.

On the client, you subscribe to the stream and reduce events into UI state:

ts
for await (const event of agentStream) {
  switch (event.type) {
    case "TEXT_MESSAGE_CONTENT": appendToBubble(event.messageId, event.delta); break;
    case "TOOL_CALL_START":      showToolPending(event.toolCallName);            break;
    case "STATE_SNAPSHOT":       setState(event.snapshot);                       break;
    case "STATE_DELTA":          setState(applyJsonPatch(getState(), event.delta)); break;
    case "RUN_ERROR":            showError(event.message);                       break;
  }
}

Because the contract is just typed JSON, the frontend is fully decoupled from the agent framework — swap LangGraph for CrewAI behind the same event stream and the UI doesn't change. That decoupling, more than any single feature, is why adoption moved fast.

Debugging an AG-UI stream

Agent-to-UI bugs are almost always malformed or mis-ordered events. A practical loop:

  1. Capture the raw SSE and format each event — a run is unreadable as one long line.
  2. When the UI state drifts from the agent's, diff your reconstructed state against a STATE_SNAPSHOT to find the delta you applied wrong.
  3. Lock the event shape with a JSON Schema so a producer that emits a malformed event is caught in tests, not in the browser.

For neighboring pieces see A2A protocol, MCP explained, Agent traces, tools & state, and WebSockets & JSON in real time.

Frequently asked questions

AG-UI is an open, event-based protocol from CopilotKit, released in early 2026, that standardizes how an agent backend streams its activity to a frontend. An agent run is a single ordered stream of typed JSON events — text, tool calls, and state updates — delivered by default over Server-Sent Events.

They cover different layers. A2A connects agents to other agents; MCP connects an agent to tools over JSON-RPC; AG-UI connects a running agent to the user interface. A full stack often uses all three together.

It sends a full STATE_SNAPSHOT once, then streams incremental STATE_DELTA events expressed as JSON Patch (RFC 6902) operations. The client applies each patch to its local copy, so only the changed fields travel over the wire.

Server-Sent Events by default, but the protocol is transport-agnostic — it also runs over WebSockets or plain HTTP. What matters is the ordered stream of typed JSON events, not the pipe carrying them.

Usually not. Official Python and TypeScript SDKs plus adapters for common agent frameworks emit the events for you, so you work with your agent framework and let the adapter produce the stream.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.