Sessions

One-line summary

The session system manages conversation persistence as append-only JSONL transcript files, with a layered store for metadata, maintenance, and multi-agent coordination.

Responsibilities

  • Persist conversation transcripts as append-only JSONL files (~/.openclaw/agents/<id>/sessions/<sessionId>.jsonl)
  • Maintain a session store with metadata, model overrides, delivery context, and labels
  • Perform automated maintenance: prune stale entries, cap entry count, rotate large files, enforce disk budgets
  • Parse and classify session keys (direct/group/channel, cron, subagent, ACP, threaded)
  • Track input provenance (external user vs inter-session vs internal system)

Architecture diagram

Key source files

FileLinesRole
src/config/sessions/store.ts1154Core store: load/save with atomic file ops, caching (TTL), maintenance (prune, cap, rotate), file locking, key normalization
src/gateway/session-utils.ts875Gateway API layer: session listing/filtering, multi-agent store consolidation, title derivation, model resolution
src/config/sessions/transcript.ts159Append assistant messages to JSONL transcript, emit update events
src/sessions/session-key-utils.ts128Parse session keys, detect cron/subagent/ACP/threaded sessions, derive chat type
src/sessions/send-policy.ts124Resolve send policy (allow/deny) based on channel, chat type, session key patterns
src/sessions/model-overrides.ts77Apply model/provider overrides to session entries
src/sessions/input-provenance.ts80Track message origin: external_user, inter_session, internal_system
src/sessions/level-overrides.ts33Apply verbose level overrides
src/sessions/transcript-events.ts26Event emitter for transcript updates
src/sessions/session-label.ts21Label validation (max 64 chars)
src/config/sessions/disk-budget.tsDisk budget enforcement for session storage
src/config/sessions/paths.tsSession file path resolution
src/config/sessions/metadata.tsMetadata derivation from inbound messages

Data flow

Inbound (writing)

User message arrives

Auto-Reply Pipeline processes message

Pi Agent Runtime executes LLM call

Pi SDK appends turn to JSONL transcript file
  (user message + assistant response + tool calls + tool results)

transcript.ts appends assistant metadata

transcript-events.ts emits update event

store.ts records metadata (updatedAt, deliveryContext, model, etc.)

Outbound (reading)

Next user message arrives

Pi Agent Runtime loads FULL JSONL transcript as AgentMessage[]

estimateTokens() calculates history size

If history > contextWindow - reserveTokens → trigger compaction

Otherwise: send entire history as input to LLM

Maintenance (background)

saveSessionStore() triggers maintenance:
  1. pruneStaleEntries() — remove entries older than maxAge
  2. capEntryCount() — keep most recent N entries
  3. rotateSessionFile() — rotate files exceeding maxSize
  4. disk-budget.ts — enforce total disk budget

Modes: "warn" (log) or "enforce" (delete)

Token optimization impact

The session system is the single largest token consumer (60-80% of input tokens):

MechanismToken impactDetails
Full history loading40,000-150,000 tokens/callEvery turn loads the ENTIRE JSONL transcript. No windowing.
Append-only growthLinear growth per turnEach tool call adds ~500-2,000 tokens to history
No built-in truncationGrows until compaction triggersCompaction only triggers at ~180k tokens (200k context - 20k reserve)
Tool results in historyCan be huge per entryFile reads, web fetches stored verbatim in transcript

Key insight for optimization

The JSONL transcript format means every past tool result is re-sent on every LLM call until compaction. A single web_fetch or read tool result of 5,000 tokens stays in history for all subsequent calls. With 10 such tool calls, that's 50,000 tokens of stale tool results per call.

Session store maintenance vs token optimization

The store maintenance (pruning, capping, rotating) operates on session metadata, not transcript content. It controls how many sessions exist on disk, not how large each session's transcript grows. Transcript size is controlled by the compaction system in src/agents/compaction.ts.

How it connects to other modules

  • Depends on:

    • @mariozechner/pi-coding-agentSessionManager, CURRENT_SESSION_VERSION
    • config/ — maintenance settings, paths
    • agents/session-write-lock.ts — file locking for concurrent access
    • channels/chat-type.ts — chat type normalization
    • routing/session-key.ts — key normalization
  • Depended by:

    • agents/pi-embedded-runner/ — loads transcript for LLM calls
    • auto-reply/ — session entry creation, model/level overrides
    • gateway/ — session listing API for Control UI
    • cron/ — session targeting for scheduled jobs
    • memory/ — session-aware memory indexing

Session key format

agent:<agentId>:<channel>:<chatType>:<identifier>

Examples:
  agent:default:telegram:direct:12345
  agent:default:discord:group:guild-channel
  agent:default:cron:run:job-123
  agent:default:subagent:depth-1:parent-key

Detection helpers:

  • isCronRunSessionKey() — matches cron:run: pattern
  • isSubagentSessionKey() — matches subagent: or checks depth
  • isAcpSessionKey() — matches ACP protocol sessions
  • getSubagentDepth() — returns nesting level

My blind spots

  • Exact JSONL format and how Pi SDK reads/writes transcript lines
  • disk-budget.ts enforcement strategy — how it decides which sessions to evict
  • File locking implementation in agents/session-write-lock.ts — race conditions?
  • How rotateSessionFile() works — does it archive or truncate?
  • Whether Gateway API supports pagination for session listing (relevant for UI performance)
  • session-utils.fs.ts — filesystem operations and archive utilities
  • None yet

Change frequency

  • store.ts: High — maintenance logic, normalization, and concurrent access handling evolve frequently
  • session-utils.ts: High — gateway API features (filtering, multi-agent, display) expand with UI needs
  • session-key-utils.ts: Medium — new session types added as features ship (ACP, threaded)
  • transcript.ts: Low — append-only logic is stable
  • send-policy.ts: Low — policy rules are rarely modified