Session Management in OpenClaw: Routing and Isolation
Learn how OpenClaw routes messages into sessions by source, and how to configure DM isolation for multi-user safety. Essential for operators and developers managing conversation state.
Read this when
- You want to understand session routing and isolation
- You want to configure DM scope for multi-user setups
- You are debugging daily or idle session resets
OpenClaw assigns every incoming message to a session according to its origin: DMs, group chats, cron jobs, and so on. The gateway holds all session state, and UI clients fetch session data from it.
To pick up the same Gateway-managed session in the Control UI, terminal, or a coding harness, refer to Session synchronization and attachment.
For the personal-agent setup, where a single rolling conversation spans all your DM channels and also absorbs group activity and background work, see The main session.
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 |
DM isolation
Continuity is preserved by default because every DM lands in the same session, which suits single-user deployments.
Warning
Turn on DM isolation whenever more than one person can reach your agent. Without it, every user shares one conversation context, meaning Alice's private messages would be exposed to Bob.
{
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 |
Tip
When one person reaches you through several channels, apply
session.identityLinksto collapse their identities into a single canonical peer id so they all share one session.
Dock linked channels
Dock commands redirect the current direct-chat session's reply destination to a different linked channel while keeping the same session alive. Examples, configuration, and troubleshooting appear in Channel docking.
Confirm your configuration with openclaw security audit.
Group and room routing
Where non-direct peers keep their conversation context is governed by session.groupScope:
| Value | Behavior |
|---|---|
per-group (default) | Preserve each group, room, or channel in its existing channel-scoped session |
main | Send groups, rooms, and channels into the agent's main session |
A route binding can supersede the global setting. That proves handy when a specific named team room should feed into the main conversation:
{
bindings: [
{
agentId: "main",
match: {
channel: "slack",
peer: { kind: "channel", id: "C0123TEAM" },
},
session: { groupScope: "main" },
},
],
}
Use peer.kind: "group" for providers that treat the room as a group.
The binding override takes precedence over the global session.groupScope. Only session-key selection is affected: DM routing, mention gating, delivery context, and replies back to the source room all behave as before.
Incognito sessions
Incognito sessions can only be started from the Control UI's New thread screen. Enable Incognito before launching the thread so its session entry, transcript, and compaction state live in process memory rather than on disk. The thread vanishes on Gateway restart, skips OpenClaw's automatic memory flush, and produces no transcript archive when reset or deleted. Codex-backed runs also launch their harness thread in ephemeral mode, so Codex writes no rollout or local session-state files; other model providers rely on HTTP APIs and keep no local provider transcript in OpenClaw.
The incognito- segment is set aside for dashboard, subagent, and hidden internal session keys; openclaw doctor --fix renames any colliding legacy durable keys.
Incognito does not constrain the agent's usual tools. An explicit save request, or any tool-triggered file write, can still persist data outside the incognito session store. Your configured model provider still processes the messages you send, diagnostic logging stays unchanged, and OpenClaw continues recording content-free audit metadata such as HMAC references.
On multi-user gateways, incognito threads appear only to admin-scope connections and never surface through another session's agent session tools or transcript search. That shields them from storage and other gateway-mediated users, but not from the gateway owner or process operator, who can always watch live sessions.
Remember across conversations
Each conversation's local history is governed by separate transcripts. 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 merge their transcripts.
Private direct and persistent explicit UI conversations can provide relevant context to one another. Under default session.groupScope: "per-group", groups and channels remain separate in both directions: their transcripts are not private recall sources, and replies in those conversations get no private transcript context. The current conversation is also left out because its history is already loaded.
This setting leaves session keys, DM scope, routing, delivery, and tools.sessions.visibility untouched. 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
Sessions persist until you reset them by hand or choose an automatic reset policy:
- No automatic reset (default
mode: "none") - sessions keep the samesessionId; 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, default4, 0-23) on the gateway host. Daily freshness is based on when the currentsessionIdstarted, not on later metadata writes. - Idle reset (
mode: "idle") - opt into a new session aftersession.reset.idleMinutesof 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
/newor/resetin 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:
{
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.
Where state lives
- Runtime session rows:
~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite - Archived transcript files:
~/.openclaw/agents/<agentId>/sessions/ - Legacy row migration source:
~/.openclaw/agents/<agentId>/sessions/sessions.json
The per-agent SQLite database tracks several lifecycle timestamps for each session row:
sessionStartedAt: marks the start of the currentsessionId; the daily reset relies on this value.lastInteractionAt: reflects the most recent user or channel interaction, which extends the idle timeout.updatedAt: records the last write to the store row; helpful for enumeration and cleanup, though it does not determine daily or idle reset freshness.
When upgrading from older installations, gateway startup and openclaw doctor --fix automatically migrate legacy sessions.json rows and hot transcript JSONL history into SQLite. Rows missing sessionStartedAt are populated from the legacy transcript JSONL session header whenever that data exists. If an older row also lacks lastInteractionAt, idle freshness defaults to the session start time rather than later bookkeeping timestamps. For explicit inspection or validation, use openclaw doctor --session-sqlite inspect --session-sqlite-all-agents and the Doctor migration sequence.
Session maintenance
OpenClaw controls session storage growth over time through session.maintenance, with these defaults:
{
session: {
maintenance: {
mode: "enforce", // "enforce" applies cleanup; "warn" only reports
pruneAfter: "30d",
archiveDashboardAfter: "7d", // false or 0 disables
maxEntries: 500,
preserveRecent: "7d", // optional; false or omitted disables
},
},
}
For production-scale maxEntries limits, Gateway runtime writes employ a small high-water buffer and then trim 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 avoid paying for a full store cleanup. openclaw sessions cleanup --enforce applies the cap immediately.
maxEntries counts every live session row. Archived or pinned sessions, active or admitted work, model-locked sessions, and durable external conversation pointers are exempt from automatic eviction, yet they still count toward the cap. Cleanup removes the oldest unprotected rows until it reaches maxEntries or runs out of eligible victims. The total can therefore stay above the cap when protected rows alone exceed it or active work temporarily blocks eviction. Cleanup does not unprotect those rows; unarchive, unpin, wait for active work to finish, or explicitly delete sessions you no longer want to retain.
Gateway model-run probe 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 probe 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 group sessions 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, while other session types still require an explicit archive action.
Archived and pinned sessions are user-protected and exempt from every automatic maintenance path, including age pruning, entry caps, model-run cleanup, and disk-budget eviction. They remain protected until you unarchive, unpin, or explicitly delete them.
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 |
Further reading
- Session search - full-text recall across past transcripts
- Session Pruning - trimming tool results
- Compaction - summarizing long conversations
- Session Tools - agent tools for cross-session work
- Session Management Deep Dive - store schema, transcripts, send policy, origin metadata, and advanced config
- Multi-Agent - routing and session isolation across agents
- Background Tasks - how detached work creates task records with session references
- Channel Routing - how inbound messages are routed to sessions