Gateway
Gateway protocol session control
The session control RPC family: session listing and filtering, message send and stream, run lifecycle, and session maintenance.
Session control
-
sessions.listreturns the current session index, including per-rowagentRuntimemetadata when an agent runtime backend is configured.hasActiveRunis the authoritative aggregate direct-session activity fact. When projected,activeRunIdsis the complete exact active set; an empty array proves the session is idle. If aggregate activity is true while the field is omitted, another runtime owner is active but its exact identities are unavailable. Snapshot omission means identities unavailable. On incremental events, omission means no change,nullis the event-only tombstone that clears cached exact IDs to unavailable, and an array replaces the cache. Clients correlate only exact IDs they own locally or received from requests, history, or events and never select the first list entry as an owner. When cloud-worker placement is enabled or durable recovery state exists, session rows also include a closedplacementstate (local,requested,provisioning,syncing,starting,active,draining,reconciling,reclaimed, orfailed) plus state-specific environment, owner-epoch, workspace, bundle, ACK-cursor, or recovery fields. Active placements may include an advisorydiskSpacesample withstatus(ok,warning, orcritical),availableBytes,totalBytes, andobservedAtMs. An active paired-device placement also includesrunner: { kind: "device", status: "available" | "offline", deviceId? };deviceIdnames the paired device hosting the placement (the selected host forautoDevicedispatch), and non-device placements omit the field. This availability is process-current, derived from the exact active environment binding and reconnect-scoped node-runner proof, and starts offline after Gateway restart until that runner reconnects. Inventory changes emitsessions.changedso clients refresh the canonical row. Rows carry ownership projections — write-oncecreatedActor, the mutableowner(actor plusassignedBy/assignedAt), a boundedparticipantslist (owner excluded, up to 4 actors), and the fullparticipantCount; actor display labels and avatars are resolved from current profiles and agent identities at read time. PasscreatorIdto filter by immutablecreatedActor.id; passownerIdto filter by the current assignable owner, falling back tocreatedActorwhen no owner is assigned. The completeownersfacet is independent of pagination and remains unfiltered by either query, so clients can render the full owner picker. Authenticated callers can passinvolvingMe: trueto keep only sessions the caller owns or has prompted, evaluated against the full participant history (profile-backed human participants only). -
sessions.subscribeenables session change events for the current WebSocket client and accepts the same parameters assessions.listto return an initial list in the same response. Empty{}parameters return only the subscription acknowledgment. The subscription ends when that client disconnects. See Session list bootstrap. -
sessions.messages.subscribeandsessions.messages.unsubscribetoggle transcript/message event subscriptions for one session. PassincludeApprovals: trueto also receive sanitizedsession.approvallifecycle events for approvals whose persisted audience includes that exact session and whose reviewer binding authorizes the subscribing client. The subscribe response then includes a bounded pendingapprovalReplay; it is authoritative whentruncatedis false. The opt-in is per subscribe call, not sticky: re-subscribing to the same session withoutincludeApprovals: trueremoves an existing approval subscription. In addition to normal session-read authority, this opt-in requiresoperator.admin, oroperator.approvalson a paired device. -
sessions.previewreturns bounded transcript previews for specific session keys. -
sessions.describereturns one gateway session row for an exact session key. -
sessions.github.options,sessions.github.publish,sessions.github.status, andsessions.github.confirmaccept optionalagentIdalongsidesessionKey. Carry the selected session's agent through all four calls, especially for the shared keyglobal, which does not identify its owner. An explicit agent must be configured and match any agent-qualified session key; malformed, unknown, or conflicting owners returnINVALID_REQUESTbefore publication. Tool-originated publication remains bound to the tool caller's session and agent. -
sessions.resolveresolves or canonicalizes a session target by key, raw session ID, label, Control UI short ID, orreference: { key, slug? }. A reference searches visible active and archived sessions: its exact canonical key wins, then an optional display-name slug is matched against UUID-backed sessions. Reference discovery retains session-list visibility rules; the separatekeyselector retains exact-key read semantics. Ambiguous references and short IDs return at most ten candidates as a successful RPC result. SetallowMissing: trueto receive{ ok: false }when no session matches. -
sessions.createcreates a new session entry. When sandbox containment applies, localcwdand project paths are checked against the selected agent's canonical workspace: aliases inside it are accepted, and symlinks resolving outside it are rejected. Optionalmodel,contextWindow, andthinkingLevelvalues persist the initial model, advertised context-window choice, and reasoning overrides atomically; optionalcategoryassigns the session to a custom group and registers that group when first used.worktree: trueprovisions a managed worktree; optionalworktreeBaseRef/worktreeNameselect the base ref and branch name, andexecNode(operator.admin) binds session exec to a node host. WithoutworktreeName, OpenClaw derives a readable name from the session label or generated first-message title, then falls back to a crustacean-themed name; names already occupied by another owner, local branch, or unmanaged path receive a numeric suffix. The created worktree is echoed in the result and persisted on the session row (worktree: { id, branch, repoRoot }). When the entry is created but its nested initialchat.sendis rejected, the successful result includesrunStarted: falseandrunError; clients can preserve the prompt and retry against the returned session key. A caller that passesparentSessionKeywithemitCommandHooks: trueshould also declare the lifecycle disposition of a distinct child:succeedsParent: trueends the parent withsession_end, whilefalsekeeps the parent active and emits only the child'ssession_start. OmittingsucceedsParentpreserves the legacy parent-rollover behavior for existing clients. The disposition requires both parent linkage and command hooks; a fork cannot succeed its parent. Main-session reset-in-place behavior is unchanged because no distinct child is created. New rows are stamped with write-once creation provenance (createdVia,createdActor,createdAt) from the trusted creation seam; adopting an existing key never restamps it. For human profile actors,createdActor.labelis resolved from the current user profile when the row is projected and is never stored on the session entry, so profile renames do not drift. Session rows also carryparentSessionKey(navigation parent, persisted),controlOwnerSessionKey(runtime controller when live),forkSource(exact source key + transcript generation for forks), andpreviousSessionId(prior transcript generation under the same key). -
sessions.dispatchmoves an authorized local OpenClaw or Codex session with a live, registry-owned session managed worktree to a paired device or configured cloud profile. Pass{ key, deviceId, agentId? }for an explicit device,{ key, autoDevice: true, agentId? }for automatic paired-device selection,{ key, profileId, machineClass?, agentId? }for an explicit profile, or{ key, agentId? }to look up the managed worktree's normalized origin incloudWorkers.projectProfiles. These target modes are mutually exclusive and explicit targets take precedence over project-profile lookup. Automatic selection ranks worker-slot runtimes by available slots and then device ID; runtimes without worker slots use device ID order. If a candidate becomes ineligible during dispatch, up to three ranked candidates are attempted; other errors are not retried. Explicit and automatic device dispatch requireoperator.write; explicit-profile and project-profile dispatch requireoperator.admin. A missing origin, unmatched mapping, or mapping to an unconfigured profile returns a typedINVALID_REQUESTwithout provisioning or falling back to another target. Malformed params use the write scope before schema validation. A missing cloud profile hides only cloud targets; eligible paired-device dispatch remains available. Dispatch closes local turn admission before draining active work and returns only after placement reachesactive, with worker-child ownership forworker-turnor Gateway-owned harness execution forremote-exec. Arbitrary plain directories are not dispatchable; after admission, the workspace transport may use manifest mirroring if the managed worktree's Git metadata later becomes unavailable. SSH fallback candidates rotate only for idempotent probes, content-addressed transfers, receipt/lock-guarded artifact installation, convergent managed-worktree mirroring, and tunnel reconnects. Ambiguous unguarded stateful commands fail closed and are not replayed. Dispatch is one-way; worker-to-local pull-back is not part of this RPC. -
sessions.reclaim(operator.write) safely stops a session placement by key. It waits for an in-flight dispatch, drains admitted work, reconciles active workspace changes, and retries pending failed-environment teardown through the placement owner. Callers never need raw environment-destroy authority. -
sessions.movemoves an authorized active session to the Gateway, a paired device, or a configured profile. Gateway and device targets requireoperator.write; profile targets requireoperator.admin; malformed targets use the write scope before schema validation. The caller supplies the exact observed generation, environment, and owner epoch; session authorization and those source facts are revalidated before the move commits. Ordinary moves always reconcile the source. Only a Gateway target may addabandonSource: true, and only when the exact source is a currently offline paired-device placement. That durable decision force-fences and destroys the remote owner, skips remote workspace reconciliation, and continues from the last Gateway-synced state without replay; unsynced files and in-flight work may be lost. Available, unknown, profile, and other-worker sources reject explicit abandonment. -
sessions.groups.list,sessions.groups.put,sessions.groups.rename, andsessions.groups.deletemanage the gateway-owned custom session group catalog (names + display order). The read-scoped list result is intentionally path-free.sessions.groups.defaultsandsessions.groups.updaterequireoperator.writeand read or replace one custom group's optional working-directory and worktree defaults. Non-admin callers can save only directories inside a configured agent workspace; other absolute Gateway paths requireoperator.admin. Membership stays on each session'scategoryfield; rename and delete update member sessions server-side.sessions.groups.putreplaces only the name list and order, and rejects dropping a group that still has member sessions — delete it explicitly first. Dropping a group participates in the same member-session authorization as delete. -
sessions.sendsends a message into an existing session. -
sessions.steeris a deprecated alias forchat.sendwithqueueMode: "interrupt"; removal follows the protocol deprecation policy. -
sessions.abortaborts active work for a session. Passkeyplus optionalrunId, orrunIdalone for active runs the gateway can resolve to a session. SupplyingrunIdkeeps cancellation scoped to that run. SetclearQueued: trueon a key-only non-global request to also discard followup and lane queues owned by that session. Existing callers that omitclearQueuedpreserve those queues. The literalglobalkey keeps the existing agent-qualifiedchat.abortownership rules and does not perform non-global followup or lane cleanup. -
sessions.patchupdates session metadata/overrides and reports the resolved canonical model plus effectiveagentRuntime.contextWindowaccepts only an id advertised by the selected model'scontextWindowsarray;nullrestorescontextWindowDefault. Session organization fields and the per-sessionmodeloverride requireoperator.write; thinking, fast, verbose, trace, reasoning, and other privileged overrides requireoperator.admin. Only an admin model selection can persist as the configured agent default. Archive and restore patches require the caller-observedsessionIdfromsessions.listorsessions.describeasexpectedSessionId; missing or changed targets fail without materializing or mutating a replacement. Witharchived: true, the Gateway protects agent main sessions (includingglobalwhen global scope is configured) and theunknownsentinel; for every other real session it first fences new admission, cancels exact-session active, pending, queued, reply, embedded, and worker work, and waits for admission and runtime terminal-persistence drains before committingarchivedAt. A cancellation, drain, or persistence failure returns retryableUNAVAILABLEand leaves the session unarchived.sessions.patchManycarriesexpectedSessionIdper target, prepares archive targets in input order inside the same batch lifecycle fence, and returns ordered per-target outcomes. Spawn lineage (spawnedBy,spawnedWorkspaceDir,spawnedCwd,spawnDepth,subagentRole,subagentControlScope) is no longer publicly patchable; those facts are written once by trusted creation paths, and requests that still send them are rejected. -
sessions.assignOwner(operator.write) reassigns the session's mutable owner to a person or configured agent ({ key, owner: { type, id } }). It requires an identified caller (authenticated profile or trusted agent identity), authorizes by session visibility, and recordsassignedBy/assignedAton the row'sownerfield. The write-oncecreatedActorand creator-anchored sharing authority are unchanged; see Multi-user mode. -
sessions.reset,sessions.delete, andsessions.compactperform session maintenance. -
sessions.getreturns the full stored session row. -
Chat execution still uses
chat.history,chat.send,chat.abort, andchat.inject. ItssessionInfouses the same aggregatehasActiveRunand optional complete-exactactiveRunIdssemantics assessions.list.chat.historyis display-normalized for UI clients: inline directive tags are stripped from visible text, plain-text tool-call XML payloads (<tool_call>...</tool_call>,<function_call>...</function_call>,<tool_calls>...</tool_calls>,<function_calls>...</function_calls>, and truncated tool-call blocks) and leaked ASCII/full-width model control tokens are stripped, pure silent-token assistant rows (exactNO_REPLY/no_reply) are omitted, and oversized rows can be replaced with placeholders. Tail responses can include an opaquedeltaCursor. Pass it back ascursortochat.historyorchat.startupinstead ofoffsetormessageId. A successful catch-up returns{ kind: "delta", messages, deltaCursor, sessionInfo }; replay eachmessagesentry through the same reducer as a livesession.messagepayload.{ kind: "reset" }means the cursor is invalid, stale, belongs to another session, crossed a reset or compaction, or is too far behind; fetch a normal tail page. Catch-up never returns a partial page or continuation: more than 200 raw events or the 1 MB payload budget resets to a tail fetch. -
chat.message.getis the additive bounded full-message reader for a single visible transcript entry. PasssessionKey, optionalagentIdwhen session selection is agent-scoped, and a transcriptmessageIdpreviously surfaced throughchat.history; the gateway returns the same display-normalized projection without the lightweight history truncation cap when the stored entry is still available and not oversized. -
chat.toolTitlesis deprecated. It validates the existing bounded request shape and returns{ titles: {}, disabled: true }so older clients stop requesting titles. It makes no model calls and does not access the old title cache. Current Control UI clients display descriptions supplied with tool calls automatically. -
chat.sendaccepts one-turnfastMode: "auto"to use fast mode for model calls started before the auto cutoff, then start later retry, fallback, tool-result, or continuation calls without fast mode. The cutoff defaults to 60 seconds (DEFAULT_FAST_MODE_AUTO_ON_SECONDS) and can be configured per model withagents.defaults.models["<provider>/<model>"].params.fastAutoOnSeconds. Achat.sendcaller can pass one-turnfastAutoOnSecondsto override the cutoff for that request. PassqueueMode(steer,followup,collect, orinterrupt) to override the stored queue mode for this request only; explicit Control UI steer actions usequeueMode: "steer". Interrupt mode captures and aborts the session's current admitted turn, waits for that exact owner to settle, then starts the new turn; an idle session starts normally. A steer send targets the selected session's current state: the Gateway atomically injects the message into that session's direct active run, or starts a new turn when the session is idle. Activity in descendant subagent sessions never makes the selected session busy for this decision.expectedLeafEntryIdis an independent transcript-branch compare-and-swap for non-steer interactive sends: pass the displayed branch leaf (or deliberatenullfor an authoritative empty transcript) and the send rejects withdetails.reason: "active-leaf-changed"if another client switched transcript branches first; steer sends ignore it. -
chat.send,sessions.send, and initial-turnsessions.createacknowledgments report admission separately from transcript persistence. OptionalmessageSeqis the one-based position from an actual committed user-turn receipt; it is absent while the input exists only in pending custody.status: "started"andrunStarted: truealone do not establish a transcript row. Reconcile provisional input by its submission identity against accepted custody or canonical transcript identity, never a predicted position or matching content. -
sessions.create.fastModeacceptstrue,false, or"auto"and persists that speed override before the initial turn starts. -
sessions.title.prepare({ agentId, message, model?, catalogId?, incognito? },operator.write, rate-limited as a control-plane write) returns{ title }from the selected agent's utility model only, without creating or renaming a session; it returnstitle: nullfor incognito, empty, slash-command, or unavailable-utility input and never falls back to the primary model. A client passes a ready result assessions.create.displayName: a presentation title stored like a generated first-message title, so it is not unique, never claimslabel, and is ignored when adopting an existing key.