snapshotAt, the Gateway’s sampling time in milliseconds since the Unix epoch. Cached rows retain their original sampling time. Clients use it to order read snapshots, including runtime-only changes that do not advance persisted updatedAt; request order breaks equal-time ties. Read observations without sampling metadata retain request-order reconciliation. This read timestamp does not replace event ordering or session-identity checks.
Session control
-
sessions.catalog.listlists external session catalogs. PassmetadataOnly: truewhen a client needs catalog IDs, labels, capabilities, and share-route metadata without enumerating hosts or sessions. This mode returns the normal catalog objects withhosts: []; it retains agent and catalog selection, skips row filtering/pagination, and emits no host progress events. Omit the flag for full listing, including the host availability needed for terminal selection. The Control UI uses metadata-only discovery for the new-session picker and hidden-source labels in Settings. -
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. Worker and terminal placements may includeproviderId,profileId, and an optional closedmachineobject:class(1–128 characters),osandosLabel(1–64 characters each), and integercpuandmemoryGb(1–65,536 each). Every machine field is optional; unknown fields and empty machine summaries are omitted. Class and OS use the selected override or the provider catalog default, and labels and capacity come from that catalog. Before the catalog is available, only known class and OS overrides appear. Local and requested placements never carry machine identity. 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.listacceptssortBy: "activity"for newest activity first, without pin priority. Its timestamp is the later of the last completed run (lastActivityAt) and last user/channel input (lastInteractionAt). Sessions without either valid activity timestamp fall back toupdatedAt, thencreatedAt; missing timestamps sort last. With this mode,activeMinutesuses the same timestamp. Filtering and sorting happen before pagination, with session keys breaking timestamp ties. The defaultupdatedAtorder keeps pins first and filters by metadata update age;lastInteractionAtretains its input-only ordering. -
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. A cold transcript returnsstatus: "cold"and an emptyitemsarray. Bulk preview requests keep archived payloads in cold storage; opening chat or requesting history restores them. Clients should show an archived-history placeholder for this status. -
sessions.storage.statusreturns per-agent transcript counts, database and archive sizes, and background maintenance status (operator.admin). -
sessions.storage.runstarts or joins a background archival batch using the applied policy and returns promptly (operator.admin). Followsessions.storage.statuswhilemaintenance.runningis true. The batch continues if the requesting client disconnects. See cold transcript storage. -
sessions.describereturns one gateway session row for an exact session key, including the current caller’ssharingRoleas reported by session lists and chat history. -
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. Publication and confirmation report lease acquisition througherror.details.leaseAcquisition:heldreturnsFORBIDDENwith the recorded holder and lease epoch;store-unavailablereturns retryableUNAVAILABLEwith a closedreasoncode (sqlite-busy,lifecycle-busy, orstorage-error); caller cancellation returns non-retryableUNAVAILABLEwithaborted, the caller-signal reason, and elapsed time. SQLite owns bounded write admission. Retry the same publication idempotency key or confirmation request after a reported store failure clears; acquisition failure does not start publication effects. A workspace already occupied by a turn or reconciliation remains retryableUNAVAILABLE. -
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 first ranks worker-slot runtimes by admitted work relative to worker capacity, then by free slots after accounting for pending dispatches, and finally by device ID; runtimes without worker slots use device ID order. Only host eligibility failures detected before workspace preparation begins are retried, after any failed allocation is cleaned up, with up to three ranked candidates total. Other errors are returned immediately; workspace preparation and work already started are never replayed. 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-sessionmodel,thinkingLevel, andfastModeoverrides requireoperator.write, including clearing an override withnull. The same field policy applies tosessions.patchMany. Context-window, verbose, trace, reasoning-visibility, tool, and other privileged overrides still requireoperator.admin; combining them with write-scoped fields does not lower that requirement. 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.resetaccepts an optionalexpectedSessionIdfromsessions.listorsessions.describe. If that session ID is no longer current when the reset enters its lifecycle fence, the Gateway rejects the request before interrupting work withINVALID_REQUESTanderror.details.reason: "session-changed"; re-read the session before deciding whether to retry. Omitting the field preserves unconditional reset behavior. The guard does not reject changes that keep the same session ID, including metadata edits or another reset-in-place. -
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, compaction, or branch selection, 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. Anchored reads withsessionIdandmessageIdrequire current sharing access. After deletion removes the logical session entry but retains its transcript, direct history reads requireoperator.admin.tasks.historyseparately validates access to its recorded task and transcript. Access is checked before loading history and again before publishing it. -
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. -
sessions.activitySummary.ensure({ sessions: [{ key, agentId? }] }, 1–20 entries,operator.write) requests bounded background recap generation for sessions the caller may modify. The entire batch is authorized before any generation is queued. It returns{ sessions: [{ key, agentId, activitySummary }] }immediately, withactivitySummary.stateset tocurrent,stale,updating, orunavailableand optional cachedtextandupdatedAt. Generation uses the owning agent’s utility route and retains the previous recap on failure.sessions.changedinvalidates the row as work completes.sessions.listincludes these read-only projections only withincludeActivitySummary: true, adding caller-specificactivitySummary.canEnsure. Clients require that flag andoperator.writebefore requesting generation; accepted ensure responses returncanEnsure: true. This permission flag is not persisted in the recap cache. Ordinary listings do not request model generation. See Activity session recaps for persistence and lifecycle semantics. -
sessions.create.titleSourceoptionally 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 emitssessions.changedwith reasonchat.title.