On this page

On this page

Sessions and memory

Session management

OpenClaw routes every inbound message to a session based on where it came from: DMs, group chats, cron jobs, etc. All session state is owned by the gateway; UI clients query the gateway for session data.

To continue the same Gateway-owned session in the Control UI, terminal, or a coding harness, see Session synchronization and attachment.

For the personal-agent default — one rolling conversation shared by all your DM channels, with group activity and background work flowing into it — see The main session.

Delivered command exchanges from the shared dispatcher are conversation history too. OpenClaw appends the user's command and delivered reply as ordinary user/assistant messages. Native menus and command acknowledgements on Discord and Telegram, Slack argument menus, and Mattermost model pickers are also retained after delivery. Later turns and chat history can read these exchanges. Login, pairing, and sensitive /config set or /debug set values are redacted before storage. /new and /reset put their confirmation exchange in the new session; they do not rewrite the old session. /btw and /side stay ephemeral, and message edits or deletions do not rewrite earlier transcript rows.

How messages are routed

Source Behavior
Direct messages Shared session by default
Group chats Isolated per group by default
Rooms/channels Isolated per room by default
Cron jobs Fresh session per run
Webhooks Isolated per hook

Native catalog source IDs, Matrix room and thread IDs, and Signal group IDs are case-sensitive: IDs that differ only by case identify different conversations.

With session.scope: "global", the selected agent still owns its session. The shared key global does not merge different agents' conversations: commands, skills, replies, and background task notifications retain the agent selected by the route or explicit request. Session lists, model filters, previews, and sharing controls also retain the stored conversation's agent, rather than the aggregate view's default agent. Renaming, pinning, or editing session metadata retains the existing message preview without rereading the transcript. New messages, transcript replacements, completed transcript repairs, and cold-storage restoration refresh previews; changes to model selection or fallback state refresh the relevant model facts. Stopping with /stop, deleting, resetting, or archiving a session cancels only that agent's work for the selected conversation. Another agent's active turn and queued messages are preserved even when the agents use the same session key.

DM isolation

By default, all DMs share one session for continuity, which is fine for single-user setups.

