2026-01-03 23:44:38 +01:00
---
summary: "Agent session tools for listing sessions, fetching history, and sending cross-session messages"
read_when:
- Adding or modifying session tools
2026-01-31 16:04:03 -05:00
title: "Session Tools"
2026-01-03 23:44:38 +01:00
---
# Session Tools
2026-01-06 18:25:52 +00:00
Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history, and send to another session.
2026-01-03 23:44:38 +01:00
## Tool Names
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `sessions_list`
- `sessions_history`
- `sessions_send`
2026-01-06 08:41:45 +01:00
- `sessions_spawn`
2026-01-03 23:44:38 +01:00
## Key Model
2026-01-31 21:13:13 +09:00
2026-01-11 02:52:50 +00:00
- Main direct chat bucket is always the literal key `"main"` (resolved to the current agent’ s main key).
2026-01-13 07:15:57 +00:00
- Group chats use `agent:<agentId>:<channel>:group:<id>` or `agent:<agentId>:<channel>:channel:<id>` (pass the full key).
2026-01-03 23:44:38 +01:00
- Cron jobs use `cron:<job.id>` .
- Hooks use `hook:<uuid>` unless explicitly set.
2026-01-22 23:07:58 +00:00
- Node sessions use `node-<nodeId>` unless explicitly set.
2026-01-03 23:44:38 +01:00
2026-01-08 23:06:56 +01:00
`global` and `unknown` are reserved values and are never listed. If `session.scope = "global"` , we alias it to `main` for all tools so callers never see `global` .
2026-01-03 23:44:38 +01:00
## sessions_list
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
List sessions as an array of rows.
Parameters:
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `kinds?: string[]` filter: any of `"main" | "group" | "cron" | "hook" | "node" | "other"`
- `limit?: number` max rows (default: server default, clamp e.g. 200)
- `activeMinutes?: number` only sessions updated within N minutes
- `messageLimit?: number` 0 = no messages (default 0); >0 = include last N messages
Behavior:
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `messageLimit > 0` fetches `chat.history` per session and includes the last N messages.
- Tool results are filtered out in list output; use `sessions_history` for tool messages.
2026-01-06 08:40:21 +00:00
- When running in a **sandboxed** agent session, session tools default to **spawned-only visibility** (see below).
2026-01-03 23:44:38 +01:00
Row shape (JSON):
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `key` : session key (string)
- `kind` : `main | group | cron | hook | node | other`
2026-01-13 07:15:57 +00:00
- `channel` : `whatsapp | telegram | discord | signal | imessage | webchat | internal | unknown`
2026-01-03 23:44:38 +01:00
- `displayName` (group display label if available)
- `updatedAt` (ms)
- `sessionId`
- `model` , `contextTokens` , `totalTokens`
- `thinkingLevel` , `verboseLevel` , `systemSent` , `abortedLastRun`
- `sendPolicy` (session override if set)
2026-01-13 07:15:57 +00:00
- `lastChannel` , `lastTo`
2026-01-17 06:54:22 +00:00
- `deliveryContext` (normalized `{ channel, to, accountId }` when available)
2026-01-03 23:44:38 +01:00
- `transcriptPath` (best-effort path derived from store dir + sessionId)
- `messages?` (only when `messageLimit > 0` )
## sessions_history
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
Fetch transcript for one session.
Parameters:
2026-01-31 21:13:13 +09:00
2026-01-24 11:09:06 +00:00
- `sessionKey` (required; accepts session key or `sessionId` from `sessions_list` )
2026-01-03 23:44:38 +01:00
- `limit?: number` max messages (server clamps)
- `includeTools?: boolean` (default false)
Behavior:
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `includeTools=false` filters `role: "toolResult"` messages.
- Returns messages array in the raw transcript format.
2026-01-30 03:15:10 +01:00
- When given a `sessionId` , OpenClaw resolves it to the corresponding session key (missing ids error).
2026-01-03 23:44:38 +01:00
## sessions_send
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
Send a message into another session.
Parameters:
2026-01-31 21:13:13 +09:00
2026-01-24 11:09:06 +00:00
- `sessionKey` (required; accepts session key or `sessionId` from `sessions_list` )
2026-01-03 23:44:38 +01:00
- `message` (required)
- `timeoutSeconds?: number` (default >0; 0 = fire-and-forget)
Behavior:
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `timeoutSeconds = 0` : enqueue and return `{ runId, status: "accepted" }` .
- `timeoutSeconds > 0` : wait up to N seconds for completion, then return `{ runId, status: "ok", reply }` .
- If wait times out: `{ runId, status: "timeout", error }` . Run continues; call `sessions_history` later.
- If the run fails: `{ runId, status: "error", error }` .
2026-01-10 00:32:20 +01:00
- Announce delivery runs after the primary run completes and is best-effort; `status: "ok"` does not guarantee the announce was delivered.
2026-01-04 01:15:23 +01:00
- Waits via gateway `agent.wait` (server-side) so reconnects don't drop the wait.
2026-01-04 03:04:55 +01:00
- Agent-to-agent message context is injected for the primary run.
2026-02-13 02:01:53 +01:00
- Inter-session messages are persisted with `message.provenance.kind = "inter_session"` so transcript readers can distinguish routed agent instructions from external user input.
2026-01-30 03:15:10 +01:00
- After the primary run completes, OpenClaw runs a **reply-back loop** :
2026-01-04 03:37:44 +01:00
- Round 2+ alternates between requester and target agents.
- Reply exactly `REPLY_SKIP` to stop the ping‑ pong.
- Max turns is `session.agentToAgent.maxPingPongTurns` (0– 5, default 5).
2026-01-30 03:15:10 +01:00
- Once the loop ends, OpenClaw runs the **agent‑ to‑ agent announce step** (target agent only):
2026-01-04 03:37:44 +01:00
- Reply exactly `ANNOUNCE_SKIP` to stay silent.
2026-01-13 07:15:57 +00:00
- Any other reply is sent to the target channel.
2026-01-04 03:37:44 +01:00
- Announce step includes the original request + round‑ 1 reply + latest ping‑ pong reply.
2026-01-03 23:44:38 +01:00
2026-01-13 07:15:57 +00:00
## Channel Field
2026-01-31 21:13:13 +09:00
2026-01-13 07:15:57 +00:00
- For groups, `channel` is the channel recorded on the session entry.
- For direct chats, `channel` maps from `lastChannel` .
- For cron/hook/node, `channel` is `internal` .
- If missing, `channel` is `unknown` .
2026-01-03 23:44:38 +01:00
## Security / Send Policy
2026-01-31 21:13:13 +09:00
2026-01-13 07:15:57 +00:00
Policy-based blocking by channel/chat type (not per session id).
2026-01-03 23:44:38 +01:00
```json
{
"session": {
"sendPolicy": {
"rules": [
{
2026-01-13 07:15:57 +00:00
"match": { "channel": "discord", "chatType": "group" },
2026-01-03 23:44:38 +01:00
"action": "deny"
}
],
"default": "allow"
}
}
}
```
Runtime override (per session entry):
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `sendPolicy: "allow" | "deny"` (unset = inherit config)
2026-01-06 14:17:56 -06:00
- Settable via `sessions.patch` or owner-only `/send on|off|inherit` (standalone message).
2026-01-03 23:44:38 +01:00
Enforcement points:
2026-01-31 21:13:13 +09:00
2026-01-03 23:44:38 +01:00
- `chat.send` / `agent` (gateway)
- auto-reply delivery logic
2026-01-06 08:41:45 +01:00
## sessions_spawn
2026-01-31 21:13:13 +09:00
2026-01-13 07:15:57 +00:00
Spawn a sub-agent run in an isolated session and announce the result back to the requester chat channel.
2026-01-06 08:41:45 +01:00
Parameters:
2026-01-31 21:13:13 +09:00
2026-01-06 08:41:45 +01:00
- `task` (required)
- `label?` (optional; used for logs/UI)
2026-01-08 06:55:28 +00:00
- `agentId?` (optional; spawn under another agent id if allowed)
2026-01-06 23:17:10 +00:00
- `model?` (optional; overrides the sub-agent model; invalid values error)
2026-02-21 19:59:50 +01:00
- `thinking?` (optional; overrides thinking level for the sub-agent run)
2026-02-24 04:22:25 +00:00
- `runTimeoutSeconds?` (defaults to `agents.defaults.subagents.runTimeoutSeconds` when set, otherwise `0` ; when set, aborts the sub-agent run after N seconds)
2026-02-21 19:59:50 +01:00
- `thread?` (default false; request thread-bound routing for this spawn when supported by the channel/plugin)
- `mode?` (`run|session` ; defaults to `run` , but defaults to `session` when `thread=true` ; `mode="session"` requires `thread=true` )
2026-01-07 06:53:01 +01:00
- `cleanup?` (`delete|keep` , default `keep` )
2026-03-02 01:27:25 +00:00
- `sandbox?` (`inherit|require` , default `inherit` ; `require` rejects spawn unless the target child runtime is sandboxed)
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):
- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files
Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
- `attachments?` (optional array of inline files; subagent runtime only, ACP rejects). Each entry: `{ name, content, encoding?: "utf8" | "base64", mimeType? }` . Files are materialized into the child workspace at `.openclaw/attachments/<uuid>/` . Returns a receipt with sha256 per file.
- `attachAs?` (optional; `{ mountPath? }` hint reserved for future mount implementations)
2026-01-06 08:41:45 +01:00
2026-01-08 06:55:28 +00:00
Allowlist:
2026-01-31 21:13:13 +09:00
2026-01-09 12:44:23 +00:00
- `agents.list[].subagents.allowAgents` : list of agent ids allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
2026-03-02 01:10:39 +00:00
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
2026-01-08 06:55:28 +00:00
2026-01-08 07:06:36 +00:00
Discovery:
2026-01-31 21:13:13 +09:00
2026-01-08 07:06:36 +00:00
- Use `agents_list` to discover which agent ids are allowed for `sessions_spawn` .
2026-01-06 08:41:45 +01:00
Behavior:
2026-01-31 21:13:13 +09:00
2026-01-07 16:14:25 +00:00
- Starts a new `agent:<agentId>:subagent:<uuid>` session with `deliver: false` .
2026-01-09 12:44:23 +00:00
- Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools` ).
2026-02-20 19:26:25 -06:00
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
2026-01-07 16:14:25 +00:00
- Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }` immediately.
2026-02-21 19:59:50 +01:00
- With `thread=true` , channel plugins can bind delivery/routing to a thread target (Discord support is controlled by `session.threadBindings.*` and `channels.discord.threadBindings.*` ).
2026-02-20 19:26:25 -06:00
- After completion, OpenClaw runs a sub-agent **announce step** and posts the result to the requester chat channel.
- If the assistant final reply is empty, the latest `toolResult` from sub-agent history is included as `Result` .
- Reply exactly `ANNOUNCE_SKIP` during the announce step to stay silent.
- Announce replies are normalized to `Status` /`Result` /`Notes` ; `Status` comes from runtime outcome (not model text).
2026-01-09 12:44:23 +00:00
- Sub-agent sessions are auto-archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
2026-01-07 06:53:01 +01:00
- Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).
2026-01-06 08:40:21 +00:00
## Sandbox Session Visibility
2026-02-16 03:43:51 +01:00
Session tools can be scoped to reduce cross-session access.
Default behavior:
- `tools.sessions.visibility` defaults to `tree` (current session + spawned subagent sessions).
- For sandboxed sessions, `agents.defaults.sandbox.sessionToolsVisibility` can hard-clamp visibility.
2026-01-06 08:40:21 +00:00
Config:
```json5
{
2026-02-16 03:43:51 +01:00
tools: {
sessions: {
// "self" | "tree" | "agent" | "all"
// default: "tree"
visibility: "tree",
},
},
2026-01-09 12:44:23 +00:00
agents: {
defaults: {
sandbox: {
// default: "spawned"
2026-01-31 21:13:13 +09:00
sessionToolsVisibility: "spawned", // or "all"
},
},
},
2026-01-06 08:40:21 +00:00
}
```
2026-02-16 03:43:51 +01:00
Notes:
- `self` : only the current session key.
- `tree` : current session + sessions spawned by the current session.
- `agent` : any session belonging to the current agent id.
- `all` : any session (cross-agent access still requires `tools.agentToAgent` ).
- When a session is sandboxed and `sessionToolsVisibility="spawned"` , OpenClaw clamps visibility to `tree` even if you set `tools.sessions.visibility="all"` .