Skip to content

Live-channel envelopes

read as .md

The live WebSocket carries two envelope shapes:

EnvelopeDirectionCarries
ActionEnvelopeclient → serverA canonical user action (form submit, click, …)
StreamEnvelopeserver → clientAn outbound delivery on a named stream channel

Canonical wire reference. Shapes below mirror the TypeScript in @ggui-ai/protocol verbatim: types/events.ts (ActionEnvelope), types/live-channel.ts (StreamEnvelope).


Inbound live-channel envelope — the body of a type: 'action' WebSocket message. Flat, narrow, limited to fields the server actually enforces or diagnostic fields real consumers populate today.

interface ActionEnvelope<TPayload = JsonValue> {
sessionId: string; // required — render identity
type: EventType; // required — see EventType below
payload?: TPayload; // shape depends on `type`
clientSeq?: number; // client-monotonic dedup hint
schemaVersion?: string; // producer's PROTOCOL_SCHEMA_VERSION (advisory pre-launch)
}
FieldTypeRequiredSemantics
sessionIdstringYesServer enforces subscriber-render binding — envelopes whose sessionId doesn’t match the render the WebSocket subscriber is bound to are rejected with SESSION_MISMATCH.
typeEventTypeYesAlways data:submit — the only member of the EventType union (see below). The active render’s actionSpec gates which action names are accepted. (The pre-actionSpec EventSubscription / DEFAULT_SUBSCRIPTION gating shapes were deleted from @ggui-ai/protocol — there is no subscription gating on the wire.)
payloadTPayload?NoFor type: 'data:submit' this carries an ActionEventValue shape ({action, data, tool?}) whose data field is validated against the render’s actionSpec[action].schema.
clientSeqnumber?NoClient-monotonic sequence number for at-least-once dedup. Declared shape; no server enforcement today (no inbound dedup infrastructure yet). Clients SHOULD populate when their transport can replay (e.g., reconnect-with-backfill).
schemaVersionstring?NoProtocol schema version stamped by the producer. Pre-launch: advisory — consumers MUST NOT reject on mismatch. Post-launch: receiving major mismatches surface as UPGRADE_REQUIRED.

These fields appear on neighboring shapes but are excluded from ActionEnvelope by design:

FieldWhy omitted
appIdServer resolves it from the render; client-claimed values are ignored for enforcement.
userId / userDiagnostic render metadata captured at subscribe time, not per-delivery.
deviceInfo / interfaceContextSame as above — render-level, not action-level.
componentId / contractHashDiagnostic; no enforcement consumer today.
timestampServer uses its own clock for ordering + log emission; client-supplied timestamps aren’t authoritative.
correlationIdThe doctrine names this for agent-push ↔ user-action pairing; sessionId covers the narrow case today.

EventType is a single-member union — 'data:submit' is the only action type the protocol recognizes:

TypeNotes
data:submitCarries ActionEventValue ({action, data, tool?}); schema-validated.

The old data:change / lifecycle:focus / lifecycle:blur / interaction:click / interaction:hover / interaction:scroll / error:validation / error:connection members were removed in draft-2026-06-12 — they had no producers.

Payload shape for data:submit:

interface ActionEventValue<TData = unknown> {
action: string; // action ID from the contract (e.g. "submit", "archive")
data: TData; // action payload (e.g. form data)
tool?: string; // MCP tool name mirrored from actionSpec[action].nextStep (when declared)
}

Derivation. tool is derived server-side from actionSpec[action].nextStep at event-build time — clients do not populate it. When the action has no declared nextStep, tool is absent — the agent decides the next tool freely from broader context. The hint is advisory either way; the agent owns the call decision.


Outbound live-channel envelope — the body of a type: 'data' WebSocket message. Carries a single delivery on a named stream channel.

