Files
openclaw/docs/gateway/external-apps.md
Peter Steinberger 61ab6a8f9d feat(gateway): make suspend/resume operator-usable end to end (#122100)
* feat(gateway): make suspend/resume operator-usable end to end

A prepared Gateway now accepts authenticated WebSocket connects while
keeping every method except gateway.suspend.* fenced, so a fresh CLI or
controller process can resume a suspension instead of dead-ending on a
rejected upgrade until the two-minute lease expires. Restart drain,
worker ingress, and desktop-observe streams stay fully closed.

The gateway client surfaces non-101 upgrade responses (bounded body
read) as typed retryable errors instead of an opaque 1006 close, and
new openclaw gateway suspend / resume commands drive the whole
handshake, including bounded --wait polling with blocker output.

Live-verified on an isolated dev gateway: prepare, SIGSTOP/SIGCONT
freeze, resume, over-TTL expiry self-heal, conflict and mismatch paths.

* refactor(gateway-client): move wire-client contract types to protocol-client-contract

The connectError addition pushed protocol-client.ts over the 700-line
max-lines gate; split the adapter-facing contract types into their own
module instead of suppressing.

* refactor(gateway-client): keep contract-internal option types unexported

Knip deadcode gates reject exported types with no importer; the connect
and close decision shapes are only referenced inside the contract module.

* chore(plugin-sdk): refresh gateway-runtime API baseline after rebase

* fix(gateway-client): preserve hello type after rebase

* test(gateway): support websocket upgrade rejection events

* test(gateway): expect connection errors in close info

* fix(gateway): keep prepared-suspension connects control-only

Address ClawSweeper review: node and worker connects stay refused while
suspension is prepared (only operator control connects pass), and the
CLI never issues another suspend prepare after its --wait deadline.
2026-08-11 22:12:53 -07:00

200 lines
10 KiB
Markdown

---
summary: "Current integration path for external apps, scripts, dashboards, CI jobs, and IDE extensions"
title: "Gateway integrations for external apps"
sidebarTitle: "External apps"
read_when:
- You are building an external app, script, dashboard, CI job, or IDE extension that talks to OpenClaw
- You are choosing between Gateway RPC and the Plugin SDK
- You are integrating with Gateway agent runs, sessions, events, approvals, models, or tools
- You are pairing a hosting controller with an external wake scheduler
---
External apps talk to OpenClaw through the Gateway protocol: WebSocket
transport plus RPC methods. Use it when a script, dashboard, CI job, IDE
extension, or another process wants to start agent runs, stream events, wait
for results, cancel work, or inspect Gateway resources.
<Note>
For npm packages, device pairing, reconnect recovery, history, subscriptions,
and approvals, start with
[Building a Gateway client](https://docs.openclaw.ai/gateway/clients). If your
app supervises the Gateway as a child process, also read
[Embedding OpenClaw](https://docs.openclaw.ai/gateway/embedding). During the
initial package rollout, npm may return `E404` until the first package-bearing
OpenClaw release is published.
</Note>
<Note>
This page is for code outside the OpenClaw process. Plugin code that runs
inside OpenClaw should use documented `openclaw/plugin-sdk/*` subpaths instead.
</Note>
## What is available today
| Surface | Status | Use it for |
| ---------------------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------- |
| [Gateway client guide](https://docs.openclaw.ai/gateway/clients) | Release train | npm packages, auth, reconnect, history, events, approvals, and version policy. |
| [Embedding guide](https://docs.openclaw.ai/gateway/embedding) | Release train | Child-process environment, readiness, lifecycle, recovery, RPC ownership, and packaging. |
| [Gateway protocol](/gateway/protocol) | Ready | WebSocket transport, connect handshake, auth scopes, protocol versioning, and events. |
| [Gateway RPC reference](/reference/rpc) | Ready | Current Gateway methods for agents, sessions, tasks, models, tools, artifacts, and approvals. |
| [`openclaw agent`](/cli/agent) | Ready | One-shot script integration when shelling out to the CLI is enough. |
| [`openclaw message`](/cli/message) | Ready | Sending messages or channel actions from scripts. |
## Recommended path
1. Run or discover a Gateway.
2. Connect over the [Gateway protocol](/gateway/protocol).
3. Call documented RPC methods from [Gateway RPC reference](/reference/rpc).
4. Pin the OpenClaw version you test against.
5. Recheck the RPC reference when upgrading OpenClaw.
For agent runs, start with the `agent` RPC and pair it with `agent.wait` for a
terminal result. For durable conversation state, use the `sessions.*` methods.
For UI integrations, subscribe to Gateway events and render only the event
families your app understands.
## Cooperative host suspension
Hosting controllers that freeze or snapshot a running process can use the
host-neutral suspension handshake:
1. Stop admitting external ingress controlled by the host.
2. Call `gateway.suspend.prepare` with a stable, unique `requestId`.
3. If the response is `busy`, keep the process running and retry later.
4. If it is `ready`, save the returned `suspensionId`, then freeze or snapshot
the process before `expiresAtMs`.
5. After thaw, or if suspension is abandoned, call `gateway.suspend.resume`
with that `suspensionId` over the existing or a newly authenticated
WebSocket. The CLI equivalents are `openclaw gateway suspend` and
`openclaw gateway resume <suspensionId>`.
A prepared Gateway accepts authenticated WebSocket connects, but fences every
method except `gateway.suspend.*`. Controllers may reconnect after thaw and
call resume. The [Admin HTTP RPC plugin](/plugins/admin-http-rpc) remains
available for hosts that cannot speak WebSocket at all. If every control path
is lost, the two-minute lease expiry reopens admission automatically.
The RPC contract is:
- `gateway.suspend.prepare``operator.admin`; params
`{ "requestId": "stable-host-operation-id", "terminalPolicy": "preserve" }`
- `gateway.suspend.status``operator.read`; params
`{ "suspensionId": "id-from-prepare" }`
- `gateway.suspend.resume``operator.admin`; params
`{ "suspensionId": "id-from-prepare" }`
`terminalPolicy` is optional and accepts only `"preserve"` or `"terminate"`.
Omitting it defaults to `"preserve"`, so open terminal sessions block normal
host suspension. A caller preparing an update that will terminate the Gateway
may explicitly use `"terminate"`; this ignores open process-local terminal
sessions only. Terminal persistence activity and all other tracked work still
block preparation.
IDs are trimmed, must contain a non-whitespace character, and are limited to
128 characters. A busy prepare result has `status: "busy"`, `reason`,
`retryAfterMs`, `activeCount`, and `blockers`. A ready result has this shape:
```json
{
"status": "ready",
"suspensionId": "2c3f...",
"expiresAtMs": 1770000000000,
"activeCount": 0,
"blockers": []
}
```
Status returns `{"status":"running"}` or a ready result with `expiresAtMs`.
Resume returns `{"ok":true,"status":"running","resumed":true}`; repeating it
after a successful resume returns `resumed: false`.
A competing request ID or transient scheduler-resume failure returns retryable
`UNAVAILABLE` with `retryAfterMs`. During scheduler recovery, prepare, status,
and resume all return that error, the Gateway remains not-ready and
fail-closed, and the host must not freeze or snapshot it. OpenClaw retries the
scheduler automatically and reopens admission only after recovery succeeds. A
mismatched resume ID returns `INVALID_REQUEST`. Prepare shares the Gateway's
control-plane write budget of three attempts per minute; honor the returned
retry delay. WebSocket clients are bucketed by device and IP. Admin HTTP
controllers are bucketed by resolved client IP, so controllers behind one
proxy can share a budget.
Preparation is refuse-only: OpenClaw closes new root/session/command admission,
pauses automatic cron ticks, and inspects work synchronously. If anything is
active, it resumes the scheduler and reopens admission before returning
`busy`; it does not interrupt or drain that work. A ready lease lasts two
minutes. Repeating `prepare` with the same `requestId` renews it; expiry resumes
the scheduler before reopening admission.
Restart emission that becomes due during a ready lease waits until the lease
resumes; an in-flight restart makes preparation return `busy`.
While ready, `/healthz` remains live and `/readyz` returns `503`. Local or
authenticated readiness responses include `gateway-draining`; unauthenticated
remote probes receive only `{ "ready": false }`. The HTTP health probe,
suspension methods on existing WebSocket connections, and an already-enabled
Admin HTTP RPC route remain available. Other RPCs return retryable
`UNAVAILABLE`. Built-in HTTP user-work routes and ordinary plugin HTTP routes,
including OpenAI-compatible APIs, tool/session operations, node watches, and
configured hooks, return `503` with `error.code: "gateway_unavailable"`. New
plugin-owned WebSocket upgrades also return `503`; this covers upgrade
ownership, not work performed later over an established plugin socket.
This handshake does not persist incoming messages, stop third-party channel
transports, or control the hosting platform. The host must fence its ingress
before preparation and remains responsible for wake, snapshot/freeze, and
stop. `activeCount` is the aggregate tracked-work count, while `blockers`
contains the non-zero category counts and bounded task details. This is not a
general process-quiescence barrier. A `background-exec` blocker is aggregate
only: command text, process IDs, output, and session or scope identifiers never
cross the protocol. Channel health, maintenance, cache refresh, established
plugin WebSocket sessions, and unregistered plugin-owned background work can
remain active.
The hosting platform must freeze or snapshot the full process tree and its
filesystem consistently; unregistered work cannot be proven idle by this first
contract.
<Tip>
For host wake scheduling, keep the OpenClaw-facing part in an in-process
plugin and project idempotent full snapshots to the external host adapter.
The hosting controller should not import the Plugin SDK or reconstruct cron
state from event deltas. See [Safe external cron
projection](/plugins/hooks#safe-external-cron-projection).
</Tip>
## App code vs plugin code
Use Gateway RPC when code lives outside OpenClaw:
- Node scripts that start or observe agent runs
- CI jobs that call a Gateway
- dashboards and admin panels
- IDE extensions
- external bridges that do not need to become channel plugins
- integration tests with fake or real Gateway transports
Use the Plugin SDK when code runs inside OpenClaw:
- provider plugins
- channel plugins
- tool or lifecycle hooks
- agent harness plugins
- trusted runtime helpers
External apps should not import `openclaw/plugin-sdk/*`; those subpaths are for
plugins loaded by OpenClaw.
## Related
- [Building a Gateway client](https://docs.openclaw.ai/gateway/clients)
- [Embedding OpenClaw](https://docs.openclaw.ai/gateway/embedding)
- [Gateway protocol](/gateway/protocol)
- [Gateway RPC reference](/reference/rpc)
- [CLI agent command](/cli/agent)
- [CLI message command](/cli/message)
- [Agent loop](/concepts/agent-loop)
- [Agent runtimes](/concepts/agent-runtimes)
- [Sessions](/concepts/session)
- [Background tasks](/automation/tasks)
- [ACP agents](/tools/acp-agents)
- [Plugin SDK overview](/plugins/sdk-overview)