Harness SDK

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:

  • state sends a full snapshot (used on connect and reconnect)
  • delta sends 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 at path. Used for status changes, adding messages, etc.
  • append-text: Append text to a string at path. 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. idle when no run is active, running during execution, error if something went wrong.
  • messages: Ordered list of messages in the conversation. Grows as the run progresses.
  • error: Error message when status is "error".

Message Fields

  • id: Unique message identifier.
  • role: "user" for submitted prompts, "assistant" for Claude responses.
  • content: The text content. Streamed incrementally via append-text operations.
  • status: Lifecycle of the message. pendingstreamingcomplete.
  • toolCalls: Active tool calls during this message (optional).

Typical Flow

  1. Client connects via WebSocket
  2. Server sends state snapshot (empty idle state or resumed session)
  3. Client sends commands: [{ type: "submit", prompt: "..." }]
  4. 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
  5. 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 EventState Delta
run.startedSet status to running, add user message
assistant.deltaAppend text to current assistant message
tool.startedAdd tool call to current assistant message
run.completedSet message status to complete, set status to idle
run.errorSet 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.