# Gateway protocol session control

> The session control RPC family: listing, sending, streaming, forking, and lifecycle methods

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

---
The session control RPC family: session listing and filtering, message send and stream, run lifecycle, and session maintenance.

Session rows include `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.

Once session stores are admitted, authorization for direct session targets prepares only the requested rows. A dirty target must pass canonical validation before authorization; unrelated rows continue validating in the background. Method scopes, profile bindings, and startup availability are checked again after preparation. `sessions.list` authorizes the caller's scope first and leaves row validity and visibility filtering to the session reader.

`sessions.describe` and `sessions.get` are connection-bound observations. Disconnecting cancels further row-preparation retries, including preparation during authorization, after in-flight preparation settles. Reconnect and issue a new read to obtain a current result. Accepted session mutations retain their existing completion lifetime.

Changing a session's model or native runtime consent can wait for runtime preparation. During that wait, other sessions can still be created or updated. Changes to the same session remain ordered, and a prepared change is rejected if its captured session state changed before commit.

Model-catalog reads in `sessions.create`, `sessions.patch`, `sessions.patchMany`, and Gateway copies through `sessions.catalog.continue` share a 20-second budget per request. A stalled catalog or canceled caller returns retryable `UNAVAILABLE` before the affected session change commits. `sessions.patchMany` retains ordered per-target outcomes; independent successful targets remain committed.

Session list orders update incrementally as committed session metadata changes. Each request still applies current visibility, activity, and time filters before pagination. Archived sessions retain their list position when their display rows are released from memory.

## Session control

- `sessions.catalog.list` lists external session catalogs. Pass `metadataOnly: true` when a client needs catalog IDs, labels, capabilities, and share-route metadata without enumerating hosts or sessions. This mode returns the normal catalog objects with `hosts: []`; 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.catalog.import` (`operator.write`) preserves a readable catalog transcript in an ordinary Gateway session. Pass `{ catalogId, hostId, threadId, agentId?, sourceHomeId?, displayName? }`; caller visibility and provider resolution match catalog reads. Optional `displayName` (1–500 characters) sets only a newly created import's presentation title; an existing import keeps its title. The Control UI and CLI `--all` supply the catalog row name when available; imports without a title use `Imported <catalog label> session`. The closed result is `{ sessionKey, importedItems, totalItems, complete, created }`, with nonnegative integer item counts. `importedItems` counts newly appended source items, `totalItems` counts source items read, and `created` reports whether the destination session was created. Re-importing the same locator for the same agent reuses its session and appends only unseen items. Import retains the most recent history up to 50,000 items or 64 MiB, with the existing per-item text limit; `complete: false` means older history was omitted by a bound. It records source provenance and marks content as untrusted reference material without native continuation, model locks, or node run bindings. See [Import transcripts](https://funcoding.ai/agents/openclaw/nodes/session-catalogs/#import-transcripts).
- `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. Worker and terminal placements may include `providerId`, `profileId`, and an optional closed `machine` object: `class` (1–128 characters), `os` and `osLabel` (1–64 characters each), and integer `cpu` and `memoryGb` (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 advisory `diskSpace` sample with `status` (`ok`, `warning`, or `critical`), `availableBytes`, `totalBytes`, and `observedAtMs`. Provisioning and active placements may include `workerRuntimeInstall` while the Gateway is transferring or the node is installing a new runtime: `phase` (`transferring` or `installing`), `transferredBytes`, `totalBytes`, `startedAtMs`, and `updatedAtMs`. This process-local progress clears when installation settles; progress changes emit `sessions.changed`. 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. Runner availability changes emit keyed `sessions.changed` snapshots only for affected active device placements, so clients update held rows without reloading the session list. Capacity-only inventory changes do not invalidate session rows. 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.list` accepts `excludeDock: true` to omit conversations created with `surface: "plugin-dock"` before ownership and people facets, counts, and pagination. Rows expose `isDock` alongside `createdSurface: "plugin-dock"`. Ordinary Control UI discovery and Workboard rosters use this filter; exact session reads and deep links retain their normal access checks. `sessions.search.scope` accepts the same filter.
- `sessions.list` accepts `includeOwnerSessionCounts: true` to return a complete `ownerSessionCounts` array of `{ profileId, open, running }` for effective human-profile owners. Counts use caller-visible matching sessions before pagination, with merged profiles resolved to their canonical identity. `open` counts unarchived sessions; `running` counts each conversation once when it has direct executing agent-turn work or active delegated subagent descendants, using the same descendant activity projection as session rows. It includes descendant-only work and later turns in retained child sessions, but excludes queued direct-only work and stale stored run status. All list filters still apply. For a sidebar-wide summary, omit `agentId` and time/search/owner filters, exclude global/unknown sentinels, and use `excludeSubagents`, `excludeCron`, `excludeSystem`, and `excludeDock`; a small `limit` does not truncate the counts. Unlike the bounded association-based `people` facet, this array is not capped by the people picker. An absent profile means zero only when the response contains `ownerSessionCounts`; an omitted field does not establish a count. Clients refresh this summary on `sessions.changed`, including keyless visibility/profile invalidations, rather than reconstructing it from paginated session rows or human presence.
- `sessions.list` accepts `sortBy: "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 to `updatedAt`, then `createdAt`; missing timestamps sort last. With this mode, `activeMinutes` uses the same timestamp. Filtering and sorting happen before pagination, with session keys breaking timestamp ties. The default `updatedAt` order keeps pins first and filters by metadata update age; `lastInteractionAt` retains its input-only ordering.
- With `activeMinutes`, `sessions.list` returns `activityExpiresAt` when visible candidates have an age boundary. This epoch-ms deadline includes candidates outside the selected person and returned page, so derived views can expire their people facets too. The boundary is inclusive: reread after `Date.now() > activityExpiresAt`. The field is absent without an age filter or when no candidate can age out; it does not replace session-change invalidation.
- `sessions.list` accepts `activityPulseBoundaries`, 2–64 strictly ascending epoch-ms boundaries computed in the caller's local time zone. The returned `activityPulse.buckets` counts activity in each half-open interval; session, running, and optional distinct-people counts cover the complete filtered result before pagination. `started` counts those sessions created within `activeMinutes` and is omitted when that time filter is absent.
- `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](https://funcoding.ai/agents/openclaw/gateway/protocol/rpc-bootstrap-and-events/#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. Concurrent requests for the same session and reviewer share replay preparation. Approval activity outside that session and reviewer does not invalidate the replay. If visible approval state changes during preparation, the Gateway retries once; another change returns `UNAVAILABLE` and rolls back the provisional subscription so the client can retry. 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. A cold transcript returns `status: "cold"` and an empty `items` array. 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.status` returns per-agent transcript counts, database and archive sizes, and background maintenance status (`operator.admin`).
- `sessions.storage.run` starts or joins a background archival batch using the applied policy and returns promptly (`operator.admin`). Follow `sessions.storage.status` while `maintenance.running` is true. The batch continues if the requesting client disconnects. See [cold transcript storage](https://funcoding.ai/agents/openclaw/reference/session-management-compaction/maintenance/#cold-transcript-storage).
- `sessions.describe` returns one gateway session row for an exact session key, including the current caller's `sharingRole` as reported by session lists and chat history.
- `sessions.branches.list` lists persisted transcript branches independently of checkout availability. Concurrent label and activity updates do not invalidate the listing; changes to session identity or read access still reject the read.
- `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. Publication and confirmation report lease acquisition through `error.details.leaseAcquisition`: `held` returns `FORBIDDEN` with the recorded holder and lease epoch; `store-unavailable` returns retryable `UNAVAILABLE` with a closed `reason` code (`sqlite-busy`, `lifecycle-busy`, or `storage-error`); caller cancellation returns non-retryable `UNAVAILABLE` with `aborted`, 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 retryable `UNAVAILABLE`. `sessions.github.options` has a five-second handler deadline and returns retryable `UNAVAILABLE` on timeout. Retry options discovery; expiration prevents late delivery and further read steps without cancelling an OAuth token rotation already owned by the lifecycle service.
- `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. In fleets with `agents.ownership: "explicit"`, a request with an agent-qualified `parentSessionKey` and no `key` or `agentId` creates the child under the parent’s agent. An explicit child key or `agentId` keeps precedence, and legacy fleets keep their existing default placement. 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 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 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 checks, 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.files.list` and `sessions.files.get` list and preview files for a visible session. In-root files need session read access. Out-of-root reads require both Gateway-host file-tool access for that session and current permission to start a turn there; successful out-of-root previews omit `hash` and remain read-only. `sessions.files.set` retains workspace containment and hash-based compare-and-swap for existing files.
- `sessions.files.assets` accepts `{ sessionKey, agentId?, path, refs }`, where `path` is the HTML file's returned `workspacePath` or `path`, and `refs` contains relative asset URLs resolved from its folder. It uses the same current session read boundary as `sessions.files.get`. Each call accepts at most 64 references of at most 4,096 characters, with a 1 MiB per-asset and 4 MiB aggregate byte cap before base64 encoding. Cloud repository inspection retains its stricter 256 KiB per-file preview cap and stopped-repository artifact availability. It returns `{ assets }`, with each entry either `{ ref, mimeType, content }` containing base64 bytes or `{ ref, error }`, where `error` is `not_found`, `too_large`, `outside_session_boundary`, or `unsupported`. Scheme, protocol-relative, and fragment-only references are unsupported. `canvas.document.preview` accepts session read access for caller-provided HTML and isolated sandbox metadata; it does not read session data.
- `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, including runs admitted or queued without a Gateway chat controller before their runner starts (such as OpenAI-compatible HTTP requests), as well as active channel replies. Session-scoped `chat.abort { sessionKey }` covers those runs too, without cascading to children. When that run has a known run ID, `chat.abort` returns it in `runIds` and `sessions.abort` returns it as `abortedRunId`. 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`, `thinkingLevel`, and `fastMode` overrides require `operator.write`, including clearing an override with `null`. The same field policy applies to `sessions.patchMany`. Context-window, verbose, trace, reasoning-visibility, tool, and other privileged overrides still require `operator.admin`; combining them with write-scoped fields does not lower that requirement. Only an admin model selection can persist as the configured agent default. On multi-user gateways, archive and restore additionally require the session creator or `operator.admin`; membership and assigned ownership do not grant archive access. Archive, restore, snooze, and wake 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.
- The `sessions.patch` receipt includes session metadata and resolved model/runtime facts. Its `entry` omits the saved `skillsSnapshot` and `systemPromptReport`; both snapshots remain persisted unchanged by display-metadata patches.
- `sessions.patch { snoozedUntil }` sets a future positive integer wake time in epoch milliseconds; `null` clears snooze. The Gateway stamps `snoozedAt` when the wake time changes, preserving it for an identical value. Snooze accepts active sessions eligible for pinning and rejects the same protected targets as archive, plus archived and child sessions. It keeps the session active and its pin intact, without lifecycle drains, automation disabling, worktree transitions, or work-admission changes. Pinning or archiving clears snooze. Real user/channel interaction and a completed run that updates user-facing activity also clear it; system events and preserved-state runs do not. Session rows expose `snoozedUntil` and `snoozedAt`, so clients compare the wake time to their own clock. Time expiry emits no event and leaves stale fields until a later write clears them; explicit snooze and wake patches publish the normal `sessions.changed` event with reason `patch`. `sessions.list` does not filter by snooze.
- `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](https://funcoding.ai/agents/openclaw/concepts/multi-user/#assigning-an-owner).
- `sessions.reset`, `sessions.delete`, and `sessions.compact` perform session maintenance. Explicit `agentId` values that cannot identify an agent are rejected before reset cleanup or mutation; they never fall back to the default agent. `sessions.reset` accepts an optional `expectedSessionId` from `sessions.list` or `sessions.describe`. If that session ID is no longer current when the reset enters its lifecycle fence, the Gateway rejects the request before interrupting work with `INVALID_REQUEST` and `error.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.get` returns `{ messages }` containing recent messages from the OpenClaw-owned stored transcript for the selected session. It does not import external CLI transcripts. Use `sessions.describe` for session metadata and `chat.history` for the display-normalized conversation.
- 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.
  For eligible Claude CLI bindings, `chat.history` merges external transcript messages with local messages. These merged snapshots remain byte-bounded; a terminal snapshot does not guarantee complete conversation history.
  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. Unchanged history returns an empty `messages` array with current session metadata and requested input receipts. `{ 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 its byte budget resets to a tail fetch. All history pages use a 512 KiB message budget; `maxBytes` can lower it. Oversized source rows, including indivisible display groups, become references with their original `__openclaw.id` and `truncated: true`. Read them through `chat.message.get`; stored transcript bytes stay unchanged.
  Anchored `messageId` reads can return opaque `olderCursor` and `newerCursor` values. Pass either back as `cursor` with the same `sessionKey` and optional `agentId` to continue in that direction. These responses use the full-page shape, not the delta shape. Continue through empty pages while the directional cursor is present; hidden rows still advance the cursor. Cursors retain the selected physical session and transcript source; `windowReset: true` means that source is no longer available and the caller must reopen the anchor. The response budget does not shorten error-recovery lookahead. Anchored reads and their continuations require current sharing access. After deletion removes the logical session entry but retains its transcript, direct history reads require `operator.admin`. `cron.history` separately validates access to its recorded automation run and transcript. Access is checked before loading history and again before publishing it.
- `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. Pass the history page's `sessionId` when fetching a reference from a retained physical session; the same session ownership and current sharing checks apply. `maxChars` accepts up to 8,000,000 characters per text field for large referenced outputs; the serialized response must still fit the WebSocket payload limit.
- `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.create.label` claims an explicit label within the physical session store, including archived sessions. Labels are trimmed and case-sensitive. Concurrent creations with distinct keys cannot claim the same label: the losing request receives `INVALID_REQUEST` with `label already in use: <label>` and must choose another label. A rejected fork does not retain copied history. Agents configured to share a store share its label namespace; separate stores do not.
- `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.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, with `activitySummary.state` set to `current`, `stale`, `updating`, or `unavailable` and optional cached `text` and `updatedAt`. Generation uses the owning agent's utility route and retains the previous recap on failure. `sessions.changed` invalidates the row as work completes. `sessions.list` includes these read-only projections only with `includeActivitySummary: true`, adding caller-specific `activitySummary.canEnsure`. Clients require that flag and `operator.write` before requesting generation; accepted ensure responses return `canEnsure: true`. This permission flag is not persisted in the recap cache. Ordinary listings do not request model generation. See [Activity session recaps](https://funcoding.ai/agents/openclaw/reference/database-schemas/layout/#activity-session-recaps) for persistence and lifecycle semantics.
- `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`.

- `sessions.create.surface` optionally accepts `"plugin-dock"` for an operator-created conversation owned by a plugin dock. It persists the immutable `createdSurface` marker; `createdVia`, the authenticated human creator, sharing, and sandbox policies retain their ordinary operator behavior. Omitting it keeps the existing creation behavior. Trusted spawn and other internal creation contracts cannot be replaced by this option. Adopting an existing session preserves its original creation surface and provenance.
