Skip to main content
The browser receives agent output over two channels that share one frame vocabulary, ChatStreamEvent, defined in web/protocol/src/chat-protocol.ts.

Turn stream: NDJSON over POST

POST /api/chat runs a turn and streams NDJSON frames back on the response. The first frame is a start frame that advertises adapterCapabilities (for example reasoning-summary-v1), which is how optional features are negotiated: clients that don’t recognize a capability simply don’t render the corresponding enhancement. While a turn is active, the NDJSON stream is authoritative; the SSE handler skips frames for a session whose status is streaming or submitted so nothing renders twice.

Out-of-turn pushes: SSE with replay

GET /api/chat/events?sessionId= opens a Server-Sent Events channel for pushes that happen outside an active turn: tool-Question dialog requests, state frames from notify/setStatus, and messages sent by IPython code. Every dispatched frame lands in a per-session in-memory ring buffer (500 frames) with a monotonically increasing integer seq. On reconnect the client sends Last-Event-ID and the server replays every frame with a greater sequence number. If the ring buffer overflowed while the client was away, the server emits a resync-required state frame and the client rehydrates the session with GET /api/chat/session. Sequence numbers persist in sessionStorage, so a page reload resumes the SSE cursor without a server round trip. Sequences are per-session and in-memory; they are not durable across a server restart.

Frame types

Tool naming

The event mapper converts engine tool names to PascalCase frame types with special-casing for IPython and short acronyms: tool-IPython, tool-Bash, tool-Edit, tool-Task, tool-Question, tool-WebSearch, and so on. Tool renderers in web/design/src/components dispatch on part.type, and anything without a dedicated card falls back to a generic tool renderer.

Interactive questions

When the engine asks for input (confirm, select, input), the bridge emits a tool-Question frame and registers a pending dialog with a 60-second timeout. The browser answers through POST /api/chat/question; aborting a turn also cancels its pending dialogs. Pending dialogs are not persisted across a server restart.
Last modified on August 30, 2026