---
title: Version policy
description: Major / minor / patch semantics for the ggui protocol, deprecation timeline, support matrix, and CI enforcement.
---

> **Semver promise:** a change to `@ggui-ai/protocol` is breaking if-and-only-if a consumer built against the prior version now fails the conformance kit.

This page defines "major", "minor", and "patch" on the ggui protocol, how deprecation windows work, which server ↔ client pairs are supported, and how a breaking change is migrated.

The canonical version constant is `PROTOCOL_SCHEMA_VERSION` (an alias of `PROTOCOL_VERSION` in [`@ggui-ai/protocol`](https://github.com/ggui-ai/ggui/blob/main/packages/protocol/src/version.ts)). Current value: `draft-2026-06-12`. Wire-level negotiation runs through the schema-version handshake; mismatches surface as the `UPGRADE_REQUIRED` error. The current server default is `versionPolicy: 'reject'` — on mismatch the server emits `UPGRADE_REQUIRED` and closes the connection; `'advisory'` is a legacy opt-out that keeps it open.

---

## 1. Semver semantics applied to the protocol

The protocol follows semver. What counts as each bump kind is defined by the **conformance kit** ([`packages/protocol-conformance`](https://github.com/ggui-ai/ggui/tree/main/packages/protocol-conformance)), not by surface-syntax changes in the `@ggui-ai/protocol` package.

### Major (N → N+1)

A bump is **major** iff at least one conformance-kit fixture that passed against version N now fails against version N+1 when the only change is the protocol version.

Examples of changes that are major:

- Renaming a **reserved channel** (e.g., `_ggui:preview` → `_ggui:assembly`).
- Removing a canonical live-channel error code that the kit asserts (e.g., deleting `CONTRACT_VIOLATION`).
- **Narrowing** an extensibly-closed union (e.g., collapsing `SubmitActionKind` variants so previously-recognized kinds now fail validation).
- Changing the shape of an **envelope** (`ActionEnvelope`, `StreamEnvelope`) in a way that invalidates fixtures emitted by prior-version producers — required-field additions, required-field renames, required-field type changes.
- Tightening a `MUST` / `MUST NOT` clause in the spec such that prior-conformant implementations become non-conformant.
- Changing handshake semantics in a way that rejects a subscribe that would previously have succeeded (e.g., the `versionPolicy` default flip `advisory` → `reject` — already shipped on 2026-04-24 as a pre-launch config-default change with its own release note; post-v1.0, a flip of this shape is major).
- Removing or replacing a **transport binding** previously declared conformant.

### Minor (N.x → N.(x+1))

A bump is **minor** iff every conformance-kit fixture that passed against N.x still passes against N.(x+1), and the delta is additive.

Examples of changes that are minor:

- Adding a new **`channel_error` code** literal (consumers that don't recognize it degrade per the extensibly-closed union rule).
- Adding a new **observability event kind** to the renderer's `ggui:observe` postMessage union (extensibly-closed — see the [Bootstrap handshake](/protocol/bootstrap-handshake/) page).
- Adding an **optional field** on an envelope. Pre-existing consumers see `undefined` and MUST behave as before.
- Adding a new **reserved channel** whose absence is not a failure mode for prior-version consumers.
- Adding a new **canonical live-channel error code** (the `code` field is typed open).
- Adding a new **transport binding** (e.g., stdio MCP, HTTP long-poll) while keeping WebSocket canonical.

### Patch (N.x.y → N.x.(y+1))

A bump is **patch** iff the only changes are documentation clarifications, fixture additions that do not change assertions on prior-version consumers, or internal implementation adjustments to first-party packages that do not affect the wire or the kit.

Examples:

- Spec prose clarifications that close a reader-ambiguity without changing obligations.
- New conformance-kit fixtures that assert behavior already required by the prior version's spec text.
- Bug fixes in first-party servers / clients that bring them back into conformance (the bar was always there — the code now honors it).

### The `draft-` prefix

Pre-v1 versions use a `draft-` prefix (e.g., `draft-2026-06-12`). While `draft-`, the semver rules above describe **intent**, not **obligation** — the protocol reserves the right to ship breaking changes without a migration doc until the first stable release tags `v1.0`.

**Rule flip at v1.0:** once `PROTOCOL_SCHEMA_VERSION` drops the `draft-` prefix, every major bump MUST have a migration doc (CI-enforced) and every deprecation MUST honor the window described in §3.

---

## 2. Breaking change definition — the kit is the arbiter

> **A change is breaking if-and-only-if a consumer built against the prior version now fails the conformance kit.**

Anchor every future "is this breaking?" debate to a kit run. Opinion, intent, and prose-level spec reading are NOT the arbiter — the kit is.

Operational consequences:

1. If you're unsure whether a PR is breaking, **run the kit** against the prior-version tag and against the PR branch. If fixtures that passed before now fail, the PR is major.
2. If the kit passes but you "feel" the change is risky, **add a fixture** that captures the worry. Either the fixture passes (the change is not breaking) or it fails (the change is breaking and you just found out) — both outcomes are productive.
3. If you think a change is major but the kit disagrees, the kit has a gap. Patch the kit in the same PR; the patched kit decides.

This anchor means every change's breaking-ness is **observable and reproducible**, not a matter of taste.

---

## 3. Deprecation timeline

Deprecation is the only graceful path from a minor-version ship to a major-version removal.

### The window

- **v(N)** — the version in which a surface is first tagged `@deprecated` (in TSDoc) AND documented in the release notes as deprecated. The surface MUST continue to work identically to the prior version; `@deprecated` is a signal to consumers, not a behavior change.
- **v(N+1)** — deprecated surface still works. Callers SHOULD migrate in this window. Release notes repeat the deprecation warning.
- **v(N+2)** — removal is allowed here at the earliest. Removal is a major bump, so this version is v(N+2) = major. The two-minor window guarantees consumers saw at least one full minor cycle with the `@deprecated` warning before removal.

### Minimum

**At least two minor versions MUST elapse between `@deprecated` and removal.** If N is the first deprecated version and N+2 is the major that removes it, the intermediate v(N+1) minor MUST ship. Shorter windows (v(N) deprecated → v(N+1) removed) are NOT allowed for non-security changes.

### Security-fix escape hatch

A **critical security fix** MAY skip the window — shipping a breaking change without a prior `@deprecated` minor — if and only if:

1. The release note explicitly names the CVE or security class.
2. The release note explains why the window was skipped.
3. A migration doc per §5 ships alongside the release.

Non-security urgency (e.g., "this mistake is embarrassing") is NOT grounds for skipping the window.

### What "deprecated" means on the wire

The protocol does not have a wire-level "deprecated field" marker. Deprecation lives in TSDoc on the `@ggui-ai/protocol` types and in the release notes. Consumers parsing the wire cannot detect a field is `@deprecated` from the frame alone — they learn it from the package changelog.

### Policy-default flips count as breaking

Changing a **default** that the wire handshake depends on is breaking under §2 because consumers that relied on the prior default observably fail against the new default. The canonical example already happened pre-launch: the `versionPolicy` default flip `advisory` → `reject` shipped on 2026-04-24 as a config-default change with its own release note. Post-v1.0, a flip of that shape MUST use the same window: ship the new value as an opt-in in v(N), document the flip in the release notes, then flip the default in v(N+2).

---

## 4. Version support matrix

Which server versions support which client versions. Populated at launch (v1.0); the row below is illustrative — the protocol is pre-v1 (`draft-2026-06-12`) today:

| Server `PROTOCOL_SCHEMA_VERSION` | Min client | Max client | Status                   | EOL date |
| -------------------------------- | ---------- | ---------- | ------------------------ | -------- |
| _`1.0.x`_                        | _`1.0.0`_  | _`1.0.x`_  | _current (illustrative)_ | —        |

### Column meanings

- **Server version** — the `PROTOCOL_SCHEMA_VERSION` the server emits in the subscribe-ack payload's `serverVersion`.
- **Min client / Max client** — the lowest / highest entries in the client's `CLIENT_SUPPORTED_VERSIONS` set guaranteed to subscribe successfully.
- **Status** — one of:
  - `current` — actively maintained; all fixes land here.
  - `security-only` — no new features; only security patches.
  - `EOL` — no longer maintained. Servers on EOL versions MAY refuse to start.
- **EOL date** — the date `current` transitions to `security-only`, or `security-only` transitions to EOL.

### Matrix update cadence

- New **minor** ship → append a row; prior row may update Max client.
- New **major** ship → append a new row; prior-major row transitions to `security-only` for at least 6 months before EOL.
- Security-only → EOL → at least 6 additional months.

### Out-of-matrix clients

If a client targets a version outside the matrix, the server emits `UPGRADE_REQUIRED`. Whether the connection stays open depends on the server's `versionPolicy` at the time.

---

## 5. Migration-guide requirement

Every major bump MUST ship a migration doc at `docs/protocol/migrations/v<N>-to-v<N+1>.md`. (Current pre-v1 drafts use date-named migration docs — e.g. `2026-06-05-gguisession-reintroduction.md`; the `v<N>-to-v<N+1>.md` pattern applies from v1.0.) The CI check (§7) enforces the file's existence; content follows a standard template with required sections:

- **What changed** — 1–2 paragraph summary.
- **Why** — motivation (conformance-kit failure mode that drove the change).
- **Breaking-change summary** — bullet list of renamed / removed / tightened surfaces.
- **Migration steps per consumer kind** — fixture authors, ConformanceHost implementers, agent builders, SDK consumers.
- **Timeline** — dates for v(N) deprecation ship, v(N+1) soft cut, v(N+2) removal.
- **Rollback procedure** — how to pin to prior version if a consumer can't migrate fast enough.

---

## 6. Release cadence expectations

These are targets, not promises:

- **Minor (N.x → N.(x+1)):** roughly monthly. Additive adjustments driven by conformance-kit fixture gaps, new observability events, or new canonical error codes tend to accumulate on this cadence. Empty minor cycles are skipped rather than force-shipped.
- **Major (N → N+1):** annual or slower. Rare and pre-announced. A major ship is a disruption event for every downstream — the protocol leans hard on minor + deprecation windows to defer majors.
- **Patch (N.x.y → N.x.(y+1)):** ad hoc. Doc corrections, fixture additions that tighten existing assertions, and first-party impl bug fixes ship when ready.

Announcements ship in the version history maintained in [`@ggui-ai/protocol`'s `version.ts`](https://github.com/ggui-ai/ggui/blob/main/packages/protocol/src/version.ts) and in the public protocol release notes.

---

## 7. CI enforcement

Two workflows guard this policy:

- **Spec drift** — ensures spec envelope code blocks match the `@ggui-ai/protocol` types. Catches structural drift that would otherwise ship as silent breakage.
- **Version migration** — reads `PROTOCOL_SCHEMA_VERSION` on the PR head and on the base ref; if the **major** component changed, asserts the migration doc exists in the PR diff.

The second check is the policy's teeth: you cannot ship a major bump without the migration doc the policy requires.

---

## Appendix: Worked examples

### A.1 Adding a new live-channel error code

**Scenario:** a new failure mode — `TOOL_DENIED` — needs to land alongside the existing canonical literals on the WS `error` / `channel_error` frame.

- **Bump kind:** minor. Prior-version consumers that don't know `TOOL_DENIED` see an extensibly-closed (`code: string`) value and MUST degrade gracefully. No kit fixture asserting the prior closed set should fail.
- **Required work:** add the constant to `@ggui-ai/protocol`, document it as a canonical live-channel error-code literal, add a conformance-kit fixture asserting the new code round-trips.
- **Migration doc:** none needed (not a major bump).

### A.2 Removing a canonical error-code literal

**Scenario:** `SCHEMA_MISMATCH_ERROR` is merged into `CONTRACT_VIOLATION` with a structured `causedBy`.

- **Bump kind:** major. A consumer that pattern-matches on `SCHEMA_MISMATCH_ERROR` now never sees it, fails its fixture.
- **Required work:** first ship `@deprecated SCHEMA_MISMATCH_ERROR` in v(N), continue emitting both `SCHEMA_MISMATCH_ERROR` and the new shape during v(N+1), then remove `SCHEMA_MISMATCH_ERROR` emission in v(N+2).
- **Migration doc:** `vN-to-v(N+1).md` (enforced by CI on the major-bump PR). Covers the `causedBy` shape, fixture changes, and consumer-side pattern-match migration.

### A.3 Renaming a reserved channel

**Scenario:** `_ggui:preview` → `_ggui:assembly` for parity with transport-layer conventions.

- **Bump kind:** major. Every consumer that subscribes to the old name now fails. Every fixture that references the old name needs updating.
- **Required work:** can NOT ship via deprecation (reserved-channel names are matched verbatim). Must bundle with other breaking changes into the next major. Migration doc is non-trivial — every producer AND consumer changes.
- **Operational note:** changes of this shape are exactly why the protocol prefers extensibly-closed unions + additive fields + separate channels over in-place renames.

### A.4 Clarifying a spec MUST

**Scenario:** the spec's `refresh` semantics paragraph has two readings; the kit's fixture matched only one.

- **Bump kind:** patch IF the kit's existing assertion already covered the intended reading (prose clarifies code); minor IF the clarification surfaces a new required behavior that prior-version impls weren't necessarily honoring (new assertion); major IF the clarification tightens behavior such that previously-conformant impls now fail.
- **Test:** run the kit against the prior-version tag. If it passes, the clarification is patch/minor. If it fails, it was major all along — the original spec text was ambiguous and shipping the clarification is a breaking change.

---

## See also

- [Protocol overview](/protocol/overview/) — three-channel topology and reference implementation.
- [Conformance kit](https://github.com/ggui-ai/ggui/tree/main/packages/protocol-conformance) — the test suite this policy treats as the arbiter.
- [Conformance](/protocol/conformance/) — the 4-criteria contract bar + 6-criteria protocol bar this policy gates.