Agent Skills: AG-UI — Agent User Interaction Protocol

This skill should be used when integrating AG-UI — the full event taxonomy (lifecycle, text message, tool call, state, activity, reasoning, custom), RunAgentInput, SSE and binary transports, AbstractAgent/HttpAgent, STATE_DELTA JSON Patch, generative UI, human-in-the-loop interrupts, and CopilotKit frontends.

UncategorizedID: lobbi-docs/claude/ag-ui-protocol

Install this agent skill to your local

pnpm dlx add-skill https://github.com/TheLobbi/claude/tree/HEAD/plugins/mcp-apps-studio/skills/ag-ui-protocol

Skill Files

Browse the full folder contents for ag-ui-protocol.

Download Skill

Loading file tree…

plugins/mcp-apps-studio/skills/ag-ui-protocol/SKILL.md

Skill Metadata

Name
ag-ui-protocol
Description
This skill should be used when integrating AG-UI — the full event taxonomy (lifecycle, text message, tool call, state, activity, reasoning, custom), RunAgentInput, SSE and binary transports, AbstractAgent/HttpAgent, STATE_DELTA JSON Patch, generative UI, human-in-the-loop interrupts, and CopilotKit frontends.

AG-UI — Agent User Interaction Protocol

AG-UI standardizes the event stream between a long-running agent backend and a user-facing frontend. It is not a widget format. Where MCP Apps answers "how does a tool render a card in a chat host", AG-UI answers "how does my app show what the agent is doing, share state with it, and let the user interrupt".

Built on plain HTTP and WebSockets. Deliberately transport-agnostic and deliberately tolerant of event-shape drift, so existing frameworks can adapt their native formats with minimal work.

Use AG-UI when you own the frontend. Use MCP Apps when someone else's chat host is the frontend. They compose: an MCP Apps View can drive an AG-UI stream inside itself for a long-running sub-task.

Core interface

run(input: RunAgentInput) -> Observable<BaseEvent>

RunAgentInput carries thread and run identifiers, message history (role/content), available tools (name, description, parameters), context, and forwarded properties.

BaseEvent — every event has:

{ type: string, timestamp?: number, rawEvent?: unknown }

rawEvent preserves the original framework event when AG-UI is a translation layer. Keep it; it is what makes debugging a wrapped agent tractable.

Event taxonomy

Lifecycle

| Event | Fields | |---|---| | RunStarted | threadId, runId, parentRunId?, input? | | RunFinished | outcome? (discriminated union), result? | | RunError | message, code? | | StepStarted | stepName | | StepFinished | stepName |

parentRunId is how sub-agents nest. A frontend that ignores it will flatten a delegation tree into noise.

Text messages

| Event | Fields | |---|---| | TextMessageStart | messageId, role | | TextMessageContent | messageId, delta | | TextMessageEnd | messageId | | TextMessageChunk | messageId?, role?, delta? |

*Chunk is the compact form: emit it alone and the middleware synthesizes start/content/end. Use the explicit triple when you need precise boundaries.

Tool calls

| Event | Fields | |---|---| | ToolCallStart | toolCallId, toolCallName, parentMessageId? | | ToolCallArgs | toolCallId, delta | | ToolCallEnd | toolCallId | | ToolCallResult | messageId, toolCallId, content, role? | | ToolCallChunk | toolCallId?, toolCallName?, parentMessageId?, delta? |

ToolCallArgs streams partial JSON. Render optimistically but treat it as incomplete until ToolCallEnd.

State

| Event | Fields | |---|---| | StateSnapshot | snapshot — the complete state | | StateDelta | delta — RFC 6902 JSON Patch operations | | MessagesSnapshot | messages — full conversation history |

Snapshot on connect and after any desync; delta for everything in between. A frontend that only handles snapshots works but wastes bandwidth; one that only handles deltas breaks on reconnect. Handle both.

Activity

