Sessions
A session is one conversation between a sender (or group) and an agent: its message history, tool results, and settings. WednesdayAI keeps one direct-chat session per agent as the primary session and gives group chats, threads, and cron jobs their own keys. All session state is owned by the gateway — UI clients query the gateway rather than reading files. This page covers session keys and DM scoping, the reset lifecycle, where state lives on disk, external storage backends, maintenance, and the identity/privacy boundary.Session keys
Direct chats followsession.dmScope (default "main"):
Group and channel chats always isolate:
agent:<agentId>:<channel>:group:<id> (rooms/channels use :channel:<id>). Telegram forum topics append :topic:<threadId>. Other sources: isolated cron jobs use cron:<job.id>, webhooks hook:<uuid>.
session.scope: "global" collapses everything onto a single global key. session.mainKey renames the main session key (default main).
Secure DM scoping
session.dmScope: "per-channel-peer" by default when the key is unset; explicit values are preserved. Verify with openclaw security audit.
Linking one person across channels
session.identityLinks maps platform-prefixed ids to a canonical identity, so the same person shares one DM session under per-peer-style scopes:
Reset lifecycle
Sessions are reused until they expire; expiry is evaluated on the next inbound message.- Daily reset (default): a session is stale once its last update predates the most recent daily boundary — 4:00 AM local time on the gateway host (
session.reset.atHour). - Idle reset (optional):
session.reset.idleMinutesadds a sliding idle window. When both are set, whichever expires first wins. - Legacy idle-only: setting
session.idleMinuteswithout anysession.reset/resetByTypeconfig keeps idle-only mode for backward compatibility. - Per-type overrides:
session.resetByTypeoverrides the policy fordirect,group, andthreadsessions (dmis a deprecated alias ofdirect). - Per-channel overrides:
session.resetByChanneloverrides the policy for a channel and takes precedence overreset/resetByType. - Manual reset: exact
/newor/reset(plus any triggers insession.resetTriggers) starts a fresh session id;/new <model>also switches model. Isolated cron jobs always mint a fresh session id per run.
systemctl --user restart openclaw-gateway; macOS: wednesdayai gateway restart --deep). Expiry itself is evaluated lazily on the next message — no restart-free timer fires.
Where state lives
On the gateway host, per agent:- Store index:
~/.openclaw/agents/<agentId>/sessions/sessions.json— a map ofsessionKey -> { sessionId, updatedAt, ... }. Deleting entries is safe; they are recreated on demand. - Transcripts:
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl(Telegram topic sessions use<sessionId>-topic-<threadId>.jsonl). - Group entries may carry
displayName,channel,subject,room,space, andoriginmetadata for UI labelling.
inputTokens, outputTokens, totalTokens, contextTokens) — clients never parse JSONL transcripts to compute totals.
External storage
By default sessions live as local JSONL files plus thesessions.json index (the fs-jsonl path). Configure session.storage for durable or shared storage:
session.storage is set but backend is not: a configured databaseUrl selects postgres; otherwise sqlite (default path <state dir>/sessions.db, i.e. ~/.openclaw/sessions.db). Without any session.storage block, sessions run on the fs-jsonl default.
The configured backend is the single source of truth for transcripts. Switching from JSONL to SQLite/Postgres needs no manual migration — the store index is rebuilt on demand, the database schema converges automatically on startup, and existing .jsonl files may be backfilled once (disable with session.storage.migration.validateOnStart: false for a clean cut-over). See Redis session cache for the hot-cache layer and Session consumer claims for the durable claim ledger both database backends carry.
Maintenance
session.maintenance bounds sessions.json and transcript artifacts. Maintenance runs during session-store writes and on demand via openclaw sessions cleanup.
"enforce" order: prune stale entries → cap entry count → archive orphaned transcripts → purge old *.deleted.* / *.reset.* archives → rotate sessions.json → enforce disk budget toward highWaterBytes.
openclaw sessions cleanup --dry-run --json.
Identity and privacy
Plugins and providers never receive raw identity configuration. The boundary:session.identityLinksstays core configuration. Plugin contexts expose resolved fields (canonicalIdentity,senderIsOwner, andidentitySource— one of"sender","parent-session","job-config"describing how the identity was resolved), never the rawidentityLinksmap.channelConversationIdidentifies the transport conversation or thread (legacy plugin fields namedconversationIdrefer to this channel identity).providerConversationIdis an opaque SHA-256-derived correlation id WednesdayAI mints for model calls (prefixed hash ofagent + session, 32 hex chars). It is not a storage column; store it only as an opaque value.hashedUserIdis a one-way hash of the sender identity — plugins can correlate users without seeing phone numbers or handles.turn.turnIdidentifies one user turn (same value asrunIdin the v1 plugin contract). User and assistant entries for one exchange share it — dedupe append fan-out with turn id + entry id or raw hash.sessionId/sessionKeymay be absent during very earlymessage_receivedhooks; WednesdayAI omits them rather than fabricating values.- Heartbeat and cron attribution:
heartbeat.identity(or a cron job’sidentity) attributes a system run to a registered user — it must case-insensitively match asession.identityLinkskey at run time; absent means a system run with no identity. Matches resolve toidentitySource: "job-config".
Inspecting
openclaw status— store path and recent sessions.openclaw sessions --json— every entry (filter with--active <minutes>).openclaw gateway call sessions.list --params '{}'— sessions from a running (or remote, via--url/--token) gateway./statusin chat — reachability, context usage, current toggles./context list//context detail— system prompt and workspace-file contributions.
Troubleshooting
Two different people share context in DMs —dmScope: "main" is the default and pools all DMs into one session. Set dmScope: "per-channel-peer" (or "per-account-channel-peer" for multi-account) and confirm with openclaw security audit.
Session does not reset at the expected daily time — the daily boundary uses the gateway host’s local time (default 4:00 AM). If the host timezone differs from yours, adjust session.reset.atHour or the host timezone, then restart the gateway.
sessions.json grows large and writes slow down — enable mode: "enforce" with both pruneAfter and maxEntries limits; add maxDiskBytes + highWaterBytes for hard bounds. Preview with openclaw sessions cleanup --dry-run --json.
Related
- System prompt — what each session starts with
- Session consumer claims — durable session-work ledger for plugins
- Redis session cache — Postgres hot-cache layer
- Heartbeat — periodic runs on the main session