Harness Transport Protocol
Overview
The Harness transport protocol connects a HarnessClient (browser) to a HarnessServer (backend) over a WebSocket. The protocol follows a command-send, state-receive pattern inspired by assistant-ui's AssistantTransportRuntime:
- Client sends commands (e.g. submit a prompt, cancel a run)
- Server sends state updates (the full current state, streamed incrementally)
The client never computes state. It sends commands and renders whatever the server tells it.
Connection
Client Server
| |
|--- WebSocket connect --------->|
| |
|<-- state (initial snapshot) ---|
| |
|--- commands ------------------>|
|<-- delta -----------------------|
|<-- delta -----------------------|
|<-- delta -----------------------|
| |On connect, the server sends the current state as an initial snapshot. The client can then send commands at any time.
Wire Format
All messages are JSON. Each message has a type field.
Client → Server (Commands)
type ClientMessage =
| { type: "commands"; commands: HarnessCommand[] }Server → Client (State Updates)
type ServerMessage =
| { type: "state"; state: HarnessState }
| { type: "delta"; operations: DeltaOperation[] }
| { type: "error"; message: string }State updates use the object stream pattern from assistant-stream:
statesends a full snapshot (used on connect and reconnect)deltasends incremental operations to patch the current state
DeltaOperation
type DeltaOperation =
| { type: "set"; path: string[]; value: JSONValue }
| { type: "append-text"; path: string[]; value: string }set: Replace the value atpath. Used for status changes, adding messages, etc.append-text: Append text to a string atpath. Used for streaming assistant output.
Examples:
{ "type": "set", "path": ["status"], "value": "running" }
{ "type": "append-text", "path": ["messages", "0", "content"], "value": " world" }
{ "type": "set", "path": ["messages", "0", "status"], "value": "complete" }The client applies operations in order to maintain a local copy of the state.
Commands
Commands are actions the client wants the server to perform.
type HarnessCommand =
| { type: "submit"; prompt: string }
| { type: "cancel" }submit
Submit a prompt for execution. The server queues it and begins processing.
cancel
Cancel the current run. The server aborts the active agent session.
State
The server maintains and streams a single state object:
type HarnessState = {
status: "idle" | "running" | "error";
messages: HarnessMessage[];
error?: string;
};
type HarnessMessage = {
id: string;
role: "user" | "assistant";
content: string;
status: "pending" | "streaming" | "complete" | "error";
toolCalls?: HarnessToolCall[];
};
type HarnessToolCall = {
id: string;
name: string;
status: "running" | "complete" | "error";
};State Fields
- status: The overall run status.
idlewhen no run is active,runningduring execution,errorif something went wrong. - messages: Ordered list of messages in the conversation. Grows as the run progresses.
- error: Error message when
statusis"error".
Message Fields
- id: Unique message identifier.
- role:
"user"for submitted prompts,"assistant"for Claude responses. - content: The text content. Streamed incrementally via
append-textoperations. - status: Lifecycle of the message.
pending→streaming→complete. - toolCalls: Active tool calls during this message (optional).
Typical Flow
- Client connects via WebSocket
- Server sends
statesnapshot (empty idle state or resumed session) - Client sends
commands: [{ type: "submit", prompt: "..." }] - Server sends deltas:
- Set status to
running - Add user message (complete)
- Add assistant message (pending → streaming)
- Append text deltas to assistant message content
- Set tool call status as tools execute
- Set assistant message status to
complete - Set status to
idle
- Set status to
- Client renders state after each delta
Extracting assistant-ui Messages
The HarnessClient provides a state converter that maps HarnessState to assistant-ui's ThreadMessage[] format, enabling the use of assistant-ui components for rendering.
Extracting HarnessState from the Sandbox Runner
The HarnessServer translates sandbox-runner SSE events into state deltas:
| Sandbox Runner Event | State Delta |
|---|---|
run.started | Set status to running, add user message |
assistant.delta | Append text to current assistant message |
tool.started | Add tool call to current assistant message |
run.completed | Set message status to complete, set status to idle |
run.error | Set error, set status to error |
Reconnection
On reconnect, the server sends a full state snapshot. The client replaces its local state entirely. No special reconnection handshake is needed.