| Event | Fields | |---|---| | ActivitySnapshot | messageId, activityType, content, replace? | | ActivityDelta | messageId, activityType, patch (RFC 6902) |

Activity is the "what is the agent doing right now" channel — progress bars, sub-task lists, live status.

Reasoning

| Event | Fields | |---|---| | ReasoningStart | messageId | | ReasoningMessageStart | messageId, role | | ReasoningMessageContent | messageId, delta | | ReasoningMessageEnd | messageId | | ReasoningMessageChunk | messageId, delta? | | ReasoningEnd | messageId | | ReasoningEncryptedValue | subtype, entityId, encryptedValue |

ReasoningEncryptedValue carries provider-encrypted reasoning you must pass back verbatim on the next turn. Never drop it, never try to parse it.

Special

| Event | Fields | |---|---| | Raw | event, source? | | Custom | name, value | | MetaEvent (draft) | metaType, payload |

Custom is the open extension point — use it for domain events your frontend knows about and the protocol does not.

Server implementation

Python — ag_ui.core (event types), ag_ui.encoder (SSE):

from ag_ui.core import RunStartedEvent, TextMessageChunkEvent, RunFinishedEvent
from ag_ui.encoder import EventEncoder

async def stream(input: RunAgentInput, accept: str):
    encoder = EventEncoder(accept=accept)
    yield encoder.encode(RunStartedEvent(thread_id=input.thread_id, run_id=input.run_id))
    async for token in agent(input):
        yield encoder.encode(TextMessageChunkEvent(message_id=mid, delta=token))
    yield encoder.encode(RunFinishedEvent(thread_id=input.thread_id, run_id=input.run_id))

Pass the request's Accept header into EventEncoder — that is how it chooses between text SSE and the binary protocol.

TypeScript — @ag-ui/core (types), @ag-ui/client (HttpAgent, AbstractAgent):

import { AbstractAgent } from "@ag-ui/client";

class MyAgent extends AbstractAgent {
  run(input: RunAgentInput): Observable<BaseEvent> { /* emit events */ }
}

HttpAgent connects to any endpoint that accepts a POST and streams events back. Extend AbstractAgent when you are wrapping a framework.

Ordering contract, always: RunStarted → content events → RunFinished or RunError. A stream that ends without a terminal event leaves the frontend spinning forever.

Capabilities the protocol gives you

  • Live token/event streaming with cancel and resume.
  • Streaming tool output for long-running effects.
  • Shared state, typed, read-only or read-write, event-sourced with conflict resolution.
  • Generative UI — static (typed components the frontend already has) and declarative.
  • Thinking-step visualization from traces and tool events.
  • Frontend tool calls — typed handoff where the frontend executes the tool.
  • Backend tool rendering as first-class events.
  • Human-in-the-loop interrupts — pause, approve, edit, retry without losing state.
  • Agent steering — redirect a running agent with live user guidance.
  • Multimodal: files, images, audio, transcripts, voice, annotations.
  • Sub-agents with nested delegation and scoped state.

Frontend

CopilotKit speaks AG-UI natively — point its React components at your endpoint and you get a working chat UI without building one. Custom frontends subscribe to the observable and reduce events into their own store.

SDKs exist for Kotlin, Go, Dart, Java, and Rust in addition to TypeScript and Python. Framework integrations include LangGraph, CrewAI, Microsoft Agent Framework, Google ADK, AWS, Pydantic AI, and LlamaIndex.

Why not just REST

REST and GraphQL assume a bounded request with a deterministic response. Agents are long-running, nondeterministic, and compose. AG-UI makes state updates and side effects first-class protocol elements instead of things you bolt on with polling.

Related

  • protocol-selection — when AG-UI is the answer and when it is not.
  • a2ui-protocol — declarative UI payloads that can ride an AG-UI stream.
  • ui-state-architecture — reconciling AG-UI shared state with UI state.