Session Management & Compaction (Deep Dive)
This document explains how Clawdbot 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 - 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)
Source of truth: the Gateway
Clawdbot is designed around a single Gateway process that owns session state.- UIs (macOS app, web Control UI, TUI) should query the Gateway for session lists and token counts.
- In remote mode, session files are on the remote host; “checking your local Mac files” won’t reflect what the Gateway is using.
Two persistence layers
Clawdbot persists sessions in two layers:-
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.)
- Key/value map:
-
Transcript (
<sessionId>.jsonl)- Append-only transcript with tree structure (entries have
id+parentId) - Stores the actual conversation + tool calls + compaction summaries
- Used to rebuild the model context for future turns
- Append-only transcript with tree structure (entries have
On-disk locations
Per agent, on the Gateway host:- Store:
~/.clawdbot/agents/<agentId>/sessions/sessions.json - Transcripts:
~/.clawdbot/agents/<agentId>/sessions/<sessionId>.jsonl- Telegram topic sessions:
.../<sessionId>-topic-<threadId>.jsonl
- Telegram topic sessions:
src/config/sessions.ts.
Session keys (sessionKey)
A sessionKey identifies which conversation bucket you’re in (routing + isolation).
Common patterns:
- Main/direct chat (per agent):
agent:<agentId>:<mainKey>(defaultmain) - Group:
agent:<agentId>:<provider>:group:<id> - Room/channel (Discord/Slack):
agent:<agentId>:<provider>:channel:<id>or...:room:<id> - Cron:
cron:<job.id> - Webhook:
hook:<uuid>(unless overridden)
Session ids (sessionId)
Each sessionKey points at a current sessionId (the transcript file that continues the conversation).
Rules of thumb:
- Reset (
/new,/reset) creates a newsessionIdfor thatsessionKey. - Idle expiry (
session.idleMinutes) creates a newsessionIdwhen a message arrives after the idle window.
initSessionState() in src/auto-reply/reply/session.ts.
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 unlesssessionFileis set)updatedAt: last activity timestampsessionFile: optional explicit transcript path overridechatType:direct | group | room(helps UIs and send policy)provider,subject,room,space,displayName: metadata for group/channel labeling- Toggles:
thinkingLevel,verboseLevel,reasoningLevel,elevatedLevelsendPolicy(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
Transcript structure (*.jsonl)
Transcripts are managed by @mariozechner/pi-coding-agent’s SessionManager.
The file is JSONL:
- First line: session header (
type: "session", includesid,cwd,timestamp, optionalparentSession) - Then: session entries with
id+parentId(tree)
message: user/assistant/toolResult messagescustom_message: extension-injected messages that do enter model context (can be hidden from UI)custom: extension state that does not enter model contextcompaction: persisted compaction summary withfirstKeptEntryIdandtokensBeforebranch_summary: persisted summary when navigating a tree branch
SessionManager to read/write them.
Context windows vs tracked tokens
Two different concepts matter:- Model context window: hard cap per model (tokens visible to the model)
- Session store counters: rolling stats written into
sessions.json(used for /status and dashboards)
- The context window comes from the model catalog (and can be overridden via config).
contextTokensin the store is a runtime estimate/reporting value; don’t treat it as a strict guarantee.
Compaction: what it is
Compaction summarizes older conversation into a persistedcompaction entry in the transcript and keeps recent messages intact.
After compaction, future turns see:
- The compaction summary
- Messages after
firstKeptEntryId
When auto-compaction happens (Pi runtime)
In the embedded Pi agent, auto-compaction triggers in two cases:- Overflow recovery: the model returns a context overflow error → compact → retry.
- Threshold maintenance: after a successful turn, when:
contextTokens > contextWindow - reserveTokens
Where:
contextWindowis the model’s context windowreserveTokensis headroom reserved for prompts + the next model output
Compaction settings (reserveTokens, keepRecentTokens)
Pi’s compaction settings live in Pi settings:
- If
compaction.reserveTokens < 20000, Clawdbot bumps it to 20000. - If it’s already higher, Clawdbot leaves it alone.
ensurePiCompactionReserveTokens() in src/agents/pi-settings.ts (called from src/agents/pi-embedded-runner.ts).
User-visible surfaces
You can observe compaction and session state via:/status(in any chat session)clawdbot status(CLI)clawdbot sessions/sessions --json- Verbose mode:
🧹 Auto-compaction complete+ compaction count
Silent housekeeping (NO_REPLY)
Clawdbot supports “silent” turns for background tasks where the user should not see intermediate output.
Convention:
- The assistant starts its output with
NO_REPLYto indicate “do not deliver a reply to the user”. - Clawdbot strips/suppresses this in the delivery layer.
2026.1.10, Clawdbot also suppresses draft/typing streaming when a partial chunk begins with NO_REPLY, so silent operations don’t leak partial output mid-turn.
Pre-compaction “memory flush” (design)
Goal: before auto-compaction happens, run a short sequence of turns that writes durable state to disk (e.g.memory/YYYY-MM-DD.md in the agent workspace) so compaction can’t erase critical context.
Two viable hooks:
-
Pre-threshold flush (Clawdbot-side)
- Monitor session context usage.
- When it crosses a “soft threshold” (below Pi’s real compaction threshold), enqueue a silent “write memory now” directive to the agent.
- Use
NO_REPLYso the user sees nothing.
-
Pi extension hook (
session_before_compact)- Pi’s extension API exposes a
session_before_compactevent that receives compaction preparation details and can cancel or replace compaction. - Clawdbot can ship an extension that reacts here and performs housekeeping (and/or produces a custom compaction result).
- Pi’s extension API exposes a
Troubleshooting checklist
- Session key wrong? Start with /concepts/session and confirm the
sessionKeyin/status. - Store vs transcript mismatch? Confirm the Gateway host and the store path from
clawdbot status. - Compaction spam? Check:
- model context window (too small)
- compaction settings (
reserveTokenstoo 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.