interface StreamEnvelope {
sessionId: string; // required
channel: string; // required — keys into streamSpec
mode: StreamChannelMode; // required — 'append' | 'replace'
payload: JsonValue; // required — channel-specific shape
complete?: boolean; // terminal marker for completable channels
seq?: number; // server-assigned monotonic outbound sequence
schemaVersion?: string; // producer's PROTOCOL_SCHEMA_VERSION
}
FieldTypeRequiredSemantics
sessionIdstringYesRender this delivery belongs to.
channelstringYesChannel name. Keys into the agent’s declared streamSpec. Names starting with _ggui: are server-owned (see Reserved channels).
modeStreamChannelModeYesState-folding mode. 'append' (default for narrative streams — message log, telemetry) accumulates deliveries; 'replace' (default for snapshot streams — task progress, session state) replaces the current value. Senders declare; receivers honor. Typically equals the channel’s declared mode on the spec, but the envelope is the authoritative per-delivery signal.
payloadJsonValueYesValidated against streamSpec[channel].schema. Shape is channel-specific; consumers typecheck via contract inference when they use defineContract + useStream.
completeboolean?NoTerminal completion marker — truthy on the last delivery for a completable channel (one declared with complete: true on the spec). Consumers use this to transition subscribers into a “channel closed” state. Absent on non-terminal deliveries.
seqnumber?NoRender-scoped monotonic outbound sequence. Server-assigned; clients MUST NOT populate it on producer-side inputs. Gap-free within a single render, starting at 1. Used by the client to track lastSeenSeq for reconnect (pass it back as SubscribePayload.fromSeq) and to dedupe deliveries (at-least-once semantics). OPTIONAL today because hosted cloud doesn’t yet stamp seq; OSS @ggui-ai/mcp-server always populates it. A future slice promotes this to required.
schemaVersionstring?NoSame advisory-pre-launch / UPGRADE_REQUIRED-post-launch semantics as on ActionEnvelope.
FieldWhy omitted
replayPer-channel policy declared on streamSpec[channel].replay. A per-delivery field would imply replay can vary message-to-message, which it can’t.
timestampReplay correctness needs seq only; timestamp is a future optional addition driven by a concrete client-UX need.

Channel names starting with _ggui: are reserved for the server. Agent-authored streamSpec MUST NOT declare any name in this namespace; structural validation rejects every reserved-prefix entry with the message Stream channel '<name>' is in the reserved '_ggui:' namespace — server-owned channels cannot be declared in agent streamSpec.

Two channel names are recognized today:

ChannelOwnerPayload shapePurpose
_ggui:previewServer (A2UI)A2UI ServerMessage — see @ggui-ai/preview-a2uiProvisional A2UI assembly stream emitted by the server during fresh-gen ggui_render flows. The renderer subscribes implicitly and dispatches the payload through its preview surface.
_ggui:lifecycleServerGguiLifecyclePayloadGeneration-progress lifecycle kinds (handshake_started, handshake_completed, render_started, consume_polling) the renderer surfaces as a progress affordance.

A typo inside the reserved namespace (e.g. _ggui:preveiw) is NOT recognized — delivery falls through to the normal “unknown channel” rejection rather than silently passing as a reserved channel.

The _ggui:contract-error channel was a reserved channel in earlier drafts but was removed entirely in draft-2026-06-11 — the channel, its payload shape, and its validator are gone. Contract violations now surface as a typed CONTRACT_VIOLATION error frame on the live channel (code -32020) on the call that caused them; nothing lands on the consume buffer.


The schemaVersion field is present on both envelopes. It carries the producer’s PROTOCOL_SCHEMA_VERSION constant.

Pre-launch (draft- versions): advisory. Consumers MUST NOT reject on mismatch; missing schemaVersion fields are normal.

Post-launch: receiving an envelope whose major version diverges from the consumer’s known major surfaces as a live-channel error with code: UPGRADE_REQUIRED. The handshake resolution flow is documented in the WebSocket Protocol reference.

See Version policy for the full mapping of what counts as a major / minor / patch bump.