Session Management Deep Dive
Deep dive: session store + transcripts, lifecycle, and (auto)compaction internals
This document explains how OpenClaw manages sessions end-to-end:
- Session routing (how inbound messages map to a sessionKey)
- Session store (sessions.json) and what it tracks
- Transcript persistence (*.jsonl) and its structure
- Transcript hygiene (provider-specific fixups before runs)
- Context limits (context window vs tracked tokens)
- Compaction (manual + auto-compaction) and where to hook pre-compaction work
- Silent housekeeping (e.g. memory writes that shouldn't produce user-visible output)
If you want a higher-level overview first, start with:
- /concepts/session
- /concepts/compaction
- /concepts/session-pruning
- /reference/transcript-hygiene
Two persistence layers
OpenClaw persists sessions in two layers:
1. Session store (sessions.json)
- Key/value map: sessionKey -> SessionEntry
- Small, mutable, safe to edit (or delete entries)
- Tracks session metadata (current session id, last activity, toggles, token counters, etc.)
2. Transcript (<sessionId>.jsonl)
- Append-only transcript with tree structure (entries have id + parentId)
- Stores actual conversation + tool calls + compaction summaries
- Used to rebuild model context for future turns
Session keys (`sessionKey`)
A sessionKey identifies _which conversation bucket_ you're in (routing + isolation).
Common patterns:
- Main/direct chat (per agent): agent:<agentId>:<mainKey> (default main)
- Group: agent:<agentId>:<channel>:group:<id>
- Room/channel (Discord/Slack): agent:<agentId>:<channel>:channel:<id> or ...:room:<id>
- Cron: cron:<job.id>
- Webhook: hook:<uuid> (unless overridden)
The canonical rules are documented at /concepts/session.
Session store schema (`sessions.json`)
The store's value type is SessionEntry in src/config/sessions.ts.
Key fields (not exhaustive):
- sessionId: current transcript id (filename is derived from this unless sessionFile is set)
- updatedAt: last activity timestamp
- sessionFile: optional explicit transcript path override
- chatType: direct | group | room (helps UIs and send policy)
- provider, subject, room, space, displayName: metadata for group/channel labeling
Toggles:
- thinkingLevel, verboseLevel, reasoningLevel, elevatedLevel
- sendPolicy (per-session override)
Model selection:
- providerOverride, modelOverride, authProfileOverride
Token counters (best-effort / provider-dependent):
- inputTokens, outputTokens, totalTokens, contextTokens
- compactionCount: how often auto-compaction completed for this session key
- memoryFlushAt: timestamp for last pre-compaction memory flush
- memoryFlushCompactionCount: compaction count when last flush ran
The store is safe to edit, but Gateway is authority: it may rewrite or rehydrate entries as sessions run.
Context windows vs tracked tokens
Two different concepts matter:
1. Model context window: hard cap per model (tokens visible to model)
2. Session store counters: rolling stats written into sessions.json (used for /status and dashboards)
If you're tuning limits:
- The context window comes from model catalog (and can be overridden via config).
- contextTokens in store is a runtime estimate/reporting value; don't treat it as a strict guarantee.
For more, see /token-use.
When auto-compaction happens (Pi runtime)
In embedded Pi agent, auto-compaction triggers in two cases:
1. Overflow recovery: model returns a context overflow error β compact β retry.
2. Threshold maintenance: after a successful turn, when:
contextTokens > contextWindow - reserveTokens
Where:
- contextWindow is model's context window
- reserveTokens is headroom reserved for prompts + next model output
These are Pi runtime semantics (OpenClaw consumes events, but Pi decides when to compact).
User-visible surfaces
You can observe compaction and session state via:
- /status (in any chat session)
- openclaw status (CLI)
- openclaw sessions / sessions --json
- Verbose mode: π§Ή Auto-compaction complete + compaction count
Pre-compaction "memory flush" (implemented)
Goal: before auto-compaction happens, run a silent agentic turn that writes durable state to disk (e.g. memory/YYYY-MM-DD.md in agent workspace) so compaction can't erase critical context.
OpenClaw uses pre-threshold flush approach:
1. Monitor session context usage.
2. When it crosses a "soft threshold" (below Pi's compaction threshold), run a silent "write memory now" directive to agent.
3. Use NO_REPLY so the user sees nothing.
Config (agents.defaults.compaction.memoryFlush):
- enabled (default: true)
- softThresholdTokens (default: 4000)
- prompt (user message for the flush turn)
- systemPrompt (extra system prompt appended for the flush turn)
Notes:
- The default prompt/system prompt include a NO_REPLY hint to suppress delivery.
- The flush runs once per compaction cycle (tracked in sessions.json).
- The flush runs only for embedded Pi sessions (CLI backends skip it).
- The flush is skipped when the session workspace is read-only (workspaceAccess: "ro" or "none").
- See Memory for the workspace file layout and write patterns.
Pi also exposes a session_before_compact hook in the extension API, but OpenClaw's flush logic lives on the Gateway side today.
Troubleshooting checklist
- Session key wrong? Start with /concepts/session and confirm the sessionKey in /status.
- Store vs transcript mismatch? Confirm the Gateway host and the store path from openclaw status.
- Compaction spam? Check:
- model context window (too small)
- compaction settings (reserveTokens too high for the model window can cause earlier compaction)
- tool-result bloat: enable/tune session pruning
- Silent turns leaking? Confirm the reply starts with NO_REPLY (exact token) and you're on a build that includes the streaming suppression fix.