json5
{  session: {    dmScope: "per-channel-peer", // isolate by channel + sender  },}

session.dmScope options:

Value Behavior
main (default) All DMs share the main session
per-peer Isolate by sender, across channels
per-channel-peer Isolate by channel + sender (recommended)
per-account-channel-peer Isolate by account + channel + sender

Slack Agent View and Assistant View DMs are the exception: each visible root gets its own :thread:<rootTs> session on top of the base that dmScope selects, so those conversations stay isolated even under main. See Agent View DMs.

Verify your setup with openclaw security audit.

Retired channel docking

Channel docking and manual cross-channel reply focus have been removed. The /dock-* commands no longer move a session's reply destination to another channel.

Use session.identityLinks to associate a person's identities for DM session routing, or thread-bound sessions to keep a supported conversation attached to a subagent. These are separate features; neither restores manual cross-channel docking.

Group and room routing

session.groupScope controls where non-direct peers store conversation context:

Value Behavior
per-group (default) Keep each group, room, or channel in its existing channel-scoped session
main Route groups, rooms, and channels into the agent's main session

A route binding can override the global value. This is useful when only a named team room should join the main conversation:

json5
{  bindings: [    {      agentId: "main",      match: {        channel: "slack",        peer: { kind: "channel", id: "C0123TEAM" },      },      session: { groupScope: "main" },    },  ],}

Use peer.kind: "group" for providers that classify the room as a group. The binding override wins over global session.groupScope. This setting changes session-key selection only: DM routing, mention gating, delivery context, and replies to the source room remain unchanged.

Incognito sessions

Incognito sessions are available only from the Control UI's New thread screen. Turn on Incognito before starting the thread to keep its session entry, transcript, and compaction state in process memory instead of on disk. The thread expires 24 hours after creation or when the Gateway restarts, whichever comes first. Activity does not extend its lifetime. Expiry stops active work and deletes the session and transcript without an archive. Incognito does not run OpenClaw's automatic memory flush, and does not create a transcript archive when you reset or delete it. Codex-backed runs also start their harness thread in ephemeral mode, so Codex writes no rollout or local session-state files; other model providers use HTTP APIs and keep no local provider transcript in OpenClaw.

Delegated work uses its native execution and completion owners. Live subagent activity and completion delivery remain available.

The incognito- segment is reserved for dashboard, subagent, and hidden internal session keys; openclaw doctor --fix renames any colliding legacy durable keys.

Incognito does not restrict the agent's normal tools. An explicit request to save information, or any tool-driven file write, can still persist data outside the incognito session store. Your configured model provider still processes the messages you send. Incognito content is excluded from ordinary Gateway output, delivery and response diagnostics, WebSocket event previews, raw-stream, cache-trace, and Anthropic payload logs. Live replies remain available, and OpenClaw still records operational diagnostics and content-free audit metadata such as HMAC references.

On multi-user gateways, incognito threads are visible only to admin-scope connections and never appear through another session's agent session tools or transcript search. This protects them from storage and other gateway-mediated users, not from the gateway owner or process operator, who can always observe live sessions.

Remember across conversations

Separate transcripts control each conversation's local history. For a personal or fully trusted agent, memory.search.rememberAcrossConversations: true adds an optional retrieval step across that agent's other private conversations; it does not combine their transcripts.

Private direct and persistent explicit UI conversations can supply relevant context to one another. Under default session.groupScope: "per-group", groups and channels stay separate in both directions: their transcripts are not private recall sources, and replies in those conversations do not receive private transcript context. The current conversation is also excluded because its history is already loaded.

This setting does not change session keys, DM scope, routing, delivery, or tools.sessions.visibility. Shared workspace memory in MEMORY.md and memory/*.md also keeps its existing behavior. The current memory provider must support protected private transcript recall; context engines such as Lossless Claw remain independent and can run alongside it. See Active Memory for setup and runtime details.

Session lifecycle

Independent sessions can initialize their first turns concurrently. Requests for the same session remain ordered; parent forks and resets retain their shared-state coordination.

Sessions are reused until you reset them manually or opt into an automatic reset policy:

  • No automatic reset (default mode: "none") - sessions keep the same sessionId; compaction manages the active context as the conversation grows.
  • Daily reset (mode: "daily") - opt into a new session at a configured local hour (session.reset.atHour, default 4, 0-23) on the gateway host. Daily freshness is based on when the current sessionId started, not on later metadata writes.
  • Idle reset (mode: "idle") - opt into a new session after session.reset.idleMinutes of inactivity. Idle freshness is based on the last real user/channel interaction, so heartbeat, cron, and exec system events do not keep the session alive.
  • Manual reset - type /new or /reset in chat. /new <model> also switches the model.

When both daily and idle resets are configured, whichever expires first wins. Heartbeat, cron, exec, and other system-event turns may write session metadata, but those writes do not extend daily or idle reset freshness. When a reset rolls the session, queued system-event notices for the old session are discarded so stale background updates are not prepended to the first prompt in the new session.

Sessions with an active provider-owned CLI session follow the same no-automatic-reset default. Use /reset or configure session.reset explicitly when those sessions should expire on a timer.

Opt into automatic resets globally, then override them per chat type or channel:

json5
{  session: {    reset: { mode: "daily", atHour: 4 },    resetByType: {      group: { mode: "idle", idleMinutes: 120 },      thread: { mode: "daily", atHour: 6 },    },    resetByChannel: {      discord: { mode: "idle", idleMinutes: 10080 },    },  },}

resetByType supports direct, group, and thread. Doctor migrates legacy dm entries to direct and session.idleMinutes to session.reset.idleMinutes; the schema rejects both retired forms.

Gateway restart recovery

When a Gateway restart interrupts an active turn, OpenClaw tries to continue the existing session automatically. Three attempts that fail to start a backend turn exhaust the recovery budget. Once a real backend turn starts, the budget refreshes, so a later Gateway restart does not consume the old allowance. Accepting, queueing, or preparing a resume request alone does not refresh it. CLI backends that do not report turn acceptance refresh the budget only after observed assistant output or tool activity; silent startup does not refresh it.

For a freshly created session's eligible local, idle, restart-safe initial turn, sessions.create commits the session first, then commits the input transcript and restart claim together before acknowledging a started run. Failure or a crash between those commits can leave the created session with no retained input bytes. After the input commits, restart recovery retains that turn even if the client never receives the acknowledgment. Queued input, hook-dependent input, worker placement, idle chat.send, and retries of existing durable input retain their existing admission and recovery behavior. A restart during managed worktree preparation resumes the accepted turn and prepares or reuses its local worktree before starting the agent. Recovery does not inherit the original caller's permission to run worktree setup scripts.

When replaying an interrupted turn, recovery preserves its recorded tool calls and results, including nested tool activity, and reuses the original user message. A completed reply or a later user message closes that turn to replay.

For authenticated operator turns, recovery revalidates the original caller's recorded permissions against current profile, role, access-grant, and device policy. A Control UI administrator can therefore continue authorized automation work after a restart without losing operator.admin. Recovery cannot gain scopes the original caller lacked, and revocation still stops the recovered run. Older interrupted turns without a recorded authorization source remain restricted; send a fresh authenticated message to continue privileged work. Session ownership or a saved display name never grants recovery permissions.

This also covers parent turns started by subagent completion or pause notices. An interrupted parent continues independently of later child completions, and a retry of the same notice joins that recovery instead of starting the turn again. Parents still waiting after yielding to children remain owned by their child batch.

Messages sent while restart recovery is waiting to start stay pending. Once recovery starts, they follow the session's normal message queue policy. You do not need to resend a message just because recovery is waiting for capacity. Stopping or replacing the session still cancels pending work.

If subagent recovery changes session-protection facts while an incoming turn is being prepared, reply initialization refreshes those facts and retries within its existing bounded retry budget. Exhaustion reports an error asking you to retry the message. A saved user message that has not reached the model remains user input when a subagent announcement continues the session; it is not demoted to background context.

If automatic recovery is exhausted, the transcript remains available. Use Resume in new session in WebChat, or /new or /reset in other channels, to start a replacement session.

Where state lives

  • Runtime session rows and transcripts: ~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite by default
  • Archived transcript files: ~/.openclaw/agents/<agentId>/sessions/
  • Legacy row migration source: ~/.openclaw/agents/<agentId>/sessions/sessions.json

Archive discovery uses the selected store, recorded transcript paths, and agent directories. The pre-agent ~/.openclaw/sessions/ directory is no longer an implicit fallback; explicitly configured paths still work.

The session rows in the per-agent SQLite database keep separate lifecycle timestamps:

  • sessionStartedAt: when the current sessionId began; daily reset uses this.
  • lastInteractionAt: last user/channel interaction that extends idle lifetime.
  • updatedAt: last store-row mutation; useful for listing and pruning, but not authoritative for daily/idle reset freshness.

To import legacy sessions.json rows and hot transcript JSONL history from an older installation, stop the Gateway, back up its state, and run openclaw doctor --fix before restarting it. Gateway and local CLI startup use SQLite without importing, restoring, or rewriting legacy session files. If startup finds a legacy store, it refuses readiness and prints the Doctor command for the active profile instead of silently starting with empty history. During Doctor import, rows without sessionStartedAt are resolved from the legacy transcript JSONL session header when available. If an older row also lacks lastInteractionAt, idle freshness falls back to that session start time, not to later bookkeeping writes. Use openclaw doctor --session-sqlite inspect --session-sqlite-all-agents and the Doctor migration sequence for inspection and validation.

Session maintenance

OpenClaw bounds session storage over time via session.maintenance, defaults shown:

json5
{  session: {    maintenance: {      mode: "enforce", // "enforce" applies cleanup; "warn" only reports      pruneAfter: "30d",      archiveDashboardAfter: "7d", // false or 0 disables this dashboard trigger      maxEntries: 5000,      preserveRecent: false, // opt in with a duration such as "7d"    },  },}

For production-sized maxEntries limits, Gateway runtime writes use a small high-water buffer and clean back down to the configured cap in batches. Session store reads do not prune or cap entries during Gateway startup, so startup and isolated cron sessions do not pay for a full store cleanup. openclaw sessions cleanup --enforce applies the cap immediately.

Ordinary entry writes also arm background maintenance at the next age boundary, with a periodic recheck every 30 minutes while the store remains open. This lets eligible sessions age out without further traffic. Writes that cannot change age or count maintenance outcomes skip candidate scans. Automatic planning reads only retention and protection metadata before entering the foreground write queue; cap selection retains only the required oldest eligible entries. The writer checks the prepared store revision before applying changes, so concurrent updates are reconsidered instead of overwritten. If writes invalidate an automatic maintenance plan, its replacement waits for a quiet window after the last write (one second, then two seconds). Three consecutive invalidations pause automatic retries and log the cause; a new entry write can schedule another attempt. warn mode captures the maintenance age fact without constructing or dispatching automatic reclamation.

maxEntries defaults to 5000 unarchived session rows. Archived rows do not consume the cap. Existing explicit limits remain unchanged. When pressure exceeds the cap, cleanup archives the oldest eligible ordinary sessions instead of deleting their transcripts. Synthetic runtime sessions such as cron, hooks, heartbeat, ACP, and sub-agents remain disposable and may be removed. Pinned root sessions, active or admitted work, model-locked sessions, and durable external conversation pointers are protected; the unarchived total can therefore remain above the cap when protected rows alone exceed it.

Root sessions and sessions auto-parented to the agent's Home root can be pinned; genuine child sessions and subagent runs reject pin requests. Persistent child sessions retain their sidebar nesting; subagent runs appear in transcript activity and session transcripts. Existing child pins disappear and no longer protect the session from maintenance.

Gateway model-run check sessions are short-lived by default. Rows matching agent:*:explicit:model-run-<uuid> use fixed 24h retention, but cleanup is pressure-gated: it only removes stale check rows when session-entry maintenance/cap pressure is reached, and runs before the broader stale-entry age cutoff and entry cap. Normal direct, group, thread, cron, hook, heartbeat, ACP, and sub-agent sessions do not inherit this 24h retention.

Maintenance preserves durable external conversation pointers, including direct, group, and thread-scoped chat sessions, while still allowing synthetic cron, hook, heartbeat, ACP, and sub-agent entries to age out.

Shared or high-volume installations can set preserveRecent to protect recently active interactive sessions and every SQLite history generation owned by those sessions. The option is disabled when omitted or set to false, so personal installations keep the normal oldest-first policy. Synthetic model-run, cron, hook, heartbeat, ACP, and sub-agent sessions remain eligible for bounded cleanup. Protection can temporarily keep the store above its entry or disk target; it expires after the configured inactivity window.

Recent-session protection does not change managed-worktree garbage collection; durable dashboard sessions auto-archive after 7 days of inactivity by default, and pruneAfter archives other eligible durable sessions in place after 30 days by default, preserving their session ids and transcript generations. Disposable automation rows still delete at their age cutoff.

Pinned sessions and manual, legacy, age-retention, stale-dashboard, or recovery archives are user-protected and exempt from automatic maintenance. Sessions archived because maxEntries was reached record that reason and remain searchable/restorable until physical usage exceeds maxDiskBytes; disk-budget cleanup may then delete the oldest cap archives after cheaper artifacts and unreferenced history are exhausted. Sessions without a recorded archive reason remain protected.

After skipping a history generation or archived session, disk-budget cleanup rechecks physical usage before considering another deletion. A measurement failure stops the sweep.

Background disk-budget checks run at most every 30 minutes on entry writes. Delete and reset operations can request a check sooner, but repeated requests coalesce to at most one forced check per minute per store. If cleanup exhausts eligible history and the store remains over budget, automatic checks back off for 30 minutes and log one warning until the pressure clears or the budget changes. The warning recommends raising session.maintenance.maxDiskBytes or exporting and deleting unneeded sessions. Checks resume on subsequent activity; openclaw sessions cleanup --enforce remains available immediately.

Cleanup first tries to truncate the WAL without waiting for readers. If readers prevent truncation, a complete PASSIVE checkpoint is sufficient: frames relevant to cleanup must have reached the main database, even if the WAL file remains allocated. Retained WAL bytes still count toward the physical budget. Successful cleanup logs one outcome with the before/after bytes and removal counts.

An incomplete SQLite WAL checkpoint is a separate deferral. Cleanup preserves archives and history instead of deleting more data behind the blocked checkpoint. The result records deferredReason: "checkpoint-incomplete", WAL bytes before and after, and the checkpoint outcome. Automatic and manual budget passes remain deferred until the checkpoint owner records completion after the pending cleanup work. Newer frames from concurrent writes do not erase that completion. Each SQLite cleanup deletion or vacuum commit requires a new completion before further pruning, so a reader pinning those changes still defers cleanup. Ordering uses monotonic time; elapsed time, a budget change, or a system clock correction cannot release the gate. Normal periodic checkpointing continues, and subsequent activity can resume cleanup after recovery.

Look for session history disk budget deferred until a completed WAL checkpoint is observed in the Gateway log. Its checkpoint fields include bounded operation names for explicitly tracked readers, connection and thread IDs, and open-transaction flags. Collecting these facts does not keep connections open or change worker retirement. They do not prove which connection holds the blocking SQLite read mark. Raw native statements outside explicit reader tracking, other workers, and other processes can remain unidentified. No transcript contents, SQL text, or bound values are included.

If you previously used DM isolation and later returned session.dmScope to main, preview stale peer-keyed DM rows with openclaw sessions cleanup --dry-run --fix-dm-scope. Applying the same flag retires those old direct-DM rows and keeps their transcripts as deleted archives.

Preview any maintenance run with openclaw sessions cleanup --dry-run.

Inspecting sessions

Command Shows
openclaw status Session store path and recent activity
openclaw sessions --json All sessions (filter with --active <minutes>)
/status in chat Context usage, model, and toggles
/context list What is in the system prompt
Was this useful?