POST /chat streams the agent’s response as Server-Sent Events (AG-UI protocol frames). The adapter is responsible only for the wire mechanics — headers first, frames verbatim, backpressure honored, teardown on disconnect. The event semantics live in the AG-UI chat reference.
What the adapter writes
On a chat request the adapter:- Flushes headers immediately (
X-Accel-Buffering: no,Cache-Control: no-cache, the AG-UI content type) so the client starts receiving events before the first token. - Writes each frame verbatim —
data: ...\n\npayload lines from the runtime, plus: heartbeatcomment lines on the runtime’s heartbeat cadence (default 15s) to keep intermediaries from timing the connection out. - Honors backpressure — if
res.write()returnsfalse, it waits for the socket’sdrainevent before writing the next frame, so a slow client can’t balloon memory. - Unsubscribes on disconnect — if the client hangs up mid-stream, the adapter calls
iterator.return()on the runtime subscription immediately. No zombie pumps, no leaked timers.
EventSource / fetch-stream:
Reconnect & resume
ThePOST /chat response carries an x-persona-run-id header. If the connection drops, the client can reattach to the same run and keep receiving the tail of the stream:
runGraceMs (default 5 minutes) in-process. Because resume state lives in the runtime instance’s memory, route all chat traffic for a given user to the same instance (sticky sessions) until multi-instance resume ships — see the Reconnect & resume reference for the full mechanics and the honest limitation.
Binary streams
File downloads (GET /files/:id) and any other kind: 'binary' response stream through the same writer, chunk by chunk, without buffering the whole file.
Disconnect teardown, in detail
The adapter registers aclose handler on the response that calls return() on the runtime’s subscription iterator. Because the runtime’s subscriptions return a fresh iterator per [Symbol.asyncIterator]() call, the adapter pins the loop to the single iterator it holds — so return() always cancels the exact subscription being consumed. A mid-stream disconnect therefore stops the upstream run within one event loop tick.
After a normal completion (or after the close teardown), the close listener is removed — there are no lingering listeners across requests.
Example: live token streaming
data: {...} — the TEXT_MESSAGE_CHUNK events carry token deltas; the TOOL_CALL_CHUNK events carry tool call fragments; the terminal state settles when the run finishes. See the Streaming events reference for the full event taxonomy.
Heartbeat cadence
Heartbeats are sent by the runtime, not the adapter — the adapter just passes them through. Configure the cadence onPersonaModule.forRoot():