Skip to content
Protocol Specification

Agentic Rendering Protocol

A transport-agnostic, bidirectional protocol for communication between AI agents and rendering surfaces. One agent, every screen.

v0.1 Draft
Open Standard
CC BY 4.0

The Problem

AI agents produce structured output — tables, forms, charts, status messages, code blocks — but the rendering of that output is tightly coupled to the transport and frontend framework. An agent built for a web chat cannot render on a desktop app. An agent behind an SSE stream cannot serve a mobile client expecting a different protocol.

Every new rendering surface requires custom integration code.

The Solution

ARP defines a protocol — not a framework, not a library. Like how Wayland decoupled applications from display servers, ARP decouples agents from renderers.

The agent decides what to display. The renderer decides how to display it.

Architecture
Agent (backend)                              Renderer (web, CLI, mobile)
───────────────                              ──────────────────────────
Owns surfaces         ── render / delta ──►  Owns display
Owns UI state         ◄── input events ────  Owns input routing

Design Principles

01
No middleman
Agent sends render commands directly. Renderer sends input directly. No intermediate framework.
02
Async & non-blocking
All messages are asynchronous. Neither side blocks waiting for a response.
03
Every frame is perfect
Atomic commits. Render state accumulates in a pending buffer and applies atomically. No flickering.
04
Capability-driven
Renderers declare what they support. Agents adapt. CLI gets tables, web gets everything.
05
Transport-agnostic
Logical messages, not wire formats. WebSocket, SSE, gRPC, Unix sockets, stdio.
06
Typed components
Agents emit typed descriptors like table with headers and rows — not HTML.

Protocol Messages

Every ARP message is JSON with at minimum { v: 1, type: "<type>" }.

Server to Client
TypePurpose
helloCapability handshake on connect
deltaIncremental text chunk
tool_startTool execution started
tool_endTool execution finished
renderGenerative UI component
patchIncremental component update
errorError event
commitStream complete
Client to Server
TypeInput TypePurpose
inputtextUser text message
inputactionButton click / UI action
inputform_submitForm submission

14 Built-in Components

Every ARP-conformant renderer must support at least text. Components declare fallback chains — chart falls back to table, which falls back to text.

text
markdown
status-card
table
code-block
diff
key-value
progress
chart
form
confirm
choices
product-cards
image

Transports

Available
WebSocket
/_arp/v1
Primary transport. Persistent bidirectional connection with auto-reconnect.
Available
SSE
Server-Sent Events
Fallback transport. Required for file uploads via multipart/form-data.
Planned
gRPC
High-performance
For native desktop and mobile applications.
Planned
stdio
NDJSON framing
For CLI renderers and pipe-based integrations.

Client SDKs

@haira/arp — Core (zero dependencies)
typescript
import { ArpClient } from '@haira/arp'

const client = new ArpClient('ws://localhost:8080/_arp/v1', {
  onDelta: (text) => appendToChat(text),
  onRender: (event) => renderComponent(event.component, event.props),
  onDone: () => markStreamComplete(),
})

client.connect()
client.sendText('Show me the sales data')
@haira/arp-react — Drop-in Chat UI
tsx
import { ArpChat } from '@haira/arp-react'

function App() {
  return (
    <ArpChat
      url="ws://localhost:8080/_arp/v1"
      theme="dark"
      title="Data Explorer"
    />
  )
}

Also available: @haira/arp-vue for Vue 3 and github.com/haira-lang/arp-go for Go backends.

Haira Integration

Every Haira server speaks ARP natively. No configuration needed.

haira
import "ui"

tool query_database(query: string) -> any {
    """Executes a SQL query and displays results."""
    rows, err = postgres.query(db, query)
    if err != nil {
        return ui.status_card("error", "Query Failed", conv.to_string(err))
    }
    return ui.table("Query Results", headers, rows)
}

agent DataExplorer {
    provider: OpenAI
    tools: [query_database]
    ui: ui
}

@webhook("/chat")
workflow Chat(message: string, session_id: string) -> stream {
    return DataExplorer.stream(message, session: session_id)
}

Extension Lifecycle

New components follow a three-phase lifecycle inspired by Wayland:

1. Experimental
x-vendor-name
Vendor-created. Breaking changes allowed.
2. Staging
s-name
Requires 2+ renderer implementations. Governance review.
3. Core
No prefix
Part of the ARP spec. Only additive changes.

Conformance Levels

LevelRequired ComponentsTarget
Minimaltext + text inputVoice assistants, IoT
Basictext, table, form, confirm, choicesCLI terminals
StandardAll core components, full object modelWeb / desktop
FullStandard + streaming, multi-surface, file uploadRich web apps

Released under the Apache-2.0 License.