mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-31 20:01:36 +00:00
docs: rewrite session management, session pruning, and session tools pages
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Session Pruning"
|
||||
summary: "Session pruning: tool-result trimming to reduce context bloat"
|
||||
summary: "How session pruning trims old tool results to reduce context bloat and improve cache efficiency"
|
||||
read_when:
|
||||
- You want to reduce LLM context growth from tool outputs
|
||||
- You are tuning agents.defaults.contextPruning
|
||||
@@ -8,90 +8,102 @@ read_when:
|
||||
|
||||
# Session Pruning
|
||||
|
||||
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It does **not** rewrite the on-disk session history (`*.jsonl`).
|
||||
Session pruning trims **old tool results** from the in-memory context before
|
||||
each LLM call. It does **not** rewrite the on-disk session history (JSONL) --
|
||||
it only affects what gets sent to the model for that request.
|
||||
|
||||
## When it runs
|
||||
## Why prune
|
||||
|
||||
- When `mode: "cache-ttl"` is enabled and the last Anthropic call for the session is older than `ttl`.
|
||||
- Only affects the messages sent to the model for that request.
|
||||
- Only active for Anthropic API calls (and OpenRouter Anthropic models).
|
||||
- For best results, match `ttl` to your model `cacheRetention` policy (`short` = 5m, `long` = 1h).
|
||||
- After a prune, the TTL window resets so subsequent requests keep cache until `ttl` expires again.
|
||||
Long-running sessions accumulate tool outputs (exec results, file reads, search
|
||||
results). These inflate the context window, increasing cost and eventually
|
||||
forcing [compaction](/concepts/compaction). Pruning removes stale tool output so
|
||||
the model sees a leaner context on each turn.
|
||||
|
||||
## Smart defaults (Anthropic)
|
||||
Pruning is also important for **Anthropic prompt caching**. When a session goes
|
||||
idle past the cache TTL, the next request re-caches the full prompt. Pruning
|
||||
reduces the cache-write size for that first post-TTL request, which directly
|
||||
reduces cost.
|
||||
|
||||
- **OAuth or setup-token** profiles: enable `cache-ttl` pruning and set heartbeat to `1h`.
|
||||
- **API key** profiles: enable `cache-ttl` pruning, set heartbeat to `30m`, and default `cacheRetention: "short"` on Anthropic models.
|
||||
- If you set any of these values explicitly, OpenClaw does **not** override them.
|
||||
## How it works
|
||||
|
||||
## What this improves (cost + cache behavior)
|
||||
Pruning runs in `cache-ttl` mode, which is the only supported mode:
|
||||
|
||||
- **Why prune:** Anthropic prompt caching only applies within the TTL. If a session goes idle past the TTL, the next request re-caches the full prompt unless you trim it first.
|
||||
- **What gets cheaper:** pruning reduces the **cacheWrite** size for that first request after the TTL expires.
|
||||
- **Why the TTL reset matters:** once pruning runs, the cache window resets, so follow‑up requests can reuse the freshly cached prompt instead of re-caching the full history again.
|
||||
- **What it does not do:** pruning doesn’t add tokens or “double” costs; it only changes what gets cached on that first post‑TTL request.
|
||||
1. **Check the clock** -- pruning only runs if the last Anthropic API call for
|
||||
the session is older than `ttl` (default `5m`).
|
||||
2. **Find prunable messages** -- only `toolResult` messages are eligible. User
|
||||
and assistant messages are never modified.
|
||||
3. **Protect recent context** -- the last `keepLastAssistants` assistant
|
||||
messages (default `3`) and all tool results after that cutoff are preserved.
|
||||
4. **Soft-trim** oversized tool results -- keep the head and tail, insert
|
||||
`...`, and append a note with the original size.
|
||||
5. **Hard-clear** remaining eligible results -- replace the entire content with
|
||||
a placeholder.
|
||||
6. **Reset the TTL** -- subsequent requests keep cache until `ttl` expires
|
||||
again.
|
||||
|
||||
## What can be pruned
|
||||
### What gets skipped
|
||||
|
||||
- Only `toolResult` messages.
|
||||
- User + assistant messages are **never** modified.
|
||||
- The last `keepLastAssistants` assistant messages are protected; tool results after that cutoff are not pruned.
|
||||
- If there aren’t enough assistant messages to establish the cutoff, pruning is skipped.
|
||||
- Tool results containing **image blocks** are skipped (never trimmed/cleared).
|
||||
- Tool results containing **image blocks** are never trimmed.
|
||||
- If there are not enough assistant messages to establish the cutoff, pruning
|
||||
is skipped entirely.
|
||||
- Pruning currently only activates for Anthropic API calls (and OpenRouter
|
||||
Anthropic models).
|
||||
|
||||
## Context window estimation
|
||||
## Smart defaults
|
||||
|
||||
Pruning uses an estimated context window (chars ≈ tokens × 4). The base window is resolved in this order:
|
||||
OpenClaw auto-configures pruning for Anthropic profiles:
|
||||
|
||||
1. `models.providers.*.models[].contextWindow` override.
|
||||
2. Model definition `contextWindow` (from the model registry).
|
||||
3. Default `200000` tokens.
|
||||
| Profile type | Pruning | Heartbeat | Cache retention |
|
||||
| -------------------- | ------------------- | --------- | ------------------ |
|
||||
| OAuth or setup-token | `cache-ttl` enabled | `1h` | (provider default) |
|
||||
| API key | `cache-ttl` enabled | `30m` | `short` (5 min) |
|
||||
|
||||
If `agents.defaults.contextTokens` is set, it is treated as a cap (min) on the resolved window.
|
||||
If you set any of these values explicitly, OpenClaw does not override them.
|
||||
|
||||
## Mode
|
||||
Match `ttl` to your model `cacheRetention` policy for best results (`short` =
|
||||
5 min, `long` = 1 hour).
|
||||
|
||||
### cache-ttl
|
||||
## Pruning vs compaction
|
||||
|
||||
- Pruning only runs if the last Anthropic call is older than `ttl` (default `5m`).
|
||||
- When it runs: same soft-trim + hard-clear behavior as before.
|
||||
| | Pruning | Compaction |
|
||||
| -------------- | --------------------------------- | ------------------------------- |
|
||||
| **What** | Trims tool result messages | Summarizes conversation history |
|
||||
| **Persisted?** | No (in-memory, per request) | Yes (in JSONL transcript) |
|
||||
| **Scope** | Tool results only | Entire conversation |
|
||||
| **Trigger** | Every LLM call (when TTL expired) | Context window threshold |
|
||||
|
||||
## Soft vs hard pruning
|
||||
Built-in tools already truncate their own output. Pruning is an additional layer
|
||||
that prevents long-running chats from accumulating too much tool output over
|
||||
time. See [Compaction](/concepts/compaction) for the summarization approach.
|
||||
|
||||
- **Soft-trim**: only for oversized tool results.
|
||||
- Keeps head + tail, inserts `...`, and appends a note with the original size.
|
||||
- Skips results with image blocks.
|
||||
- **Hard-clear**: replaces the entire tool result with `hardClear.placeholder`.
|
||||
## Configuration
|
||||
|
||||
## Tool selection
|
||||
### Defaults (when enabled)
|
||||
|
||||
- `tools.allow` / `tools.deny` support `*` wildcards.
|
||||
- Deny wins.
|
||||
- Matching is case-insensitive.
|
||||
- Empty allow list => all tools allowed.
|
||||
| Setting | Default | Description |
|
||||
| ----------------------- | ----------------------------------- | ------------------------------------------------ |
|
||||
| `ttl` | `5m` | Prune only after this idle period |
|
||||
| `keepLastAssistants` | `3` | Protect tool results near recent assistant turns |
|
||||
| `softTrimRatio` | `0.3` | Context ratio for soft-trim eligibility |
|
||||
| `hardClearRatio` | `0.5` | Context ratio for hard-clear eligibility |
|
||||
| `minPrunableToolChars` | `50000` | Minimum tool result size to consider |
|
||||
| `softTrim.maxChars` | `4000` | Max chars after soft-trim |
|
||||
| `softTrim.headChars` | `1500` | Head portion to keep |
|
||||
| `softTrim.tailChars` | `1500` | Tail portion to keep |
|
||||
| `hardClear.enabled` | `true` | Enable hard-clear stage |
|
||||
| `hardClear.placeholder` | `[Old tool result content cleared]` | Replacement text |
|
||||
|
||||
## Interaction with other limits
|
||||
### Examples
|
||||
|
||||
- Built-in tools already truncate their own output; session pruning is an extra layer that prevents long-running chats from accumulating too much tool output in the model context.
|
||||
- Compaction is separate: compaction summarizes and persists, pruning is transient per request. See [/concepts/compaction](/concepts/compaction).
|
||||
|
||||
## Defaults (when enabled)
|
||||
|
||||
- `ttl`: `"5m"`
|
||||
- `keepLastAssistants`: `3`
|
||||
- `softTrimRatio`: `0.3`
|
||||
- `hardClearRatio`: `0.5`
|
||||
- `minPrunableToolChars`: `50000`
|
||||
- `softTrim`: `{ maxChars: 4000, headChars: 1500, tailChars: 1500 }`
|
||||
- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
|
||||
|
||||
## Examples
|
||||
|
||||
Default (off):
|
||||
Disable pruning (default state):
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { contextPruning: { mode: "off" } } },
|
||||
agents: {
|
||||
defaults: {
|
||||
contextPruning: { mode: "off" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -99,7 +111,11 @@ Enable TTL-aware pruning:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { contextPruning: { mode: "cache-ttl", ttl: "5m" } } },
|
||||
agents: {
|
||||
defaults: {
|
||||
contextPruning: { mode: "cache-ttl", ttl: "5m" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -111,11 +127,32 @@ Restrict pruning to specific tools:
|
||||
defaults: {
|
||||
contextPruning: {
|
||||
mode: "cache-ttl",
|
||||
tools: { allow: ["exec", "read"], deny: ["*image*"] },
|
||||
tools: {
|
||||
allow: ["exec", "read"],
|
||||
deny: ["*image*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See config reference: [Gateway Configuration](/gateway/configuration)
|
||||
Tool selection supports `*` wildcards, deny wins over allow, matching is
|
||||
case-insensitive, and an empty allow list means all tools are allowed.
|
||||
|
||||
## Context window estimation
|
||||
|
||||
Pruning estimates the context window (chars = tokens x 4). The base window is
|
||||
resolved in this order:
|
||||
|
||||
1. `models.providers.*.models[].contextWindow` override.
|
||||
2. Model definition `contextWindow` from the model registry.
|
||||
3. Default `200000` tokens.
|
||||
|
||||
If `agents.defaults.contextTokens` is set, it caps the resolved window.
|
||||
|
||||
## Related
|
||||
|
||||
- [Compaction](/concepts/compaction) -- summarization-based context reduction
|
||||
- [Session Management](/concepts/session) -- session lifecycle and routing
|
||||
- [Gateway Configuration](/gateway/configuration) -- full config reference
|
||||
|
||||
@@ -1,251 +1,220 @@
|
||||
---
|
||||
summary: "Agent session tools for listing sessions, fetching history, and sending cross-session messages"
|
||||
summary: "Agent tools for listing sessions, reading history, cross-session messaging, and spawning sub-agents"
|
||||
read_when:
|
||||
- Adding or modifying session tools
|
||||
- You want to understand agent session tools
|
||||
- You are configuring cross-session access or sub-agent spawning
|
||||
title: "Session Tools"
|
||||
---
|
||||
|
||||
# Session Tools
|
||||
|
||||
Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history, and send to another session.
|
||||
OpenClaw gives agents a small set of tools to interact with sessions: list them,
|
||||
read their history, send messages across sessions, and spawn isolated sub-agent
|
||||
runs.
|
||||
|
||||
## Tool Names
|
||||
## Overview
|
||||
|
||||
- `sessions_list`
|
||||
- `sessions_history`
|
||||
- `sessions_send`
|
||||
- `sessions_spawn`
|
||||
| Tool | Purpose |
|
||||
| ------------------ | ----------------------------------- |
|
||||
| `sessions_list` | List sessions with optional filters |
|
||||
| `sessions_history` | Fetch transcript for one session |
|
||||
| `sessions_send` | Send a message into another session |
|
||||
| `sessions_spawn` | Spawn an isolated sub-agent session |
|
||||
|
||||
## Key Model
|
||||
## Session keys
|
||||
|
||||
- Main direct chat bucket is always the literal key `"main"` (resolved to the current agent’s main key).
|
||||
- Group chats use `agent:<agentId>:<channel>:group:<id>` or `agent:<agentId>:<channel>:channel:<id>` (pass the full key).
|
||||
- Cron jobs use `cron:<job.id>`.
|
||||
- Hooks use `hook:<uuid>` unless explicitly set.
|
||||
- Node sessions use `node-<nodeId>` unless explicitly set.
|
||||
Session tools use **session keys** to identify conversations:
|
||||
|
||||
`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`.
|
||||
- `"main"` -- the agent's main direct-chat session.
|
||||
- `agent:<agentId>:<channel>:group:<id>` -- group chat (pass the full key).
|
||||
- `cron:<job.id>` -- cron job session.
|
||||
- `hook:<uuid>` -- webhook session.
|
||||
- `node-<nodeId>` -- node session.
|
||||
|
||||
`global` and `unknown` are reserved and never listed. If
|
||||
`session.scope = "global"`, it is aliased to `main` for all tools.
|
||||
|
||||
## sessions_list
|
||||
|
||||
List sessions as an array of rows.
|
||||
Lists sessions as an array of rows.
|
||||
|
||||
Parameters:
|
||||
**Parameters:**
|
||||
|
||||
- `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
|
||||
| Parameter | Type | Default | Description |
|
||||
| --------------- | ---------- | -------------- | -------------------------------------------------------- |
|
||||
| `kinds` | `string[]` | all | Filter: `main`, `group`, `cron`, `hook`, `node`, `other` |
|
||||
| `limit` | `number` | server default | Max rows returned |
|
||||
| `activeMinutes` | `number` | -- | Only sessions updated within N minutes |
|
||||
| `messageLimit` | `number` | `0` | Include last N messages per session (0 = none) |
|
||||
|
||||
Behavior:
|
||||
When `messageLimit > 0`, OpenClaw 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.
|
||||
|
||||
- `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.
|
||||
- When running in a **sandboxed** agent session, session tools default to **spawned-only visibility** (see below).
|
||||
|
||||
Row shape (JSON):
|
||||
|
||||
- `key`: session key (string)
|
||||
- `kind`: `main | group | cron | hook | node | other`
|
||||
- `channel`: `whatsapp | telegram | discord | signal | imessage | webchat | internal | unknown`
|
||||
- `displayName` (group display label if available)
|
||||
- `updatedAt` (ms)
|
||||
- `sessionId`
|
||||
- `model`, `contextTokens`, `totalTokens`
|
||||
- `thinkingLevel`, `verboseLevel`, `systemSent`, `abortedLastRun`
|
||||
- `sendPolicy` (session override if set)
|
||||
- `lastChannel`, `lastTo`
|
||||
- `deliveryContext` (normalized `{ channel, to, accountId }` when available)
|
||||
- `transcriptPath` (best-effort path derived from store dir + sessionId)
|
||||
- `messages?` (only when `messageLimit > 0`)
|
||||
**Row fields:** `key`, `kind`, `channel`, `displayName`, `updatedAt`,
|
||||
`sessionId`, `model`, `contextTokens`, `totalTokens`, `thinkingLevel`,
|
||||
`verboseLevel`, `sendPolicy`, `lastChannel`, `lastTo`, `deliveryContext`,
|
||||
`transcriptPath`, and optionally `messages`.
|
||||
|
||||
## sessions_history
|
||||
|
||||
Fetch transcript for one session.
|
||||
Fetches the transcript for one session.
|
||||
|
||||
Parameters:
|
||||
**Parameters:**
|
||||
|
||||
- `sessionKey` (required; accepts session key or `sessionId` from `sessions_list`)
|
||||
- `limit?: number` max messages (server clamps)
|
||||
- `includeTools?: boolean` (default false)
|
||||
| Parameter | Type | Default | Description |
|
||||
| -------------- | --------- | -------------- | ----------------------------------------------- |
|
||||
| `sessionKey` | `string` | required | Session key or `sessionId` from `sessions_list` |
|
||||
| `limit` | `number` | server default | Max messages |
|
||||
| `includeTools` | `boolean` | `false` | Include `toolResult` messages |
|
||||
|
||||
Behavior:
|
||||
When given a `sessionId`, OpenClaw resolves it to the corresponding session key.
|
||||
|
||||
- `includeTools=false` filters `role: "toolResult"` messages.
|
||||
- Returns messages array in the raw transcript format.
|
||||
- When given a `sessionId`, OpenClaw resolves it to the corresponding session key (missing ids error).
|
||||
### Gateway APIs
|
||||
|
||||
## Gateway session history and live transcript APIs
|
||||
Control UI and gateway clients can use lower-level APIs directly:
|
||||
|
||||
Control UI and gateway clients can use the lower level history and live transcript surfaces directly.
|
||||
|
||||
HTTP:
|
||||
|
||||
- `GET /sessions/{sessionKey}/history`
|
||||
- Query params: `limit`, `cursor`, `includeTools=1`, `follow=1`
|
||||
- Unknown sessions return HTTP `404` with `error.type = "not_found"`
|
||||
- `follow=1` upgrades the response to an SSE stream of transcript updates for that session
|
||||
|
||||
WebSocket:
|
||||
|
||||
- `sessions.subscribe` subscribes to all session lifecycle and transcript events visible to the client
|
||||
- `sessions.messages.subscribe { key }` subscribes only to `session.message` events for one session
|
||||
- `sessions.messages.unsubscribe { key }` removes that targeted transcript subscription
|
||||
- `session.message` carries appended transcript messages plus live usage metadata when available
|
||||
- `sessions.changed` emits `phase: "message"` for transcript appends so session lists can refresh counters and previews
|
||||
- **HTTP:** `GET /sessions/{sessionKey}/history` with query params `limit`,
|
||||
`cursor`, `includeTools=1`, `follow=1` (upgrades to SSE stream).
|
||||
- **WebSocket:** `sessions.subscribe` for all lifecycle events,
|
||||
`sessions.messages.subscribe { key }` for one session's transcript,
|
||||
`sessions.messages.unsubscribe { key }` to remove.
|
||||
|
||||
## sessions_send
|
||||
|
||||
Send a message into another session.
|
||||
Sends a message into another session.
|
||||
|
||||
Parameters:
|
||||
**Parameters:**
|
||||
|
||||
- `sessionKey` (required; accepts session key or `sessionId` from `sessions_list`)
|
||||
- `message` (required)
|
||||
- `timeoutSeconds?: number` (default >0; 0 = fire-and-forget)
|
||||
| Parameter | Type | Default | Description |
|
||||
| ---------------- | -------- | -------- | ---------------------------------- |
|
||||
| `sessionKey` | `string` | required | Target session key or `sessionId` |
|
||||
| `message` | `string` | required | Message content |
|
||||
| `timeoutSeconds` | `number` | > 0 | Wait timeout (0 = fire-and-forget) |
|
||||
|
||||
Behavior:
|
||||
**Behavior:**
|
||||
|
||||
- `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 }`.
|
||||
- Announce delivery runs after the primary run completes and is best-effort; `status: "ok"` does not guarantee the announce was delivered.
|
||||
- Waits via gateway `agent.wait` (server-side) so reconnects don't drop the wait.
|
||||
- Agent-to-agent message context is injected for the primary run.
|
||||
- Inter-session messages are persisted with `message.provenance.kind = "inter_session"` so transcript readers can distinguish routed agent instructions from external user input.
|
||||
- After the primary run completes, OpenClaw runs a **reply-back loop**:
|
||||
- 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).
|
||||
- Once the loop ends, OpenClaw runs the **agent‑to‑agent announce step** (target agent only):
|
||||
- Reply exactly `ANNOUNCE_SKIP` to stay silent.
|
||||
- Any other reply is sent to the target channel.
|
||||
- Announce step includes the original request + round‑1 reply + latest ping‑pong reply.
|
||||
- `timeoutSeconds = 0` -- enqueue and return `{ runId, status: "accepted" }`.
|
||||
- `timeoutSeconds > 0` -- wait for completion, then return the reply.
|
||||
- Timeout: `{ runId, status: "timeout" }`. The run continues; check
|
||||
`sessions_history` later.
|
||||
|
||||
## Channel Field
|
||||
### Reply-back loop
|
||||
|
||||
- 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`.
|
||||
After the target session responds, OpenClaw runs an alternating reply loop
|
||||
between requester and target agents:
|
||||
|
||||
## Security / Send Policy
|
||||
- Reply `REPLY_SKIP` to stop the ping-pong.
|
||||
- Max turns: `session.agentToAgent.maxPingPongTurns` (0--5, default 5).
|
||||
|
||||
Policy-based blocking by channel/chat type (not per session id).
|
||||
After the loop, an **announce step** posts the result to the target's chat
|
||||
channel. Reply `ANNOUNCE_SKIP` to stay silent. The announce includes the
|
||||
original request, round-1 reply, and latest ping-pong reply.
|
||||
|
||||
```json
|
||||
{
|
||||
"session": {
|
||||
"sendPolicy": {
|
||||
"rules": [
|
||||
{
|
||||
"match": { "channel": "discord", "chatType": "group" },
|
||||
"action": "deny"
|
||||
}
|
||||
],
|
||||
"default": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Runtime override (per session entry):
|
||||
|
||||
- `sendPolicy: "allow" | "deny"` (unset = inherit config)
|
||||
- Settable via `sessions.patch` or owner-only `/send on|off|inherit` (standalone message).
|
||||
|
||||
Enforcement points:
|
||||
|
||||
- `chat.send` / `agent` (gateway)
|
||||
- auto-reply delivery logic
|
||||
Inter-session messages are tagged with
|
||||
`message.provenance.kind = "inter_session"` so transcript readers can
|
||||
distinguish routed agent instructions from external user input.
|
||||
|
||||
## sessions_spawn
|
||||
|
||||
Spawn an isolated delegated session.
|
||||
Spawns an isolated delegated session for background work.
|
||||
|
||||
- Default runtime: OpenClaw sub-agent (`runtime: "subagent"`).
|
||||
- ACP harness sessions use `runtime: "acp"` and follow ACP-specific targeting/policy rules.
|
||||
- This section focuses on sub-agent behavior unless noted otherwise. For ACP-specific behavior, see [ACP Agents](/tools/acp-agents).
|
||||
**Parameters:**
|
||||
|
||||
Parameters:
|
||||
| Parameter | Type | Default | Description |
|
||||
| ------------------- | --------- | ---------- | -------------------------------------------- |
|
||||
| `task` | `string` | required | Task description |
|
||||
| `runtime` | `string` | `subagent` | `subagent` or `acp` |
|
||||
| `label` | `string` | -- | Label for logs/UI |
|
||||
| `agentId` | `string` | -- | Target agent or ACP harness ID |
|
||||
| `model` | `string` | -- | Override sub-agent model |
|
||||
| `thinking` | `string` | -- | Override thinking level |
|
||||
| `runTimeoutSeconds` | `number` | `0` | Abort after N seconds (0 = no limit) |
|
||||
| `thread` | `boolean` | `false` | Request thread-bound routing |
|
||||
| `mode` | `string` | `run` | `run` or `session` (session requires thread) |
|
||||
| `cleanup` | `string` | `keep` | `delete` or `keep` |
|
||||
| `sandbox` | `string` | `inherit` | `inherit` or `require` |
|
||||
| `attachments` | `array` | -- | Inline files (subagent only) |
|
||||
|
||||
- `task` (required)
|
||||
- `runtime?` (`subagent|acp`; defaults to `subagent`)
|
||||
- `label?` (optional; used for logs/UI)
|
||||
- `agentId?` (optional)
|
||||
- `runtime: "subagent"`: target another OpenClaw agent id if allowed by `subagents.allowAgents`
|
||||
- `runtime: "acp"`: target an ACP harness id if allowed by `acp.allowedAgents`
|
||||
- `model?` (optional; overrides the sub-agent model; invalid values error)
|
||||
- `thinking?` (optional; overrides thinking level for the sub-agent run)
|
||||
- `runTimeoutSeconds?` (defaults to `agents.defaults.subagents.runTimeoutSeconds` when set, otherwise `0`; when set, aborts the sub-agent run after N seconds)
|
||||
- `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`)
|
||||
- `cleanup?` (`delete|keep`, default `keep`)
|
||||
- `sandbox?` (`inherit|require`, default `inherit`; `require` rejects spawn unless the target child runtime is sandboxed)
|
||||
- `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)
|
||||
**Behavior:**
|
||||
|
||||
Allowlist:
|
||||
- Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }`.
|
||||
- Creates a new `agent:<agentId>:subagent:<uuid>` session with
|
||||
`deliver: false`.
|
||||
- Sub-agents get the full tool set minus session tools (configurable via
|
||||
`tools.subagents.tools`).
|
||||
- Sub-agents cannot call `sessions_spawn` (no recursive spawning).
|
||||
- After completion, an announce step posts the result to the requester's
|
||||
channel. Reply `ANNOUNCE_SKIP` to stay silent.
|
||||
- Sub-agent sessions are auto-archived after
|
||||
`agents.defaults.subagents.archiveAfterMinutes` (default: 60).
|
||||
|
||||
- `runtime: "subagent"`: `agents.list[].subagents.allowAgents` controls which OpenClaw agent ids are allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
|
||||
- `runtime: "acp"`: `acp.allowedAgents` controls which ACP harness ids are allowed. This is a separate policy from `subagents.allowAgents`.
|
||||
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
|
||||
### Allowlists
|
||||
|
||||
Discovery:
|
||||
- **Subagent:** `agents.list[].subagents.allowAgents` controls which agent IDs
|
||||
are allowed (`["*"]` for any). Default: only the requester.
|
||||
- **ACP:** `acp.allowedAgents` controls allowed ACP harness IDs (separate from
|
||||
subagent policy).
|
||||
- If the requester is sandboxed, targets that would run unsandboxed are
|
||||
rejected.
|
||||
|
||||
- Use `agents_list` to discover allowed targets for `runtime: "subagent"`.
|
||||
- For `runtime: "acp"`, use configured ACP harness ids and `acp.allowedAgents`; `agents_list` does not list ACP harness targets.
|
||||
### Attachments
|
||||
|
||||
Behavior:
|
||||
Each entry: `{ name, content, encoding?: "utf8" | "base64", mimeType? }`.
|
||||
Files are materialized into `<workspace>/.openclaw/attachments/<uuid>/` and a
|
||||
receipt with sha256 is returned. ACP runtime rejects attachments.
|
||||
|
||||
- Starts a new `agent:<agentId>:subagent:<uuid>` session with `deliver: false`.
|
||||
- Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools`).
|
||||
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
|
||||
- Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }` immediately.
|
||||
- With `thread=true`, channel plugins can bind delivery/routing to a thread target (Discord support is controlled by `session.threadBindings.*` and `channels.discord.threadBindings.*`).
|
||||
- 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).
|
||||
- Sub-agent sessions are auto-archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
|
||||
- Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).
|
||||
For ACP-specific behavior (harness targeting, permission modes), see
|
||||
[ACP Agents](/tools/acp-agents).
|
||||
|
||||
## Sandbox Session Visibility
|
||||
## Visibility and access control
|
||||
|
||||
Session tools can be scoped to reduce cross-session access.
|
||||
Session tools can be scoped to limit cross-session access.
|
||||
|
||||
Default behavior:
|
||||
### Visibility levels
|
||||
|
||||
- `tools.sessions.visibility` defaults to `tree` (current session + spawned subagent sessions).
|
||||
- For sandboxed sessions, `agents.defaults.sandbox.sessionToolsVisibility` can hard-clamp visibility.
|
||||
| Level | What the agent can see |
|
||||
| ---------------- | ------------------------------------------------------- |
|
||||
| `self` | Only the current session |
|
||||
| `tree` (default) | Current session + spawned sub-agent sessions |
|
||||
| `agent` | Any session belonging to the current agent |
|
||||
| `all` | Any session (cross-agent requires `tools.agentToAgent`) |
|
||||
|
||||
Config:
|
||||
Configure at `tools.sessions.visibility`.
|
||||
|
||||
### Sandbox clamping
|
||||
|
||||
Sandboxed sessions have an additional clamp via
|
||||
`agents.defaults.sandbox.sessionToolsVisibility` (default: `spawned`). When
|
||||
this is set, visibility is clamped to `tree` even if
|
||||
`tools.sessions.visibility = "all"`.
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
sessions: {
|
||||
// "self" | "tree" | "agent" | "all"
|
||||
// default: "tree"
|
||||
visibility: "tree",
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
// default: "spawned"
|
||||
sessionToolsVisibility: "spawned", // or "all"
|
||||
sessionToolsVisibility: "spawned",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
## Send policy
|
||||
|
||||
- `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"`.
|
||||
Policy-based blocking by channel or chat type prevents agents from sending to
|
||||
restricted sessions. See [Session Management](/concepts/session) for send policy
|
||||
configuration.
|
||||
|
||||
## Related
|
||||
|
||||
- [Session Management](/concepts/session) -- session routing, lifecycle, and
|
||||
maintenance
|
||||
- [ACP Agents](/tools/acp-agents) -- ACP-specific spawning and permissions
|
||||
- [Multi-agent](/concepts/multi-agent) -- multi-agent architecture
|
||||
|
||||
@@ -1,126 +1,195 @@
|
||||
---
|
||||
summary: "Session management rules, keys, and persistence for chats"
|
||||
summary: "How OpenClaw manages sessions -- routing, isolation, lifecycle, and maintenance"
|
||||
read_when:
|
||||
- Modifying session handling or storage
|
||||
- You want to understand session keys and routing
|
||||
- You want to configure DM isolation or multi-user setups
|
||||
- You want to tune session lifecycle or maintenance
|
||||
title: "Session Management"
|
||||
---
|
||||
|
||||
# Session Management
|
||||
|
||||
OpenClaw treats **one direct-chat session per agent** as primary. Direct chats collapse to `agent:<agentId>:<mainKey>` (default `main`), while group/channel chats get their own keys. `session.mainKey` is honored.
|
||||
OpenClaw manages conversations through **sessions**. Each session has a key
|
||||
(which conversation bucket it belongs to), an ID (which transcript file
|
||||
continues it), and metadata tracked in a session store.
|
||||
|
||||
Use `session.dmScope` to control how **direct messages** are grouped:
|
||||
## How sessions are routed
|
||||
|
||||
- `main` (default): all DMs share the main session for continuity.
|
||||
- `per-peer`: isolate by sender id across channels.
|
||||
- `per-channel-peer`: isolate by channel + sender (recommended for multi-user inboxes).
|
||||
- `per-account-channel-peer`: isolate by account + channel + sender (recommended for multi-account inboxes).
|
||||
Use `session.identityLinks` to map provider-prefixed peer ids to a canonical identity so the same person shares a DM session across channels when using `per-peer`, `per-channel-peer`, or `per-account-channel-peer`.
|
||||
Every inbound message is mapped to a **session key** that determines which
|
||||
conversation it joins:
|
||||
|
||||
## Secure DM mode (recommended for multi-user setups)
|
||||
| Source | Session key pattern | Behavior |
|
||||
| --------------- | ---------------------------------------- | ------------------------------------- |
|
||||
| Direct messages | `agent:<agentId>:<mainKey>` | Shared by default (`dmScope: "main"`) |
|
||||
| Group chats | `agent:<agentId>:<channel>:group:<id>` | Isolated per group |
|
||||
| Rooms/channels | `agent:<agentId>:<channel>:channel:<id>` | Isolated per room |
|
||||
| Cron jobs | `cron:<job.id>` | Fresh session per run |
|
||||
| Webhooks | `hook:<uuid>` | Unless explicitly overridden |
|
||||
| Node runs | `node-<nodeId>` | Unless explicitly overridden |
|
||||
|
||||
> **Security Warning:** If your agent can receive DMs from **multiple people**, you should strongly consider enabling secure DM mode. Without it, all users share the same conversation context, which can leak private information between users.
|
||||
Telegram forum topics append `:topic:<threadId>` for per-topic isolation.
|
||||
|
||||
**Example of the problem with default settings:**
|
||||
## DM scope and isolation
|
||||
|
||||
- Alice (`<SENDER_A>`) messages your agent about a private topic (for example, a medical appointment)
|
||||
- Bob (`<SENDER_B>`) messages your agent asking "What were we talking about?"
|
||||
- Because both DMs share the same session, the model may answer Bob using Alice's prior context.
|
||||
By default, all direct messages share one session (`dmScope: "main"`) for
|
||||
continuity across devices and channels. This works well for single-user setups,
|
||||
but can leak context when multiple people message your agent.
|
||||
|
||||
**The fix:** Set `dmScope` to isolate sessions per user:
|
||||
### Secure DM mode
|
||||
|
||||
<Warning>
|
||||
If your agent receives DMs from multiple people, you should enable DM isolation.
|
||||
Without it, all users share the same conversation context.
|
||||
</Warning>
|
||||
|
||||
**The problem:** Alice messages about a private topic. Bob asks "What were we
|
||||
talking about?" Because both share a session, the model may answer Bob using
|
||||
Alice's context.
|
||||
|
||||
**The fix:**
|
||||
|
||||
```json5
|
||||
// ~/.openclaw/openclaw.json
|
||||
{
|
||||
session: {
|
||||
// Secure DM mode: isolate DM context per channel + sender.
|
||||
dmScope: "per-channel-peer",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**When to enable this:**
|
||||
### DM scope options
|
||||
|
||||
- You have pairing approvals for more than one sender
|
||||
- You use a DM allowlist with multiple entries
|
||||
- You set `dmPolicy: "open"`
|
||||
- Multiple phone numbers or accounts can message your agent
|
||||
| Value | Key pattern | Best for |
|
||||
| -------------------------- | -------------------------------------------------- | ------------------------------------ |
|
||||
| `main` (default) | `agent:<id>:main` | Single-user, cross-device continuity |
|
||||
| `per-peer` | `agent:<id>:direct:<peerId>` | Multi-user, cross-channel identity |
|
||||
| `per-channel-peer` | `agent:<id>:<channel>:direct:<peerId>` | Multi-user inboxes (recommended) |
|
||||
| `per-account-channel-peer` | `agent:<id>:<channel>:<accountId>:direct:<peerId>` | Multi-account inboxes |
|
||||
|
||||
Notes:
|
||||
### Cross-channel identity linking
|
||||
|
||||
- Default is `dmScope: "main"` for continuity (all DMs share the main session). This is fine for single-user setups.
|
||||
- Local CLI onboarding writes `session.dmScope: "per-channel-peer"` by default when unset (existing explicit values are preserved).
|
||||
- For multi-account inboxes on the same channel, prefer `per-account-channel-peer`.
|
||||
- If the same person contacts you on multiple channels, use `session.identityLinks` to collapse their DM sessions into one canonical identity.
|
||||
- You can verify your DM settings with `openclaw security audit` (see [security](/cli/security)).
|
||||
When using `per-peer` or `per-channel-peer`, the same person messaging from
|
||||
different channels gets separate sessions. Use `session.identityLinks` to
|
||||
collapse them:
|
||||
|
||||
## Gateway is the source of truth
|
||||
```json5
|
||||
{
|
||||
session: {
|
||||
identityLinks: {
|
||||
alice: ["telegram:123456789", "discord:987654321012345678"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
All session state is **owned by the gateway** (the “master” OpenClaw). UI clients (macOS app, WebChat, etc.) must query the gateway for session lists and token counts instead of reading local files.
|
||||
The canonical key replaces `<peerId>` so Alice shares one session across
|
||||
channels.
|
||||
|
||||
- In **remote mode**, the session store you care about lives on the remote gateway host, not your Mac.
|
||||
- Token counts shown in UIs come from the gateway’s store fields (`inputTokens`, `outputTokens`, `totalTokens`, `contextTokens`). Clients do not parse JSONL transcripts to “fix up” totals.
|
||||
**When to enable DM isolation:**
|
||||
|
||||
- Pairing approvals for more than one sender
|
||||
- DM allowlist with multiple entries
|
||||
- `dmPolicy: "open"`
|
||||
- Multiple phone numbers or accounts can message the agent
|
||||
|
||||
Verify settings with `openclaw security audit` (see [security](/cli/security)).
|
||||
Local CLI onboarding writes `per-channel-peer` by default when unset.
|
||||
|
||||
## Session lifecycle
|
||||
|
||||
### Resets
|
||||
|
||||
Sessions are reused until they expire. Expiry is evaluated on the next inbound
|
||||
message:
|
||||
|
||||
- **Daily reset** (default) -- 4:00 AM local time on the gateway host. A
|
||||
session is stale once its last update is before the most recent reset time.
|
||||
- **Idle reset** (optional) -- `idleMinutes` adds a sliding idle window.
|
||||
- **Combined** -- when both are configured, whichever expires first forces a new
|
||||
session.
|
||||
|
||||
Override per session type or channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
session: {
|
||||
reset: {
|
||||
mode: "daily",
|
||||
atHour: 4,
|
||||
idleMinutes: 120,
|
||||
},
|
||||
resetByType: {
|
||||
thread: { mode: "daily", atHour: 4 },
|
||||
direct: { mode: "idle", idleMinutes: 240 },
|
||||
group: { mode: "idle", idleMinutes: 120 },
|
||||
},
|
||||
resetByChannel: {
|
||||
discord: { mode: "idle", idleMinutes: 10080 },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Manual resets
|
||||
|
||||
- `/new` or `/reset` starts a fresh session. The remainder of the message is
|
||||
passed through.
|
||||
- `/new <model>` accepts a model alias, `provider/model`, or provider name
|
||||
(fuzzy match) to set the session model.
|
||||
- If sent alone, OpenClaw runs a short greeting turn to confirm the reset.
|
||||
- Custom triggers: add to `resetTriggers` array.
|
||||
- Delete specific keys from the store or remove the JSONL transcript; the next
|
||||
message recreates them.
|
||||
- Isolated cron jobs always mint a fresh `sessionId` per run.
|
||||
|
||||
## Where state lives
|
||||
|
||||
- On the **gateway host**:
|
||||
- Store file: `~/.openclaw/agents/<agentId>/sessions/sessions.json` (per agent).
|
||||
- Transcripts: `~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl` (Telegram topic sessions use `.../<SessionId>-topic-<threadId>.jsonl`).
|
||||
- The store is a map `sessionKey -> { sessionId, updatedAt, ... }`. Deleting entries is safe; they are recreated on demand.
|
||||
- Group entries may include `displayName`, `channel`, `subject`, `room`, and `space` to label sessions in UIs.
|
||||
- Session entries include `origin` metadata (label + routing hints) so UIs can explain where a session came from.
|
||||
- OpenClaw does **not** read legacy Pi/Tau session folders.
|
||||
All session state is **owned by the gateway**. UI clients (macOS app, WebChat,
|
||||
TUI) query the gateway for session lists and token counts.
|
||||
|
||||
## Maintenance
|
||||
In remote mode, the session store lives on the remote gateway host, not your
|
||||
local machine.
|
||||
|
||||
OpenClaw applies session-store maintenance to keep `sessions.json` and transcript artifacts bounded over time.
|
||||
### Storage
|
||||
|
||||
| Artifact | Path | Purpose |
|
||||
| ------------- | --------------------------------------------------------- | --------------------------------- |
|
||||
| Session store | `~/.openclaw/agents/<agentId>/sessions/sessions.json` | Key-value map of session metadata |
|
||||
| Transcripts | `~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl` | Append-only conversation history |
|
||||
|
||||
The store maps `sessionKey -> { sessionId, updatedAt, ... }`. Deleting entries
|
||||
is safe; they are recreated on demand. Group entries may include `displayName`,
|
||||
`channel`, `subject`, `room`, and `space` for UI labeling.
|
||||
|
||||
Telegram topic sessions use `.../<sessionId>-topic-<threadId>.jsonl`.
|
||||
|
||||
## Session maintenance
|
||||
|
||||
OpenClaw keeps the session store and transcripts bounded over time.
|
||||
|
||||
### Defaults
|
||||
|
||||
- `session.maintenance.mode`: `warn`
|
||||
- `session.maintenance.pruneAfter`: `30d`
|
||||
- `session.maintenance.maxEntries`: `500`
|
||||
- `session.maintenance.rotateBytes`: `10mb`
|
||||
- `session.maintenance.resetArchiveRetention`: defaults to `pruneAfter` (`30d`)
|
||||
- `session.maintenance.maxDiskBytes`: unset (disabled)
|
||||
- `session.maintenance.highWaterBytes`: defaults to `80%` of `maxDiskBytes` when budgeting is enabled
|
||||
| Setting | Default | Description |
|
||||
| ----------------------- | ------------------- | --------------------------------------------------------------- |
|
||||
| `mode` | `warn` | `warn` reports what would be evicted; `enforce` applies cleanup |
|
||||
| `pruneAfter` | `30d` | Stale-entry age cutoff |
|
||||
| `maxEntries` | `500` | Cap entries in sessions.json |
|
||||
| `rotateBytes` | `10mb` | Rotate sessions.json when oversized |
|
||||
| `resetArchiveRetention` | `30d` | Retention for reset archives |
|
||||
| `maxDiskBytes` | unset | Optional sessions-directory budget |
|
||||
| `highWaterBytes` | 80% of maxDiskBytes | Target after cleanup |
|
||||
|
||||
### How it works
|
||||
### Enforcement order (`mode: "enforce"`)
|
||||
|
||||
Maintenance runs during session-store writes, and you can trigger it on demand with `openclaw sessions cleanup`.
|
||||
1. Prune stale entries older than `pruneAfter`.
|
||||
2. Cap entry count to `maxEntries` (oldest first).
|
||||
3. Archive transcript files for removed entries.
|
||||
4. Purge old reset/deleted archives by retention policy.
|
||||
5. Rotate `sessions.json` when it exceeds `rotateBytes`.
|
||||
6. If `maxDiskBytes` is set, enforce disk budget toward `highWaterBytes`.
|
||||
|
||||
- `mode: "warn"`: reports what would be evicted but does not mutate entries/transcripts.
|
||||
- `mode: "enforce"`: applies cleanup in this order:
|
||||
1. prune stale entries older than `pruneAfter`
|
||||
2. cap entry count to `maxEntries` (oldest first)
|
||||
3. archive transcript files for removed entries that are no longer referenced
|
||||
4. purge old `*.deleted.<timestamp>` and `*.reset.<timestamp>` archives by retention policy
|
||||
5. rotate `sessions.json` when it exceeds `rotateBytes`
|
||||
6. if `maxDiskBytes` is set, enforce disk budget toward `highWaterBytes` (oldest artifacts first, then oldest sessions)
|
||||
### Configuration examples
|
||||
|
||||
### Performance caveat for large stores
|
||||
|
||||
Large session stores are common in high-volume setups. Maintenance work is write-path work, so very large stores can increase write latency.
|
||||
|
||||
What increases cost most:
|
||||
|
||||
- very high `session.maintenance.maxEntries` values
|
||||
- long `pruneAfter` windows that keep stale entries around
|
||||
- many transcript/archive artifacts in `~/.openclaw/agents/<agentId>/sessions/`
|
||||
- enabling disk budgets (`maxDiskBytes`) without reasonable pruning/cap limits
|
||||
|
||||
What to do:
|
||||
|
||||
- use `mode: "enforce"` in production so growth is bounded automatically
|
||||
- set both time and count limits (`pruneAfter` + `maxEntries`), not just one
|
||||
- set `maxDiskBytes` + `highWaterBytes` for hard upper bounds in large deployments
|
||||
- keep `highWaterBytes` meaningfully below `maxDiskBytes` (default is 80%)
|
||||
- run `openclaw sessions cleanup --dry-run --json` after config changes to verify projected impact before enforcing
|
||||
- for frequent active sessions, pass `--active-key` when running manual cleanup
|
||||
|
||||
### Customize examples
|
||||
|
||||
Use a conservative enforce policy:
|
||||
Conservative enforce policy:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -136,7 +205,7 @@ Use a conservative enforce policy:
|
||||
}
|
||||
```
|
||||
|
||||
Enable a hard disk budget for the sessions directory:
|
||||
Hard disk budget:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -150,75 +219,26 @@ Enable a hard disk budget for the sessions directory:
|
||||
}
|
||||
```
|
||||
|
||||
Tune for larger installs (example):
|
||||
|
||||
```json5
|
||||
{
|
||||
session: {
|
||||
maintenance: {
|
||||
mode: "enforce",
|
||||
pruneAfter: "14d",
|
||||
maxEntries: 2000,
|
||||
rotateBytes: "25mb",
|
||||
maxDiskBytes: "2gb",
|
||||
highWaterBytes: "1.6gb",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Preview or force maintenance from CLI:
|
||||
Preview or force from CLI:
|
||||
|
||||
```bash
|
||||
openclaw sessions cleanup --dry-run
|
||||
openclaw sessions cleanup --enforce
|
||||
```
|
||||
|
||||
## Session pruning
|
||||
### Performance note
|
||||
|
||||
OpenClaw trims **old tool results** from the in-memory context right before LLM calls by default.
|
||||
This does **not** rewrite JSONL history. See [/concepts/session-pruning](/concepts/session-pruning).
|
||||
Large session stores can increase write-path latency. To keep things fast:
|
||||
|
||||
## Pre-compaction memory flush
|
||||
- Use `mode: "enforce"` in production.
|
||||
- Set both time and count limits (`pruneAfter` + `maxEntries`).
|
||||
- Set `maxDiskBytes` + `highWaterBytes` for hard upper bounds.
|
||||
- Run `openclaw sessions cleanup --dry-run --json` after config changes to
|
||||
preview impact.
|
||||
|
||||
When a session nears auto-compaction, OpenClaw can run a **silent memory flush**
|
||||
turn that reminds the model to write durable notes to disk. This only runs when
|
||||
the workspace is writable. See [Memory](/concepts/memory) and
|
||||
[Compaction](/concepts/compaction).
|
||||
## Send policy
|
||||
|
||||
## Mapping transports → session keys
|
||||
|
||||
- Direct chats follow `session.dmScope` (default `main`).
|
||||
- `main`: `agent:<agentId>:<mainKey>` (continuity across devices/channels).
|
||||
- Multiple phone numbers and channels can map to the same agent main key; they act as transports into one conversation.
|
||||
- `per-peer`: `agent:<agentId>:direct:<peerId>`.
|
||||
- `per-channel-peer`: `agent:<agentId>:<channel>:direct:<peerId>`.
|
||||
- `per-account-channel-peer`: `agent:<agentId>:<channel>:<accountId>:direct:<peerId>` (accountId defaults to `default`).
|
||||
- If `session.identityLinks` matches a provider-prefixed peer id (for example `telegram:123`), the canonical key replaces `<peerId>` so the same person shares a session across channels.
|
||||
- Group chats isolate state: `agent:<agentId>:<channel>:group:<id>` (rooms/channels use `agent:<agentId>:<channel>:channel:<id>`).
|
||||
- Telegram forum topics append `:topic:<threadId>` to the group id for isolation.
|
||||
- Legacy `group:<id>` keys are still recognized for migration.
|
||||
- Inbound contexts may still use `group:<id>`; the channel is inferred from `Provider` and normalized to the canonical `agent:<agentId>:<channel>:group:<id>` form.
|
||||
- Other sources:
|
||||
- Cron jobs: `cron:<job.id>` (isolated) or custom `session:<custom-id>` (persistent)
|
||||
- Webhooks: `hook:<uuid>` (unless explicitly set by the hook)
|
||||
- Node runs: `node-<nodeId>`
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- Reset policy: sessions are reused until they expire, and expiry is evaluated on the next inbound message.
|
||||
- Daily reset: defaults to **4:00 AM local time on the gateway host**. A session is stale once its last update is earlier than the most recent daily reset time.
|
||||
- Idle reset (optional): `idleMinutes` adds a sliding idle window. When both daily and idle resets are configured, **whichever expires first** forces a new session.
|
||||
- Legacy idle-only: if you set `session.idleMinutes` without any `session.reset`/`resetByType` config, OpenClaw stays in idle-only mode for backward compatibility.
|
||||
- Per-type overrides (optional): `resetByType` lets you override the policy for `direct`, `group`, and `thread` sessions (thread = Slack/Discord threads, Telegram topics, Matrix threads when provided by the connector).
|
||||
- Per-channel overrides (optional): `resetByChannel` overrides the reset policy for a channel (applies to all session types for that channel and takes precedence over `reset`/`resetByType`).
|
||||
- Reset triggers: exact `/new` or `/reset` (plus any extras in `resetTriggers`) start a fresh session id and pass the remainder of the message through. `/new <model>` accepts a model alias, `provider/model`, or provider name (fuzzy match) to set the new session model. If `/new` or `/reset` is sent alone, OpenClaw runs a short “hello” greeting turn to confirm the reset.
|
||||
- Manual reset: delete specific keys from the store or remove the JSONL transcript; the next message recreates them.
|
||||
- Isolated cron jobs always mint a fresh `sessionId` per run (no idle reuse).
|
||||
|
||||
## Send policy (optional)
|
||||
|
||||
Block delivery for specific session types without listing individual ids.
|
||||
Block delivery for specific session types without listing individual IDs:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -227,7 +247,6 @@ Block delivery for specific session types without listing individual ids.
|
||||
rules: [
|
||||
{ action: "deny", match: { channel: "discord", chatType: "group" } },
|
||||
{ action: "deny", match: { keyPrefix: "cron:" } },
|
||||
// Match the raw session key (including the `agent:<id>:` prefix).
|
||||
{ action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
|
||||
],
|
||||
default: "allow",
|
||||
@@ -238,73 +257,42 @@ Block delivery for specific session types without listing individual ids.
|
||||
|
||||
Runtime override (owner only):
|
||||
|
||||
- `/send on` → allow for this session
|
||||
- `/send off` → deny for this session
|
||||
- `/send inherit` → clear override and use config rules
|
||||
Send these as standalone messages so they register.
|
||||
- `/send on` -- allow for this session.
|
||||
- `/send off` -- deny for this session.
|
||||
- `/send inherit` -- clear override and use config rules.
|
||||
|
||||
## Configuration (optional rename example)
|
||||
## Inspecting sessions
|
||||
|
||||
```json5
|
||||
// ~/.openclaw/openclaw.json
|
||||
{
|
||||
session: {
|
||||
scope: "per-sender", // keep group keys separate
|
||||
dmScope: "main", // DM continuity (set per-channel-peer/per-account-channel-peer for shared inboxes)
|
||||
identityLinks: {
|
||||
alice: ["telegram:123456789", "discord:987654321012345678"],
|
||||
},
|
||||
reset: {
|
||||
// Defaults: mode=daily, atHour=4 (gateway host local time).
|
||||
// If you also set idleMinutes, whichever expires first wins.
|
||||
mode: "daily",
|
||||
atHour: 4,
|
||||
idleMinutes: 120,
|
||||
},
|
||||
resetByType: {
|
||||
thread: { mode: "daily", atHour: 4 },
|
||||
direct: { mode: "idle", idleMinutes: 240 },
|
||||
group: { mode: "idle", idleMinutes: 120 },
|
||||
},
|
||||
resetByChannel: {
|
||||
discord: { mode: "idle", idleMinutes: 10080 },
|
||||
},
|
||||
resetTriggers: ["/new", "/reset"],
|
||||
store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
|
||||
mainKey: "main",
|
||||
},
|
||||
}
|
||||
```
|
||||
| Method | What it shows |
|
||||
| ------------------------------------ | ---------------------------------------------------------- |
|
||||
| `openclaw status` | Store path, recent sessions |
|
||||
| `openclaw sessions --json` | All entries (filter with `--active <minutes>`) |
|
||||
| `/status` in chat | Reachability, context usage, toggles, cred freshness |
|
||||
| `/context list` or `/context detail` | System prompt contents, biggest context contributors |
|
||||
| `/stop` in chat | Abort current run, clear queued followups, stop sub-agents |
|
||||
|
||||
## Inspecting
|
||||
|
||||
- `openclaw status` — shows store path and recent sessions.
|
||||
- `openclaw sessions --json` — dumps every entry (filter with `--active <minutes>`).
|
||||
- `openclaw gateway call sessions.list --params '{}'` — fetch sessions from the running gateway (use `--url`/`--token` for remote gateway access).
|
||||
- Send `/status` as a standalone message in chat to see whether the agent is reachable, how much of the session context is used, current thinking/fast/verbose toggles, and when your WhatsApp web creds were last refreshed (helps spot relink needs).
|
||||
- Send `/context list` or `/context detail` to see what’s in the system prompt and injected workspace files (and the biggest context contributors).
|
||||
- Send `/stop` (or standalone abort phrases like `stop`, `stop action`, `stop run`, `stop openclaw`) to abort the current run, clear queued followups for that session, and stop any sub-agent runs spawned from it (the reply includes the stopped count).
|
||||
- Send `/compact` (optional instructions) as a standalone message to summarize older context and free up window space. See [/concepts/compaction](/concepts/compaction).
|
||||
- JSONL transcripts can be opened directly to review full turns.
|
||||
|
||||
## Tips
|
||||
|
||||
- Keep the primary key dedicated to 1:1 traffic; let groups keep their own keys.
|
||||
- When automating cleanup, delete individual keys instead of the whole store to preserve context elsewhere.
|
||||
JSONL transcripts can be opened directly to review full turns.
|
||||
|
||||
## Session origin metadata
|
||||
|
||||
Each session entry records where it came from (best-effort) in `origin`:
|
||||
|
||||
- `label`: human label (resolved from conversation label + group subject/channel)
|
||||
- `provider`: normalized channel id (including extensions)
|
||||
- `from`/`to`: raw routing ids from the inbound envelope
|
||||
- `accountId`: provider account id (when multi-account)
|
||||
- `threadId`: thread/topic id when the channel supports it
|
||||
The origin fields are populated for direct messages, channels, and groups. If a
|
||||
connector only updates delivery routing (for example, to keep a DM main session
|
||||
fresh), it should still provide inbound context so the session keeps its
|
||||
explainer metadata. Extensions can do this by sending `ConversationLabel`,
|
||||
`GroupSubject`, `GroupChannel`, `GroupSpace`, and `SenderName` in the inbound
|
||||
context and calling `recordSessionMetaFromInbound` (or passing the same context
|
||||
to `updateLastRoute`).
|
||||
- `label` -- human label (from conversation label + group subject/channel).
|
||||
- `provider` -- normalized channel ID (including extensions).
|
||||
- `from` / `to` -- raw routing IDs from the inbound envelope.
|
||||
- `accountId` -- provider account ID (multi-account).
|
||||
- `threadId` -- thread/topic ID when supported.
|
||||
|
||||
Extensions populate these by sending `ConversationLabel`, `GroupSubject`,
|
||||
`GroupChannel`, `GroupSpace`, and `SenderName` in the inbound context.
|
||||
|
||||
## Tips
|
||||
|
||||
- Keep the primary key dedicated to 1:1 traffic; let groups keep their own
|
||||
keys.
|
||||
- When automating cleanup, delete individual keys instead of the whole store to
|
||||
preserve context elsewhere.
|
||||
- Related: [Session Pruning](/concepts/session-pruning),
|
||||
[Compaction](/concepts/compaction),
|
||||
[Session Tools](/concepts/session-tool),
|
||||
[Session Management Deep Dive](/reference/session-management-compaction).
|
||||
|
||||
Reference in New Issue
Block a user