Skip to main content

Overview

The thread message stream pushes JSON frames to the client as the thread changes. It is the client’s only change signal: on every (re)connect the server first sends the current run status, then each change as it happens, so clients never poll.
  • thread_status — authoritative run status: a snapshot on connect, then every change
  • message_data — a message was created or updated
  • message_chunk — streamed visible content of an assistant message
  • reasoning_chunk — streamed model reasoning (opt-in)
  • event — custom events from tools, hooks, and endpoints (ThreadState.emit())
  • telemetry frames — execution progress (step_started, tool_started, stopped, …)
The official SDKs (@standardagents/client, @standardagents/react, @standardagents/vue) decode all of these; the sections below are the wire contract for other clients.

Connecting

Browsers cannot set an Authorization header on a WebSocket, so authenticated clients first mint a short-lived single-use ticket for the exact path and pass it as ?ticket=:

Query parameters

string
WebSocket ticket from POST /api/auth/websocket-ticket (required when the runtime requires authentication).
number
Deepest message depth to receive. 0 (default) is the top-level thread; higher values include subagent / sub-prompt messages.
boolean
true to also receive silent messages. Default false.
boolean
Truthy (1, true) to receive reasoning_chunk frames. Default off.

Server → Client Frames

thread_status

The thread’s authoritative run status. Sent immediately after the socket is accepted (a snapshot of the current status) and then on every change (state or reason). A reconnecting client therefore always resynchronizes from the snapshot; no polling is ever needed.
string
  • idle — nothing is running and the last run did not fail (it replied, called a stop tool, handed the turn to a human side, was stopped by the user, was refused by a hook, or hit a step/turn cap), or the thread has never run (since is 0).
  • running — an execution is in flight.
  • failed — the last run ended in an error.
string | null
The stop reason code of the last transition out of running, for clients that want detail: stop_on_response, stop_tool, awaiting_human, hook_blocked, max_total_steps, max_side_steps, max_session_turns, session_stop, aborted, aborted_by_user, step_error, repeated_tool_error, session_fail, fatal_error, execution_lost. Treat unknown codes as opaque. null while running.
A run the runtime lost (deploy, eviction, crash) never stays running: it settles to failed with reason execution_lost.

message_data

A message was created or updated (including status changes such as pending → completed). Filtered by the socket’s depth and includeSilent.

message_chunk

A streamed fragment of an assistant message’s visible content. Accumulate by message_id; the authoritative final content arrives as message_data.
Append chunks in socket order and keep provisional buffers until their terminal message_data records arrive. Do not erase already-painted text merely because a provider retry or reconnect exposes another message_id; retain it as a reconciliation alias (or as a separate keyed draft).

reasoning_chunk

A streamed fragment of model reasoning. Only sent to sockets connected with ?reasoning=1; not persisted, so it cannot be replayed from history.

event

A custom event emitted by application code with ThreadState.emit() (tools, hooks, effects, and thread endpoints), and runtime notifications such as file_created, file_updated, file_deleted, and effect_changed.

Telemetry frames

Execution progress, sent as they happen. Each frame’s type is the telemetry kind itself; timestamp is milliseconds since epoch. Telemetry is advisory — use thread_status for whether the thread is running.

Keepalive

Send the string stream_ping periodically; the runtime answers stream_pong at the edge without waking a hibernating thread. (Older servers answer a plain ping with pong.) These heartbeats are protocol keepalives, not state checks.

Handling Frames

Reconnection

Reconnect with backoff when the socket closes. On reconnect, mint a new ticket, then reconcile once: the first frame is a fresh thread_status snapshot, and a single GET /api/threads/:id/messages read catches up any messages missed while disconnected. Do not add timers that re-read thread state.

React and Vue

useThread() in @standardagents/react and @standardagents/vue manages the socket, reconnects, and exposes runStatus (the latest thread_status, null until the first frame) alongside connectionStatus/status (the socket’s own connection state).
See React Integration for full documentation.