Skip to main content
The session control RPC family: session listing and filtering, message send and stream, run lifecycle, and session maintenance.

Session control

  • sessions.list returns the current session index, including per-row agentRuntime metadata when an agent runtime backend is configured. hasActiveRun is the authoritative aggregate direct-session activity fact. When projected, activeRunIds is 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, null is 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 closed placement state (local, requested, provisioning, syncing, starting, active, draining, reconciling, reclaimed, or failed) plus state-specific environment, owner-epoch, workspace, bundle, ACK-cursor, or recovery fields. Active placements may include an advisory diskSpace sample with status (ok, warning, or critical), availableBytes, totalBytes, and observedAtMs. An active paired-device placement also includes runner: { kind: "device", status: "available" | "offline", deviceId? }; deviceId names the paired device hosting the placement (the selected host for autoDevice dispatch), 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 emit sessions.changed so clients refresh the canonical row. Rows carry ownership projections — write-once createdActor, the mutable owner (actor plus assignedBy/assignedAt), a bounded participants list (owner excluded, up to 4 actors), and the full participantCount; actor display labels and avatars are resolved from current profiles and agent identities at read time. Pass creatorId to filter by immutable createdActor.id; pass ownerId to filter by the current assignable owner, falling back to createdActor when no owner is assigned. The complete owners facet is independent of pagination and remains unfiltered by either query, so clients can render the full owner picker. Authenticated callers can pass involvingMe: true to keep only sessions the caller owns or has prompted, evaluated against the full participant history (profile-backed human participants only).
  • sessions.subscribe enables session change events for the current WebSocket client and accepts the same parameters as sessions.list to 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.subscribe and sessions.messages.unsubscribe toggle transcript/message event subscriptions for one session. Pass includeApprovals: true to also receive sanitized session.approval lifecycle 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 pending approvalReplay; it is authoritative when truncated is false. The opt-in is per subscribe call, not sticky: re-subscribing to the same session without includeApprovals: true removes an existing approval subscription. In addition to normal session-read authority, this opt-in requires operator.admin, or operator.approvals on a paired device.
  • sessions.preview returns bounded transcript previews for specific session keys.
  • sessions.describe returns one gateway session row for an exact session key.
  • sessions.github.options, sessions.github.publish, sessions.github.status, and sessions.github.confirm accept optional agentId alongside sessionKey. Carry the selected session’s agent through all four calls, especially for the shared key global, which does not identify its owner. An explicit agent must be configured and match any agent-qualified session key; malformed, unknown, or conflicting owners return INVALID_REQUEST before publication. Tool-originated publication remains bound to the tool caller’s session and agent.
  • sessions.resolve resolves or canonicalizes a session target by key, raw session ID, label, Control UI short ID, or reference: { 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 separate key selector retains exact-key read semantics. Ambiguous references and short IDs return at most ten candidates as a successful RPC result. Set allowMissing: true to receive { ok: false } when no session matches.
  • sessions.create creates a new session entry. When sandbox containment applies, local cwd and project paths are checked against the selected agent’s canonical workspace: aliases inside it are accepted, and symlinks resolving outside it are rejected. Optional model, contextWindow, and thinkingLevel values persist the initial model, advertised context-window choice, and reasoning overrides atomically; optional category assigns the session to a custom group and registers that group when first used. worktree: true provisions a managed worktree; optional worktreeBaseRef/worktreeName select the base ref and branch name, and execNode (operator.admin) binds session exec to a node host. Without worktreeName, 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 initial chat.send is rejected, the successful result includes runStarted: false and runError; clients can preserve the prompt and retry against the returned session key. A caller that passes parentSessionKey with emitCommandHooks: true should also declare the lifecycle disposition of a distinct child: succeedsParent: true ends the parent with session_end, while false keeps the parent active and emits only the child’s session_start. Omitting succeedsParent preserves 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.label is 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 carry parentSessionKey (navigation parent, persisted), controlOwnerSessionKey (runtime controller when live), forkSource (exact source key + transcript generation for forks), and previousSessionId (prior transcript generation under the same key).
  • sessions.dispatch moves 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 in cloudWorkers.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 require operator.write; explicit-profile and project-profile dispatch require operator.admin. A missing origin, unmatched mapping, or mapping to an unconfigured profile returns a typed INVALID_REQUEST without 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 reaches active, with worker-child ownership for worker-turn or Gateway-owned harness execution for remote-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.move moves an authorized active session to the Gateway, a paired device, or a configured profile. Gateway and device targets require operator.write; profile targets require operator.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 add abandonSource: 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, and sessions.groups.delete manage the gateway-owned custom session group catalog (names + display order). The read-scoped list result is intentionally path-free. sessions.groups.defaults and sessions.groups.update require operator.write and 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 require operator.admin. Membership stays on each session’s category field; rename and delete update member sessions server-side. sessions.groups.put replaces 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.send sends a message into an existing session.
  • sessions.steer is a deprecated alias for chat.send with queueMode: "interrupt"; removal follows the protocol deprecation policy.
  • sessions.abort aborts active work for a session. Pass key plus optional runId, or runId alone for active runs the gateway can resolve to a session. Supplying runId keeps cancellation scoped to that run. Set clearQueued: true on a key-only non-global request to also discard followup and lane queues owned by that session. Existing callers that omit clearQueued preserve those queues. The literal global key keeps the existing agent-qualified chat.abort ownership rules and does not perform non-global followup or lane cleanup.
  • sessions.patch updates session metadata/overrides and reports the resolved canonical model plus effective agentRuntime. contextWindow accepts only an id advertised by the selected model’s contextWindows array; null restores contextWindowDefault. Session organization fields and the per-session model override require operator.write; thinking, fast, verbose, trace, reasoning, and other privileged overrides require operator.admin. Only an admin model selection can persist as the configured agent default. Archive and restore patches require the caller-observed sessionId from sessions.list or sessions.describe as expectedSessionId; missing or changed targets fail without materializing or mutating a replacement. With archived: true, the Gateway protects agent main sessions (including global when global scope is configured) and the unknown sentinel; 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 committing archivedAt. A cancellation, drain, or persistence failure returns retryable UNAVAILABLE and leaves the session unarchived. sessions.patchMany carries expectedSessionId per 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 records assignedBy/assignedAt on the row’s owner field. The write-once createdActor and creator-anchored sharing authority are unchanged; see Multi-user mode.
  • sessions.reset, sessions.delete, and sessions.compact perform session maintenance.
  • sessions.get returns the full stored session row.
  • Chat execution still uses chat.history, chat.send, chat.abort, and chat.inject. Its sessionInfo uses the same aggregate hasActiveRun and optional complete-exact activeRunIds semantics as sessions.list. chat.history is 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 (exact NO_REPLY / no_reply) are omitted, and oversized rows can be replaced with placeholders. Tail responses can include an opaque deltaCursor. Pass it back as cursor to chat.history or chat.startup instead of offset or messageId. A successful catch-up returns { kind: "delta", messages, deltaCursor, sessionInfo }; replay each messages entry through the same reducer as a live session.message payload. { 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.get is the additive bounded full-message reader for a single visible transcript entry. Pass sessionKey, optional agentId when session selection is agent-scoped, and a transcript messageId previously surfaced through chat.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.toolTitles is 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.send accepts one-turn fastMode: "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 with agents.defaults.models["<provider>/<model>"].params.fastAutoOnSeconds. A chat.send caller can pass one-turn fastAutoOnSeconds to override the cutoff for that request. Pass queueMode (steer, followup, collect, or interrupt) to override the stored queue mode for this request only; explicit Control UI steer actions use queueMode: "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. expectedLeafEntryId is an independent transcript-branch compare-and-swap for non-steer interactive sends: pass the displayed branch leaf (or deliberate null for an authoritative empty transcript) and the send rejects with details.reason: "active-leaf-changed" if another client switched transcript branches first; steer sends ignore it.
  • chat.send, sessions.send, and initial-turn sessions.create acknowledgments report admission separately from transcript persistence. Optional messageSeq is the one-based position from an actual committed user-turn receipt; it is absent while the input exists only in pending custody. status: "started" and runStarted: true alone 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.fastMode accepts true, 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 returns title: null for incognito, empty, slash-command, or unavailable-utility input and never falls back to the primary model. A client passes a ready result as sessions.create.displayName: a presentation title stored like a generated first-message title, so it is not unique, never claims label, and is ignored when adopting an existing key.
  • sessions.create.titleSource optionally supplies up to 1,000 characters of the submitted topic when the first turn will be sent separately, such as after cloud dispatch. On a new interactive session without an initial turn, it starts ordinary background title generation without delaying creation or starting a task. Existing names keep precedence; incognito sessions and adoption of an existing session ignore this input. Completion emits sessions.changed with reason chat.title.