Reference
SSE streams
Every live surface in any is a plain HTTP Server-Sent Events response: one request opens the stream, the server pushes frames, and a terminal closed frame tells the client to reconnect. All streams share one envelope and one reason vocabulary, so a client needs a single state machine.
The envelope
event: ready
data: {}
event: <stream-specific>
data: { … }
: keepalive # comment frame every ~25s while idle
event: closed
data: {"reason": "server_shutdown"}
Rules that hold for every stream:
- Wait for
readybefore treating the stream as live. Errors that happen before the stream opens arrive as a normal JSON error envelope on the response, not as SSE. closedis terminal — reconnect with a fresh request. There is no replay and no resume cursor; the new stream's snapshot reflects current state.- Windowed streams use POST (the filter body does not fit a query string), so a browser reads them with
fetch+ a streaming body rather thanEventSource.
Close reasons
| Reason | Meaning | Emitted by |
|---|---|---|
server_shutdown |
the server is exiting (signal or POST /v1/shutdown); in-flight streams get up to 10 s to emit it |
every stream |
sdk_closed |
the engine released the underlying subscription (space or SDK closed) | query/subscribe |
overflow |
the subscriber's mailbox filled before it drained (query/subscribe: capacity mailboxCapacity, default 256, min 16; event bus: 16-deep buffer) |
query/subscribe, event bus |
drifted |
more than driftBudgetPercent (default 30) of the held window left without replacement; the engine refuses to re-query on the hot path |
query/subscribe |
overflow and drifted are split only so clients can log and back off sensibly — recovery is identical.
Windowed query/subscribe
Endpoints: POST /v1/spaces/:id/objects/query/subscribe (cross-object), POST /v1/spaces/:id/query/subscribe (per-object dataset), POST /v1/spaces/query/subscribe (the account's space list), POST /v1/spaces/:id/objects/:objectId/files/query/subscribe (one object's payload rows), POST /v1/devices/query/subscribe.
| Frame | Payload | Notes |
|---|---|---|
ready |
{} |
once |
snapshot |
{records, total?, hasNext?} |
once, right after ready; total/hasNext only with includeTotal |
changes |
[{versionId, added, updated, removed}] |
zero or more windowed events, coalesced per write |
closed |
{reason} |
terminal |
added and updated entries are {id, doc, ops} — the full post-apply record plus the $set/$unset ops that triggered the transition ($inc/$addToSet/$pull are collapsed to the resulting $set before delivery). removed entries are {id, reason} with reason one of deleted (tombstoned — drop for good), filtered-out (still exists, no longer matches) or displaced (still matches, pushed past limit). versionId is per-change DAG order local to this peer — never compare it across devices.
curl -N -X POST http://127.0.0.1:7001/v1/spaces/$SP/query/subscribe \
-d '{"objectId":"'$CHAT'","dataset":"chat_messages","sort":["-_ver.id"],"limit":50}'
# any query-subscribe $SP $CHAT --dataset chat_messages --sort=-_ver.id --limit 50
Contract and client recipe: Subscribe.
Sync status
GET /v1/sync-status/subscribe (every space, account-scoped) and GET /v1/spaces/:id/sync-status/objects/:objectId/subscribe.
| Frame | Payload |
|---|---|
ready |
{} |
status |
the same body as the matching GET — state ∈ unknown, offline, syncing, synced, error |
lagged |
{total} — the 16-deep forwarder dropped events |
closed |
{reason} |
State transitions are sparse, so lagged is rare. See Sync status.
Members
GET /v1/spaces/:id/members/subscribe — any members subscribe <spaceId>.
| Frame | Payload |
|---|---|
member |
{kind: "added"|"changed"|"removed", member: Member, previous: Member | null} |
lagged |
{total} |
member is the full post-event shape; previous is null on added.
Identities directory
GET /v1/identities/subscribe — any identities subscribe.
| Frame | Payload |
|---|---|
identities |
{added: [IdentityInfo], updated: [IdentityInfo], removed: [identity]} |
lagged |
{total} |
A contact whose profile key arrives later surfaces first in added with an empty name, then in updated once resolved.
File status
GET /v1/spaces/:id/files/subscribe — any file subscribe <spaceId>. Local transitions only.
| Frame | Payload |
|---|---|
status |
{fileId, objectId, state: "durable"|"inflight"|"limited", cached, attempts?, lastErr?} |
lagged |
{total} |
Event bus
GET /v1/events/subscribe?scope=&spaceId=&type=&target= — any events subscribe. Not an SDK subscription: the source is the in-process hub behind POST /v1/events. At-most-once, no snapshot; only events published after connect are delivered.
| Frame | Payload |
|---|---|
event |
{type, scope, spaceId?, target?, data?, sender: {identity, self}} |
closed |
{reason: "server_shutdown" | "overflow"} |
Filters are repeatable query params — AND across dimensions, OR within one; type takes an exact slug or an x.* prefix. Process frames (process.started / progress / done / failed / cancelled / cancel) ride this stream: ?type=process.*. See Event bus.
Streams in the CLI
Every streaming subcommand prints one JSON object per frame on stdout:
{"event": "ready", "data": {}}
{"event": "status", "data": {"spaceId":"…","state":"syncing"}}
{"event": "closed", "data": {"reason": "server_shutdown"}}
Note. The request body's
projectionfield is accepted but not applied: every record ships its full form, including_verand, when present,_traces/_deletedAt. Strip them client-side if you want a leaner local model.