Skip to content

Master OpenClaw Session Management & Compaction

You should think of the Gateway process as the single point of authority for session state. It owns everything related to how your sessions are handled and stored.

  • UIs like the macOS app, the web Control UI, or the TUI need to query the Gateway to get session lists and token counts.
  • If you are running in remote mode, the session files live on the remote host. Checking your local files on your Mac won’t show you what the Gateway is actually using.

OpenClaw saves your session data using two different layers to keep things organized:

  1. Session store (sessions.json)

    • This is a key/value map that links a sessionKey to a SessionEntry.
    • It is small, mutable, and safe for you to edit or delete entries.
    • It tracks metadata like the current session ID, last activity, toggles, and token counters.
  2. Transcript (<sessionId>.jsonl)

    • This is an append-only transcript that uses a tree structure where entries have an id and a parentId.
    • It stores the actual conversation, tool calls, and compaction summaries.
    • OpenClaw uses this to rebuild the model context for future turns.

On the Gateway host, you can find these files organized per agent:

  • Store: ~/.openclaw/agents/<agentId>/sessions/sessions.json
  • Transcripts: ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
    • Telegram topic sessions: .../<sessionId>-topic-<threadId>.jsonl

OpenClaw handles the resolution of these paths through src/config/sessions.ts.

You can use automatic maintenance controls under session.maintenance to manage your sessions.json and transcript files. You have several options to configure:

  • mode: Set this to warn (default) or enforce.
  • pruneAfter: This is the age cutoff for stale entries, which defaults to 30d.
  • maxEntries: This caps the number of entries in sessions.json, defaulting to 500.
  • rotateBytes: This rotates sessions.json when it gets too large, with a default of 10mb.
  • resetArchiveRetention: This manages how long to keep *.reset.<timestamp> transcript archives. It defaults to the same as pruneAfter, but you can set it to false to disable cleanup.
  • maxDiskBytes: This is an optional budget for your sessions directory.
  • highWaterBytes: This is the target usage after a cleanup, defaulting to 80% of maxDiskBytes.

When you set the mode to enforce, OpenClaw follows a specific order to clean up your disk budget:

  1. It removes the oldest archived or orphan transcript artifacts first.
  2. If usage is still above the target, it evicts the oldest session entries and their transcript files until usage is at or below highWaterBytes.

If you use mode: "warn", OpenClaw will report what it would evict but it won’t actually change your files. You can also run maintenance manually whenever you want:

Terminal window
openclaw sessions cleanup --dry-run
openclaw sessions cleanup --enforce

AI Setup Assistant

When you run isolated cron jobs, they create their own session entries and transcripts. You can manage how long these stay around using dedicated retention controls:

  • cron.sessionRetention (default 24h) handles pruning old isolated cron sessions from your store. You can set this to false to disable it.
  • cron.runLog.maxBytes and cron.runLog.keepLines take care of the ~/.openclaw/cron/runs/<jobId>.jsonl files. The defaults are 2_000_000 bytes and 2000 lines.

You can think of a sessionKey as the identifier for your conversation bucket. It is what the system uses for routing and keeping different chats isolated from each other.

Here are the common patterns you’ll see:

  • 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 you override it)

If you want to see the full canonical rules, they are documented at /concepts/session.

Every sessionKey points to a specific sessionId, which is the actual transcript file where your conversation continues.

Here is how the logic works:

  • Reset: Using /new or /reset creates a fresh sessionId for that key.
  • Daily reset: By default, at 4:00 AM local time on your gateway host, the next message you send will trigger a new sessionId.
  • Idle expiry: You can use session.reset.idleMinutes (or the legacy session.idleMinutes) to start a new session if a message arrives after the idle window. If you have both daily and idle resets configured, the one that happens first wins.
  • Thread parent fork guard: If a parent session gets too big, session.parentForkMaxTokens (default 100000) stops the system from forking the transcript so the new thread starts fresh. You can set this to 0 to turn it off.

If you’re curious about the implementation details, this all happens in initSessionState() inside src/auto-reply/reply/session.ts.

The session store uses the SessionEntry type found in src/config/sessions.ts. It holds all the important details about your active sessions.

Some of the key fields include:

  • sessionId: The current transcript ID.
  • updatedAt: The timestamp of the last activity.
  • sessionFile: An optional path if you want to override the transcript location.
  • chatType: This can be direct, group, or room, which helps the UI and send policies.
  • Metadata: Fields like provider, subject, room, space, and displayName help with labeling.
  • Toggles: You can set levels for thinkingLevel, verboseLevel, reasoningLevel, and elevatedLevel, or override the sendPolicy.
  • Model selection: Overrides for providerOverride, modelOverride, and authProfileOverride.
  • Token counters: It tracks inputTokens, outputTokens, totalTokens, and contextTokens.
  • Compaction: compactionCount tracks how often auto-compaction has finished, while memoryFlushAt and memoryFlushCompactionCount track your last memory flush.

You can edit this store manually if you need to, but remember that the Gateway is the ultimate authority. It might rewrite or update entries while sessions are running.

