"Realtime" is the most over-bought line item in a data platform. The instinct, the moment someone says live, is to reach for a message broker and start pushing. We push exactly one thing — the analyst's chat — and serve everything else as an ordinary authorized read against one append-only Postgres.

The decision was a count of moving parts, not ideology. Postgres is already the source of truth: it holds the signed, append-only observation log and it enforces row-level security on every read. A broker — Redis, NATS, Kafka — would be a second system that can fail, a second thing to secure, and a second place where the "truth" could disagree with the database. For the shape of our product, that trade is not worth it. So there is no broker, no LISTEN/NOTIFY fan-out, and no websocket open to the browser.

The one thing we stream

The analyst chat streams token by token. The reasoning service talks to the model, and as text and tool-calls arrive it emits Server-Sent Events; the app passes that stream straight through to the browser. SSE because it is the boring fit: one direction, server to client, over plain HTTP, no extra protocol to run.

// the wire, server → browser
data: {"type": "tool_start", "name": "query_observations"}

data: {"type": "text", "content": "Two vessels show an AIS gap "}
data: {"type": "text", "content": "north of the cable… "}

data: {"type": "done", "citations": ["OBS-50412"], "grounded": true}

Even here the stream is not trusted as data. Every claim the agent makes carries a citation — [OBS-####] — that the client can re-resolve with its own authorized read. The tokens are a convenience; the database is the proof.

The stream is a convenience. The database is the proof.

Everything else is a pull

Observations, events, the map, a session's state — none of it is pushed. The client asks Postgres when it needs to, and the query re-applies row-level security, so you get back exactly the slice your role and clearance are allowed to see. The same policy runs on a cold page load and on a refresh; there is no second, push-shaped code path where visibility rules could quietly drift.

What that costs — and why we pay it

The honest trade is freshness. Outside the chat, a surface is as current as its last query, and the data underneath is as current as the last scheduled ingest that wrote signed observations into the log. There is no live cursor following another analyst around the map; if two people work the same case, each re-queries and sees the same committed truth a moment apart. For analysis under audit — where every figure has to trace back to a signed observation anyway — that is the right default, not a limitation.

If we were serving a public broadcast instead of a scoped, audited workspace, we would put a real streaming layer in front. We are not, so we did not. Pick the realtime architecture that matches the shape of the room — not the one that matches the conference talk.

— END —