Session Management & Compaction (Deep Dive)

Dieses Dokument erklärt, wie OpenClaw Sessions von Anfang bis Ende verwaltet:

  • Session Routing (wie eingehende Nachrichten auf einen sessionKey gemappt werden)
  • Session Store (sessions.json) und was er trackt
  • Transcript Persistence (*.jsonl) und seine Struktur
  • Transcript Hygiene (Provider-spezifische Fixups vor Runs)
  • Context Limits (Context Window vs. getrackte Tokens)
  • Compaction (manuell + Auto-Compaction) und wo du Pre-Compaction Work einhaken kannst
  • Silent Housekeeping (z.B. Memory Writes, die keine sichtbare Ausgabe erzeugen sollen)

Wenn du zuerst einen Überblick haben möchtest, schau dir diese Seiten an:


Source of Truth: der Gateway

OpenClaw ist um einen einzelnen Gateway Process herum aufgebaut, der den Session State besitzt.

  • UIs (macOS App, Web Control UI, TUI) sollten den Gateway nach Session Listen und Token Counts abfragen.
  • Im Remote Mode liegen die Session Files auf dem Remote Host; “deine lokalen Mac Files checken” zeigt nicht, was der Gateway verwendet.

Zwei Persistence Layer

OpenClaw persistiert Sessions in zwei Layern:

  1. Session Store (sessions.json)

    • Key/Value Map: sessionKey -> SessionEntry
    • Klein, veränderbar, sicher zu editieren (oder Einträge zu löschen)
    • Trackt Session Metadata (aktuelle Session ID, letzte Aktivität, Toggles, Token Counter, etc.)
  2. Transcript (<sessionId>.jsonl)

    • Append-only Transcript mit Baumstruktur (Einträge haben id + parentId)
    • Speichert die eigentliche Konversation + Tool Calls + Compaction Summaries
    • Wird verwendet, um den Model Context für zukünftige Turns zu rebuilden

Speicherorte auf der Festplatte

Pro Agent, auf dem Gateway Host:

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

OpenClaw löst diese Pfade über src/config/sessions.ts auf.


Session Keys (sessionKey)

Ein sessionKey identifiziert, in welchem Conversation Bucket du dich befindest (Routing + Isolation).

Gängige Muster:

  • Main/Direct Chat (pro Agent): agent:<agentId>:<mainKey> (Standard: main)
  • Group: agent:<agentId>:<channel>:group:<id>
  • Room/Channel (Discord/Slack): agent:<agentId>:<channel>:channel:<id> oder ...:room:<id>
  • Cron: cron:<job.id>
  • Webhook: hook:<uuid> (außer überschrieben)

Die kanonischen Regeln sind unter /concepts/session dokumentiert.


Session IDs (sessionId)

Jeder sessionKey zeigt auf eine aktuelle sessionId (das Transcript File, das die Konversation fortsetzt).

Faustregeln:

  • Reset (/new, /reset) erstellt eine neue sessionId für diesen sessionKey.
  • Daily Reset (Standard: 4:00 Uhr morgens Lokalzeit auf dem Gateway Host) erstellt eine neue sessionId bei der nächsten Nachricht nach der Reset-Grenze.
  • Idle Expiry (session.reset.idleMinutes oder Legacy session.idleMinutes) erstellt eine neue sessionId, wenn eine Nachricht nach dem Idle Window eintrifft. Wenn Daily + Idle beide konfiguriert sind, gewinnt das, was zuerst abläuft.

Implementierungsdetail: Die Entscheidung passiert in initSessionState() in src/auto-reply/reply/session.ts.


Session Store Schema (sessions.json)

Der Value Type des Stores ist SessionEntry in src/config/sessions.ts.

Wichtige Felder (nicht vollständig):

  • sessionId: aktuelle Transcript ID (Dateiname wird davon abgeleitet, außer sessionFile ist gesetzt)
  • updatedAt: Zeitstempel der letzten Aktivität
  • sessionFile: optionaler expliziter Transcript Path Override
  • chatType: direct | group | room (hilft UIs und Send Policy)
  • provider, subject, room, space, displayName: Metadata für Group/Channel Labeling
  • Toggles:
    • thinkingLevel, verboseLevel, reasoningLevel, elevatedLevel
    • sendPolicy (Per-Session Override)
  • Model Selection:
    • providerOverride, modelOverride, authProfileOverride
  • Token Counter (Best-Effort / Provider-abhängig):
    • inputTokens, outputTokens, totalTokens, contextTokens
  • compactionCount: wie oft Auto-Compaction für diesen Session Key abgeschlossen wurde
  • memoryFlushAt: Zeitstempel für den letzten Pre-Compaction Memory Flush
  • memoryFlushCompactionCount: Compaction Count, als der letzte Flush lief

Der Store ist sicher zu editieren, aber der Gateway ist die Autorität: Er kann Einträge neu schreiben oder rehydrieren, während Sessions laufen.


Transcript Struktur (*.jsonl)

Transcripts werden vom SessionManager von @mariozechner/pi-coding-agent verwaltet.

Das File ist JSONL:

  • Erste Zeile: Session Header (type: "session", enthält id, cwd, timestamp, optional parentSession)
  • Dann: Session Entries mit id + parentId (Baum)

Wichtige Entry Types:

  • message: User/Assistant/ToolResult Messages
  • custom_message: Extension-injizierte Messages, die in den Model Context eingehen (können im UI versteckt sein)
  • custom: Extension State, der nicht in den Model Context eingeht
  • compaction: persistierte Compaction Summary mit firstKeptEntryId und tokensBefore
  • branch_summary: persistierte Summary beim Navigieren eines Tree Branch

OpenClaw “fixt” Transcripts absichtlich nicht; der Gateway verwendet SessionManager, um sie zu lesen/schreiben.