You will find that transcripts are managed by the SessionManager within @mariozechner/pi-coding-agent. The system uses the JSONL format to keep things organized.

The file structure follows a specific order:

  • The first line is the session header. It uses type: "session" and includes the id, cwd, and timestamp. You might also see an optional parentSession here.
  • The following lines are session entries. These use an id and a parentId to build a tree structure.

You should be aware of these specific entry types:

  • message: These are your standard user, assistant, or toolResult messages.
  • custom_message: These are messages injected by extensions. They enter the model context, but you can hide them from the UI.
  • custom: This tracks extension state that does not enter the model context.
  • compaction: This is a persisted compaction summary that includes firstKeptEntryId and tokensBefore.
  • branch_summary: This is a summary saved when you are navigating a specific branch in the tree.

OpenClaw does not try to “fix up” these transcripts. Instead, the Gateway uses the SessionManager to handle all reading and writing.

You need to distinguish between two different concepts when you look at how tokens are handled:

  1. Model context window: This is the hard cap for each model. It limits the tokens that are actually visible to the model.
  2. Session store counters: These are rolling statistics written into sessions.json. You will see these values used for the /status endpoint and your dashboards.

If you are tuning your limits, keep these two points in mind:

  • The context window value comes from the model catalog. If you need to change it, you can override it through your config.
  • The contextTokens value in the store is a runtime estimate used for reporting. You should not treat it as a strict guarantee.

For more details on how this works, you can look at /token-use.

Compaction is how you handle long-running conversations without losing the thread. It summarizes older parts of a chat into a single compaction entry in the transcript. This keeps your most recent messages intact so the model stays focused.

After a compaction occurs, future turns see:

  • The compaction summary
  • Every message after firstKeptEntryId

You should know that compaction is persistent. This is a key difference from session pruning, which is temporary. You can check the details on that here: /concepts/session-pruning.

If you are using the embedded Pi agent, auto-compaction is handled for you. It triggers in two specific scenarios:

  1. Overflow recovery: When the model returns a context overflow error, the system runs a compaction and retries the request.
  2. Threshold maintenance: After a successful turn, the system checks if you are nearing the limit:

contextTokens > contextWindow - reserveTokens

In this logic:

  • contextWindow is the total context window of the model
  • reserveTokens is the headroom kept for prompts and the next model output

These rules follow Pi runtime semantics. While OpenClaw consumes the events, Pi is the component that decides when it is time to compact.

Compaction settings (reserveTokens, keepRecentTokens)

Section titled “Compaction settings (reserveTokens, keepRecentTokens)”

You can find Pi’s compaction settings within the Pi configuration. Here is how the structure looks:

{
compaction: {
enabled: true,
reserveTokens: 16384,
keepRecentTokens: 20000,
},
}

OpenClaw also enforces a safety floor for embedded runs to keep things running smoothly:

  • If compaction.reserveTokens < reserveTokensFloor, OpenClaw bumps it up automatically.
  • The default floor is set to 20000 tokens.
  • You can set agents.defaults.compaction.reserveTokensFloor: 0 if you need to disable this floor.
  • If your setting is already higher than the floor, OpenClaw leaves it alone.

The reason for this is to leave enough headroom for multi-turn “housekeeping” tasks, such as memory writes, before compaction becomes unavoidable.

If you want to see the logic behind this, check out ensurePiCompactionReserveTokens() in src/agents/pi-settings.ts. This is called from src/agents/pi-embedded-runner.ts.

You can check the status of compaction and your current session state through these methods:

  • Use /status inside any chat session.
  • Run the openclaw status command in your CLI.
  • Use openclaw sessions or sessions --json for detailed data.
  • Enable verbose mode to see 🧹 Auto-compaction complete along with the compaction count.

OpenClaw supports “silent” turns for background tasks where you don’t want intermediate output to clutter the conversation.

The convention is straightforward: the assistant starts its output with NO_REPLY to signal that it should not deliver a reply to the user. OpenClaw then strips or suppresses this message in the delivery layer.

As of 2026.1.10, OpenClaw also suppresses draft/typing streaming when a partial chunk begins with NO_REPLY. This ensures that silent operations don’t leak any partial output while the turn is in progress.

Pre-compaction “memory flush” (implemented)

Section titled “Pre-compaction “memory flush” (implemented)”

The goal here is to run a silent agentic turn that writes durable state to your disk (like memory/YYYY-MM-DD.md in the agent workspace) before auto-compaction happens. This prevents compaction from erasing critical context that you might still need.

OpenClaw uses the 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 the agent.
  3. Use NO_REPLY so the user sees nothing.
  4. Track the flush in sessions.json to ensure it runs once per compaction cycle.

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 and 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, while 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.
  • 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 if your model context window is too small or if reserveTokens is too high for the model window, which can cause earlier compaction. You can also enable or tune session pruning to handle tool-result bloat.
  • Silent turns leaking? Confirm the reply starts with the exact NO_REPLY token and you’re on a build that includes the streaming suppression fix.
OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.