State-server uses a CBOR-encoded WebSocket protocol for real-time state delivery. v2.0 is built around two delivery modes:
The only supported success-path runtime messages are: response, entity.snapshot, entity.update.
2.0.wss://<host>/ws?token=<jwt>.All messages — inbound and outbound — use a single CBOR map shape:
{
id: "msg_123",
type: "response",
version: "2.0",
timestamp: 1710000000,
payload: <CBOR payload or null>,
metadata: { /* optional */ }
}
Fields: id (correlation), type (message kind), version (protocol), timestamp (unix seconds), payload (CBOR or null), metadata (optional map).
Snapshot-producing requests:
brane.joingroup.getobject.getprototype.getMutation requests (use existing inbound names):
object.create, object.update, object.delete, object.take, object.putprototype.create, prototype.update, prototype.delete, prototype.instantiatecommand.undo, command.redoSuccessful mutations are acknowledged with response(stage="accepted"); resulting changes are delivered as entity.update.
responseCarries control metadata in metadata.stage. Supported stages:
accepted — request received.snapshot_complete — snapshot has been fully streamed.version_complete — a replay/live version batch finished.live_ready — caught up; now in live mode.leave_complete — brane.leave finished.Example payload-less accepted envelope for a brane.join:
{
type: "response",
id: "msg_join",
version: "2.0",
payload: null,
metadata: { stage: "accepted", mode: "snapshot", root_id: "brane_123", base_version: 17 }
}
entity.snapshotUsed only while streaming a snapshot. payload is the raw stored CBOR entity body; metadata.version is the snapshot version.
{
type: "entity.snapshot",
version: "2.0",
payload: <raw entity cbor>,
metadata: { entity_id: "obj_1", entity_type: "group", parent_id: "brane_123", version: 17 }
}
entity.updateUsed for replayed and live mutations. payload is present for created / updated, empty for removed / deleted.
Metadata: entity_id, entity_type, action, version, optional cause, command_id, parent_id, old_parent_id.
brane.joinSnapshot path: accepted (mode=snapshot) → root entity.snapshot → descendant entity.snapshot (breadth-first) → snapshot_complete → zero or more replay/live entity.update batches → version_complete per version → live_ready when caught up.
Resume path: accepted (mode=resume) → replay entity.update batches with version >= resume_version → version_complete per batch → live_ready.
Only one brane stream is active per socket; a new brane.join cancels the old stream.
brane.leaveRequest returns response(stage="leave_complete", brane_id, unsubscribed_count). After leave, no further entity.snapshot, entity.update, version_complete, or live_ready frames for that brane.
group.get / object.get / prototype.getSnapshot-only — accepted (mode=snapshot) → one or more entity.snapshot → snapshot_complete. No replay, no live mode. object.get include=["hierarchy"] is not supported in v2.
Clients should persist last_applied_version per active brane stream only.
Rules:
base_version only after snapshot_complete.V only after version_complete(version=V).Replay is inclusive: resume_version = last_applied_version, server replays versions >= resume_version. Same-version updates form an unordered replayable batch — clients must apply idempotently.
Mutations are acknowledged with:
{ type: "response", id: "msg_mutation",
metadata: { stage: "accepted", resulting_version: 21, command_id: "cmd_21" } }
If the mutation affects a joined brane, the resulting version is delivered via entity.update followed by version_complete. command.undo and command.redo produce a normal new version; they do not trigger snapshots for already-live clients.
Prototype mutations in v2 are requester-scoped:
response + entity.update.version.version_complete.last_applied_version.Errors arrive as system.error:
{ type: "system.error", id: "msg_404",
payload: { code: "OBJECT_001", message: "The requested object was not found", request_id: "msg_404" } }
Outside the v2 core success path, explicit subscriptions remain available for targeted listeners:
subscription.create, subscription.delete.event.object.created, event.object.updated, event.object.deleted.event.presence.These are independent of snapshot streaming, brane replay, and mutation acks.
gitlab.avvyland.com/avvy/state-server — docs/WEBSOCKET_PROTOCOL.md.