# Gateway protocol versioning

> Protocol version constants, the N-1 node window, and client defaults

- 网址：https://funcoding.ai/agents/openclaw/gateway/protocol/versioning/
- 来源：OpenClaw 官方文档原文（英文），MIT 许可，同步于 2026-10-11
- 官方原文：https://docs.openclaw.ai/zh-CN/gateway/protocol/versioning

---
Which protocol versions a client may negotiate, and the constants a reference client ships with.

## Versioning

- `PROTOCOL_VERSION`, `MIN_CLIENT_PROTOCOL_VERSION`,
  `MIN_NODE_PROTOCOL_VERSION`, and `MIN_PROBE_PROTOCOL_VERSION` live in
  `packages/gateway-protocol/src/version.ts`.
- Clients send `minProtocol` + `maxProtocol`. Operator and UI clients must
  include the current protocol in that range; current clients and servers run
  protocol v4.
- Authenticated clients with both `role: "node"` and `client.mode: "node"`
  may use the N-1 node protocol (v3). Lightweight restart checks use
  the same N-1 window. Device auth, pairing, scopes, command policy, and exec
  approvals are unchanged by this compatibility window. Plugin-owned node
  capabilities and commands are withheld until the node upgrades to the current
  protocol because their hosted surfaces are not part of the N-1 contract.
- Schemas and models are generated from TypeBox definitions:
  - `pnpm protocol:gen`
  - `pnpm protocol:gen:swift`
  - `pnpm protocol:check`

### Client constants

The reference client implementation lives in `packages/gateway-client/src/`
(OpenClaw wraps it via the thin `src/gateway/client.ts` facade). These
defaults are stable across protocol v4 and are the expected baseline for
third-party clients.

| Constant                                  | Default                                               | Source                                                                                                                    |
| ----------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `PROTOCOL_VERSION`                        | `4`                                                   | `packages/gateway-protocol/src/version.ts`                                                                                |
| `MIN_CLIENT_PROTOCOL_VERSION`             | `4`                                                   | `packages/gateway-protocol/src/version.ts`                                                                                |
| `MIN_NODE_PROTOCOL_VERSION`               | `3`                                                   | `packages/gateway-protocol/src/version.ts`                                                                                |
| `MIN_PROBE_PROTOCOL_VERSION`              | `3`                                                   | `packages/gateway-protocol/src/version.ts`                                                                                |
| Request timeout (per RPC)                 | `30_000` ms                                           | `packages/gateway-client/src/client.ts` (`requestTimeoutMs`)                                                              |
| Preauth / connect-challenge timeout       | `15_000` ms                                           | `packages/gateway-client/src/timeouts.ts` (`OPENCLAW_HANDSHAKE_TIMEOUT_MS` env can raise the paired server/client budget) |
| Initial reconnect backoff                 | `1_000` ms                                            | `packages/gateway-client/src/client.ts` (`GATEWAY_RECONNECT_POLICY`)                                                      |
| Max reconnect backoff                     | `30_000` ms                                           | `packages/gateway-client/src/client.ts` (`GATEWAY_RECONNECT_POLICY`)                                                      |
| Fast-retry clamp after device-token close | `250` ms                                              | `packages/gateway-client/src/client.ts`                                                                                   |
| Force-stop grace before `terminate()`     | `250` ms                                              | `FORCE_STOP_TERMINATE_GRACE_MS`                                                                                           |
| `stopAndWait()` default timeout           | `1_000` ms                                            | `STOP_AND_WAIT_TIMEOUT_MS`                                                                                                |
| Default tick interval (pre `hello-ok`)    | `30_000` ms                                           | `packages/gateway-client/src/client.ts`                                                                                   |
| Tick-timeout close                        | code `4000` when silence exceeds `tickIntervalMs * 2` | `packages/gateway-client/src/client.ts`                                                                                   |
| `MAX_PAYLOAD_BYTES`                       | `25 * 1024 * 1024` (25 MB)                            | `src/gateway/server-constants.ts`                                                                                         |
| Chat attachment ceiling                   | `agents.defaults.mediaMaxMb`, default 20 MB decoded   | `src/gateway/chat-attachment-policy.ts`                                                                                   |
| Chat attachment image ceiling             | `min(attachment ceiling, 6 MB)`                       | `src/gateway/chat-attachment-policy.ts`, `packages/media-core/src/constants.ts`                                           |

The server advertises the effective `policy.tickIntervalMs`,
`policy.maxPayload`, `policy.maxBufferedBytes`, and `policy.attachments` in
`hello-ok`; clients should honor those values rather than the pre-handshake
defaults or hardcoded attachment sizes.

The reference client lets finite requests own their configured deadline when
every pending request has one. An `expectFinal` request without a finite
`timeoutMs`, any request with `timeoutMs: null`, or a mix of finite and
unbounded requests keeps the tick watchdog active. If inbound events and
responses remain silent past the tick-timeout threshold, the client closes the
socket with code `4000`, rejects every pending request, and reconnects. It does
not replay rejected requests after reconnecting.

## Local state owner routing

Supported local administrative commands discover the process that owns their
selected physical state directory before opening mutation-capable state. A live
owner receives authenticated RPCs on its recorded local port; a configured remote
Gateway or URL override does not redirect these local targets. With no owner,
the CLI acquires exclusive lifecycle ownership and retains it until accepted work
and cleanup settle. An unreachable, uninspectable, or still-starting owner is not
evidence that the directory is offline.

These routes require `operator.admin` and `local-state-owner-routing-v1` in
`hello-ok.features.capabilities`. Method presence alone is insufficient. Except
for creation, which is covered by that original capability, each operation also
requires its own capability:

