← HTTP API

Live updates

http api · push channel · workzone

Status boards show state that background workers mutate — sync runs, curation runs, backups, agent runs. The screen never mutates what it shows, so cache invalidation tied to user actions can't refresh it, and per-screen polling timers don't scale. One platform push channel replaces them all.

Push the fact of change, not the data
SSE as an invalidation signal The stream carries a tiny nudge — “board X changed” — and nothing else. The client reacts by refetching the affected queries over the ordinary REST endpoints it already uses. Data, auth and ACL stay on REST: the stream can never leak a payload, never drifts from the REST schema, and backend load is proportional to real state changes — not to the number of open screens.
Worker run transition or progress commit
Publish nudge → cache Redis pub/sub, after commit
Stream /events/stream — one SSE per tab
Client invalidates the board's query keys
REST normal refetch — auth, ACL, schema

The channel rides the cache Redis pub/sub — a lost nudge costs one delayed refresh, never data. Publishing is best-effort: a broken bus must not fail the worker's own transaction.

Event contract — GET /api/v1/events/stream

One multiplexed stream per tab carries every push concern; the notification bell rides it too, so the platform holds a single persistent connection. The dispatcher keeps publishing bell nudges exactly as before — only the transport is shared.

EventPayloadMeaning
hello {boards: […]} opens the stream: the boards this caller is subscribed to. The client invalidates them all — a reconnect catches up on everything missed.
board {board} the named board changed; the client invalidates its mapped query keys.
unread {count} the bell counter, refetched server-side on the bell nudge — unchanged behavior, new transport.
ping {} heartbeat every 25 s of silence — a real event frame, not an SSE comment, so the client watchdog sees liveness.
Boards and who receives them

Access is decided at subscription time: when the stream opens, the caller's role picks the channel set — admin boards for admins, an agents channel per owner for everyone. No per-message filtering, no per-user fan-out for admin boards.

BoardFed byAudience
harvester Harvester sync-run journal + page-level progress counters admins
knowledge Knowledge Store curation runs · re-embed progress · backups admins
agents Agent Engine run journal, steps included the agent's owner — own channel; admins — the all-agents channel
Publish points and coalescing

What publishes. Every committed run transition (queued → running → terminal, cancel) and every committed mid-run progress write — a sync page boundary, a re-embed batch, an agent step. Publish follows the commit, so a nudge never announces state a refetch can't see yet.

What doesn't. Pure heartbeat touches — liveness bookkeeping is not a state change and must not wake every open screen.

Coalescing is the subscriber's job. Workers publish freely — a Redis publish is near-free; each stream connection coalesces per board with a minimum emit gap of 1.5 s, trailing-edge (a burst emits one frame now and one when the gap expires). A hot worker loop can never make a client refetch faster than the gap, and the last state always lands.

Client — one connection, one registry

The shell mounts a single stream consumer holding a declarative registry board → query-key prefixes. A board frame invalidates that board's prefixes; only queries actually mounted refetch. Screens carry no live-update code at all — no timers, no intervals: a screen is live because its keys are in the registry, not because it polls.

Fallback — silent degradation
The one exception — embedder weights

The built-in embedder's weight loading is the single live status that lives outside the platform's database — inside the embeddings runtime, which the backend only proxies. There is no transition of ours to publish from, so the embedder card keeps a narrow poll only while weights load. Everything else on every board is push. A second status of this kind would justify a server-side watcher; one does not.