How the chat stream works, why SSE over WebSockets, and how the frontend consumes it
POST /v1/mind/chat responds with Content-Type: text/event-stream. The body is a sequence of Server-Sent Events — small text frames the client reads as they arrive.
Repeatedly — a chunk of the answer. Concatenate in order.
done
{ conversation_id, action, revised, remaining }
Once, at the end. revised is a guardrail-corrected final answer (usually null); remaining is the day’s quota left.
message
{ text, reason }
Instead of deltas, when the turn is blocked (quota, assessment lock, or injection). Followed by done.
Render deltas live for responsiveness. Treat done.revised as authoritative when present — if it differs from the streamed text, replace the rendered answer with it.
The chat reply is one-way streaming: the server pushes tokens, the client just reads. SSE matches that shape exactly, and avoids the cost of a protocol built for two-way traffic.
SSE (what we use)
WebSockets
Direction
Server → client, which is all we need
Full duplex (unused here)
Transport
Plain HTTP — works with existing auth, HTTP/2, proxies, CDNs, load balancers
Upgrade handshake; often needs sticky sessions
Infra
Nothing new
Connection lifecycle, ping/pong, stateful routing
Resumption
Application-level via conversation_id (survives drops)
Must be rebuilt on top of the socket
Because each request is an ordinary HTTP call, the Authorization header and JSON body work the way they do everywhere else, and a dropped stream costs nothing — the conversation lives in the server-side checkpoint, so the client just reconnects with the same conversation_id.
We do not use the browser’s native EventSource: it is GET-only and can’t send an Authorization header or a request body. Use fetch() with a streaming reader (below) — the wire format is still SSE.
Keep the conversation_id from the first meta/done and pass it on every subsequent message — MIND loads the full history server-side. To start a new thread, send no conversation_id. If a stream drops mid-answer, re-send the same message with the same conversation_id; nothing is lost.