Context Windows vs. getrackte Tokens

Zwei verschiedene Konzepte sind wichtig:

  1. Model Context Window: hartes Limit pro Model (Tokens, die für das Model sichtbar sind)
  2. Session Store Counter: rollende Stats, die in sessions.json geschrieben werden (verwendet für /status und Dashboards)

Wenn du Limits tunst:

  • Das Context Window kommt aus dem Model Catalog (und kann via Config überschrieben werden).
  • contextTokens im Store ist ein Runtime Estimate/Reporting Value; behandle es nicht als strikte Garantie.

Mehr dazu unter /token-use.


Compaction: Was es ist

Compaction fasst ältere Konversation in einem persistierten compaction Entry im Transcript zusammen und hält aktuelle Messages intakt.

Nach Compaction sehen zukünftige Turns:

  • Die Compaction Summary
  • Messages nach firstKeptEntryId

Compaction ist persistent (im Gegensatz zu Session Pruning). Siehe /concepts/session-pruning.


Wann Auto-Compaction passiert (Pi Runtime)

Im embedded Pi Agent triggert Auto-Compaction in zwei Fällen:

  1. Overflow Recovery: Das Model gibt einen Context Overflow Error zurück → compact → retry.
  2. Threshold Maintenance: Nach einem erfolgreichen Turn, wenn:

contextTokens > contextWindow - reserveTokens

Dabei gilt:

  • contextWindow ist das Context Window des Models
  • reserveTokens ist Headroom, der für Prompts + den nächsten Model Output reserviert ist

Das sind Pi Runtime Semantics (OpenClaw konsumiert die Events, aber Pi entscheidet, wann kompaktiert wird).


Compaction Settings (reserveTokens, keepRecentTokens)

Pis Compaction Settings leben in den Pi Settings:

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

OpenClaw erzwingt außerdem einen Safety Floor für embedded Runs:

  • Wenn compaction.reserveTokens < reserveTokensFloor, bumpt OpenClaw es hoch.
  • Standard Floor ist 20000 Tokens.
  • Setze agents.defaults.compaction.reserveTokensFloor: 0, um den Floor zu deaktivieren.
  • Wenn es bereits höher ist, lässt OpenClaw es in Ruhe.

Warum: Genug Headroom für Multi-Turn “Housekeeping” (wie Memory Writes) lassen, bevor Compaction unvermeidbar wird.

Implementation: ensurePiCompactionReserveTokens() in src/agents/pi-settings.ts (aufgerufen von src/agents/pi-embedded-runner.ts).


User-sichtbare Oberflächen

Du kannst Compaction und Session State beobachten über:

  • /status (in jeder Chat Session)
  • openclaw status (CLI)
  • openclaw sessions / sessions --json
  • Verbose Mode: 🧹 Auto-compaction complete + Compaction Count

Silent Housekeeping (NO_REPLY)

OpenClaw unterstützt “silent” Turns für Background Tasks, bei denen der User keine Zwischenausgabe sehen soll.

Konvention:

  • Der Assistant startet seine Ausgabe mit NO_REPLY, um anzuzeigen “deliver keine Reply an den User”.
  • OpenClaw strippt/unterdrückt das im Delivery Layer.

Seit 2026.1.10 unterdrückt OpenClaw auch Draft/Typing Streaming, wenn ein partieller Chunk mit NO_REPLY beginnt, sodass Silent Operations keine partielle Ausgabe mid-turn leaken.


Pre-Compaction “Memory Flush” (implementiert)

Ziel: Bevor Auto-Compaction passiert, einen silent agentic Turn laufen lassen, der dauerhaften State auf die Festplatte schreibt (z.B. memory/YYYY-MM-DD.md im Agent Workspace), sodass Compaction keinen kritischen Context löschen kann.

OpenClaw verwendet den Pre-Threshold Flush Ansatz:

  1. Session Context Usage monitoren.
  2. Wenn er einen “Soft Threshold” überschreitet (unterhalb von Pis Compaction Threshold), einen silent “write memory now” Directive an den Agent laufen lassen.
  3. NO_REPLY verwenden, sodass der User nichts sieht.

Config (agents.defaults.compaction.memoryFlush):

  • enabled (Standard: true)
  • softThresholdTokens (Standard: 4000)
  • prompt (User Message für den Flush Turn)
  • systemPrompt (extra System Prompt, der für den Flush Turn angehängt wird)

Hinweise:

  • Der Standard Prompt/System Prompt enthält einen NO_REPLY Hint, um Delivery zu unterdrücken.
  • Der Flush läuft einmal pro Compaction Cycle (getrackt in sessions.json).
  • Der Flush läuft nur für embedded Pi Sessions (CLI Backends überspringen ihn).
  • Der Flush wird übersprungen, wenn der Session Workspace read-only ist (workspaceAccess: "ro" oder "none").
  • Siehe Memory für das Workspace File Layout und Write Patterns.

Pi exponiert auch einen session_before_compact Hook in der Extension API, aber OpenClaws Flush Logic lebt heute auf der Gateway Seite.


Troubleshooting Checkliste

  • Session Key falsch? Starte mit /concepts/session und bestätige den sessionKey in /status.
  • Store vs. Transcript Mismatch? Bestätige den Gateway Host und den Store Path von openclaw status.
  • Compaction Spam? Prüfe:
    • Model Context Window (zu klein)
    • Compaction Settings (reserveTokens zu hoch für das Model Window kann frühere Compaction verursachen)
    • Tool-Result Bloat: aktiviere/tune Session Pruning
  • Silent Turns leaken? Bestätige, dass die Reply mit NO_REPLY (exakter Token) startet und du auf einem Build bist, der den Streaming Suppression Fix enthält.