---
title: Live-channel envelopes
description: Wire shapes for ActionEnvelope (inbound), StreamEnvelope (outbound), and the reserved _ggui namespace.
---

The live WebSocket carries two envelope shapes:

| Envelope         | Direction       | Carries                                          |
| ---------------- | --------------- | ------------------------------------------------ |
| `ActionEnvelope` | client → server | A canonical user action (form submit, click, …)  |
| `StreamEnvelope` | server → client | An outbound delivery on a named stream channel   |

Canonical wire reference. Shapes below mirror the TypeScript in [`@ggui-ai/protocol`](https://github.com/ggui-ai/ggui/tree/main/packages/protocol/src) verbatim: `types/events.ts` (ActionEnvelope), `types/live-channel.ts` (StreamEnvelope).

:::tip[Protocol version]
Wire shapes documented here are stable at `draft-2026-06-12`. See [Version policy](/protocol/version-policy/) for breaking-change semantics.
:::

---

## ActionEnvelope

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.

```ts
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)
}
```

| Field           | Type        | Required | Semantics                                                                                                                                                                                                                                                                                                                                 |
| --------------- | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sessionId`     | `string`    | Yes      | Server enforces subscriber-render binding — envelopes whose `sessionId` doesn't match the render the WebSocket subscriber is bound to are rejected with `SESSION_MISMATCH`.                                                                                                                                                               |
| `type`          | `EventType` | Yes      | Always `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.) |
| `payload`       | `TPayload?` | No       | For `type: 'data:submit'` this carries an `ActionEventValue` shape (`{action, data, tool?}`) whose `data` field is validated against the render's `actionSpec[action].schema`.                                                                              |
| `clientSeq`     | `number?`   | No       | Client-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).                                                                                                     |
| `schemaVersion` | `string?`   | No       | Protocol schema version stamped by the producer. Pre-launch: advisory — consumers MUST NOT reject on mismatch. Post-launch: receiving major mismatches surface as `UPGRADE_REQUIRED`.                                                                                                                                                     |

### Fields intentionally NOT on the envelope

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

| Field                             | Why omitted                                                                                             |
| --------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `appId`                           | Server resolves it from the render; client-claimed values are ignored for enforcement.                  |
| `userId` / `user`                 | Diagnostic render metadata captured at subscribe time, not per-delivery.                                |
| `deviceInfo` / `interfaceContext` | Same as above — render-level, not action-level.                                                         |
| `componentId` / `contractHash`    | Diagnostic; no enforcement consumer today.                                                              |
| `timestamp`                       | Server uses its own clock for ordering + log emission; client-supplied timestamps aren't authoritative. |
| `correlationId`                   | The doctrine names this for agent-push ↔ user-action pairing; `sessionId` covers the narrow case today. |

### `EventType`

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

| Type          | Notes                                                                   |
| ------------- | ----------------------------------------------------------------------- |
| `data:submit` | Carries `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.

### `ActionEventValue`

Payload shape for `data:submit`:

```ts
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.

---

## StreamEnvelope

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

```ts
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
}
```

| Field           | Type                | Required | Semantics                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| --------------- | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sessionId`     | `string`            | Yes      | Render this delivery belongs to.                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `channel`       | `string`            | Yes      | Channel name. Keys into the agent's declared `streamSpec`. Names starting with `_ggui:` are server-owned (see [Reserved channels](#reserved-channels)).                                                                                                                                                                                                                                                                                                                          |
| `mode`          | `StreamChannelMode` | Yes      | State-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.                                                                                                   |
| `payload`       | `JsonValue`         | Yes      | Validated against `streamSpec[channel].schema`. Shape is channel-specific; consumers typecheck via contract inference when they use `defineContract` + `useStream`.                                                                                                                                                                                                                                                                                                              |
| `complete`      | `boolean?`          | No       | Terminal 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.                                                                                                                                                                                                                              |
| `seq`           | `number?`           | No       | Render-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. |
| `schemaVersion` | `string?`           | No       | Same advisory-pre-launch / `UPGRADE_REQUIRED`-post-launch semantics as on `ActionEnvelope`.                                                                                                                                                                                                                                                                                                                                                                                      |

### Fields intentionally NOT on the envelope

| Field       | Why omitted                                                                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `replay`    | Per-channel policy declared on `streamSpec[channel].replay`. A per-delivery field would imply replay can vary message-to-message, which it can't. |
| `timestamp` | Replay correctness needs `seq` only; timestamp is a future optional addition driven by a concrete client-UX need.                                 |

---

## Reserved channels

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:

| Channel           | Owner         | Payload shape                                      | Purpose                                                                                                                                                                                |
| ----------------- | ------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `_ggui:preview`   | Server (A2UI) | A2UI `ServerMessage` — see `@ggui-ai/preview-a2ui` | Provisional 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:lifecycle` | Server        | `GguiLifecyclePayload`                             | Generation-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.

---

## Schema versioning

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](/api/websocket-protocol/) reference.

See [Version policy](/protocol/version-policy/) for the full mapping of what counts as a major / minor / patch bump.

---

## See also

- [WebSocket Protocol](/api/websocket-protocol/) — live-channel message types (`subscribe`, `action`, `ack`, `render`, `data`, `error`) that carry these envelope payloads.
- [MCP Protocol](/api/mcp-protocol/) — MCP JSON-RPC methods (`ggui_handshake`, `ggui_render`, `ggui_consume`, etc.).
- [Protocol overview](/protocol/overview/) — the three-channel topology.
- [Version policy](/protocol/version-policy/) — semver semantics, breaking-change definition, deprecation timeline.
- [`@ggui-ai/protocol` source](https://github.com/ggui-ai/ggui/tree/main/packages/protocol/src) — canonical TypeScript definitions.