| Local operation                                              | RPC                        | Additional capability                |
| ------------------------------------------------------------ | -------------------------- | ------------------------------------ |
| `worktrees create`                                           | `worktrees.create`         | None                                 |
| `worktrees remove`, including lossless and exact-state modes | `worktrees.remove`         | `worktrees-remove-owner-v1`          |
| `worktrees restore`, including exact-state recovery          | `worktrees.restore`        | `worktrees-restore-owner-v1`         |
| `worktrees gc`                                               | `worktrees.gc`             | `worktrees-gc-owner-v1`              |
| `worktrees recover-removal`                                  | `worktrees.recoverRemoval` | `worktrees-recover-removal-owner-v1` |
| `worktrees retire-snapshot`                                  | `worktrees.retireSnapshot` | `worktrees-retire-snapshot-owner-v1` |
| `pairing list`                                               | `channels.pairing.list`    | `channels-pairing-list-owner-v1`     |
| `pairing approve`                                            | `channels.pairing.approve` | `channels-pairing-approve-owner-v1`  |
| Default `approvals get`                                      | `exec.approvals.get`       | `exec-approvals-get-owner-v1`        |
| Default `approvals set` and allowlist edits                  | `exec.approvals.set`       | `exec-approvals-set-owner-v1`        |

The CLI sends the discovered `expectedOwnerId`; the server checks the current
owner and requester before effects. Worktree creation also carries
`expectedRepoIdentity` as the captured repository directory's `device:inode`.
These fields identify the intended owner and target; they grant no permission.
See [worktree request and result contracts](https://funcoding.ai/agents/openclaw/concepts/managed-worktrees/#gateway-methods),
[channel pairing](https://funcoding.ai/agents/openclaw/gateway/protocol/rpc-system-and-channels/#channel-dm-pairing),
and [approval snapshots](https://funcoding.ai/agents/openclaw/gateway/protocol/rpc-devices-nodes-and-approvals/#approval-families).

### Refusals and uncertain outcomes

The CLI distinguishes `OWNER_UNAVAILABLE` (ownership cannot be established),
`OWNER_REFUSED` (dispatch did not occur or the server explicitly refused before
mutation), and `OUTCOME_UNKNOWN` (a dispatched operation may have taken effect).
A server error with `details.mutationAccepted: false` identifies the explicit
pre-mutation refusal; other errors after dispatch do not prove rollback.

None of these states triggers local fallback or automatic replay. For an older
Gateway, missing capability, or authentication refusal, update the Gateway or
fix authentication. To work offline, stop the Gateway through its service owner,
wait for ownership to release, and rerun the exact command. After an uncertain
reply, inspect the target and its operation-specific status or recovery output
before deciding whether to retry. The local route allows up to ten minutes for
the existing owner operation; a timeout does not transfer ownership.

### Mixed versions and retained boundaries

New clients require the complete capability for their operation even if an older
Gateway advertises the same method. They never drop a selector or guard to make
an old request fit. Existing RPC clients can omit the additive owner fields on
methods that predate routing and retain their existing response contracts. The
new `worktrees.recoverRemoval` and `worktrees.retireSnapshot` methods require
`expectedOwnerId`.

Older CLI and SDK binaries keep their existing direct-write behavior. Routing
does not intercept those writes, arbitrary trusted SQL, or another state root
sharing an external database. Match CLI, Gateway, and SDK versions; callers must
retain their existing foreign-commit freshness checks. Routing covers the named
operations above; it does not guarantee that the Gateway is the only SQLite writer.

The following boundaries retain their existing owners and contracts:

| Boundary                       | Retained behavior                                                                                                                                                                                                                                          |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Device bootstrap               | Implicit-loopback device-pairing recovery, QR/setup-code issuance, and SDK device tokens remain with the bootstrap owner so obtaining RPC credentials does not require those credentials first.                                                            |
| Configuration and agent setup  | Agent identity, bindings, and config editing retain file-authoring/reload behavior. Interactive agent creation and onboarding still stage auth, plugins, workspace, and config locally; their authoritative commit has not moved to this route.            |
| Exec-policy synchronization    | `exec-policy preset` and `exec-policy set` retain their existing local config/policy owner. Routing the `approvals` commands does not change these operations or execution authorization.                                                                  |
| Credentials                    | Model-auth keys/order and SDK auth writes, MCP OAuth, secret-store edits, and channel authentication retain their existing locking, invalidation, and lifecycle owners.                                                                                    |
| Client preferences             | The TUI's remembered session remains a client-preference write, not conversation authority.                                                                                                                                                                |
| Plugin and delivery state      | Plugin keyed stores and dedupe retain plugin ownership; delivery-queue operations retain outbound-owner admission.                                                                                                                                         |
| Session and workspace SDKs     | Session entry, maintenance, catalog/import/link, transcript/callback, and workspace/worktree APIs retain their existing contracts. Session cleanup, agent deletion, and foreign SDK workspace/worktree mutations are not covered by this routing contract. |
| Raw storage APIs               | Raw cron replacement and low-level SQLite SDK/native access are not made exclusive by CLI routing. Prefer domain RPCs where available; a worker in a different process is still a foreign writer.                                                          |
| Offline and maintenance owners | `sandbox recreate` requires exclusive offline ownership. Embedded execution, reset/uninstall, boot admission, migrations, Doctor, and update retain their existing lifecycle owners.                                                                       |

Updates do not require a new routing RPC to install the version that supplies it.
The installed updater and candidate Doctor retain their stop, drain, backup,
maintenance, and finalization contracts. This routing adds no protocol-version,
schema, configuration, retention, or durability change.
