セッション管理と圧縮(詳細解説)
このドキュメントでは、OpenClaw がセッションをエンドツーエンドでどのように管理するかを説明します。- セッションルーティング(受信メッセージがどのように
sessionKeyにマッピングされるか) - セッションストア(
sessions.json)と、その追跡内容 - transcript の永続化(
*.jsonl)とその構造 - transcript の衛生管理(実行前のプロバイダー固有の修正)
- コンテキスト制限(コンテキストウィンドウと追跡トークンの違い)
- 圧縮(手動 + 自動圧縮)と、圧縮前処理をどこにフックするか
- サイレントなハウスキーピング(例: ユーザーに見える出力を生成すべきでないメモリ書き込み)
- /concepts/session
- /concepts/compaction
- /concepts/memory
- /concepts/memory-search
- /concepts/session-pruning
- /reference/transcript-hygiene
真実のソース: Gateway
OpenClaw は、セッション状態を所有する単一の Gateway プロセス を中心に設計されています。- UI(macOS アプリ、web Control UI、TUI)は、セッション一覧とトークン数を Gateway に問い合わせるべきです。
- リモートモードでは、セッションファイルはリモートホスト上にあります。「ローカル Mac のファイルを確認する」だけでは、Gateway が実際に使っている内容は反映されません。
2つの永続化レイヤー
OpenClaw はセッションを 2 つのレイヤーで永続化します。-
セッションストア(
sessions.json)- キー/値マップ:
sessionKey -> SessionEntry - 小さく、可変で、編集しやすい(またはエントリを削除しても安全)
- セッションメタデータ(現在の session id、最終アクティビティ、トグル、トークンカウンターなど)を追跡します
- キー/値マップ:
-
transcript(
<sessionId>.jsonl)- ツリー構造を持つ追記専用 transcript(エントリは
id+parentIdを持つ) - 実際の会話 + ツール呼び出し + 圧縮サマリーを保存します
- 将来のターンで model context を再構築するために使用されます
- ツリー構造を持つ追記専用 transcript(エントリは
ディスク上の場所
エージェントごとに、Gateway ホスト上では以下です。- ストア:
~/.openclaw/agents/<agentId>/sessions/sessions.json - transcript:
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl- Telegram トピックセッション:
.../<sessionId>-topic-<threadId>.jsonl
- Telegram トピックセッション:
src/config/sessions.ts 経由で解決します。
ストアのメンテナンスとディスク制御
セッション永続化には、sessions.json と transcript アーティファクトに対する自動メンテナンス制御(session.maintenance)があります。
mode:warn(デフォルト)またはenforcepruneAfter: 古いエントリの経過時間しきい値(デフォルト30d)maxEntries:sessions.json内のエントリ上限(デフォルト500)rotateBytes:sessions.jsonが大きくなりすぎた場合にローテーションするサイズ(デフォルト10mb)resetArchiveRetention:*.reset.<timestamp>transcript アーカイブの保持期間(デフォルト:pruneAfterと同じ。falseでクリーンアップ無効)maxDiskBytes: セッションディレクトリの任意の容量予算highWaterBytes: クリーンアップ後の任意の目標値(デフォルトはmaxDiskBytesの80%)
mode: "enforce"):
- まず、最も古いアーカイブ済みまたは孤立した transcript アーティファクトを削除します。
- それでも目標値を上回る場合は、最も古いセッションエントリとその transcript ファイルを削除します。
- 使用量が
highWaterBytes以下になるまで続けます。
mode: "warn" では、OpenClaw は削除候補を報告しますが、ストア/ファイルは変更しません。
必要に応じてメンテナンスを実行します。
Cron セッションと実行ログ
分離された cron 実行もセッションエントリ/transcript を作成し、それらには専用の保持制御があります。cron.sessionRetention(デフォルト24h)は、古い分離 cron 実行セッションをセッションストアから削除します(falseで無効)。cron.runLog.maxBytes+cron.runLog.keepLinesは、~/.openclaw/cron/runs/<jobId>.jsonlファイルを剪定します(デフォルト:2_000_000バイトと2000行)。
セッションキー(sessionKey)
sessionKey は、どの会話バケットにいるか を識別します(ルーティング + 分離)。
よくあるパターン:
- メイン/ダイレクトチャット(エージェントごと):
agent:<agentId>:<mainKey>(デフォルトmain) - グループ:
agent:<agentId>:<channel>:group:<id> - ルーム/チャンネル(Discord/Slack):
agent:<agentId>:<channel>:channel:<id>または...:room:<id> - Cron:
cron:<job.id> - Webhook:
hook:<uuid>(上書きされていない場合)
セッション id(sessionId)
各 sessionKey は現在の sessionId(会話を継続する transcript ファイル)を指します。
経験則:
- リセット(
/new、/reset)は、そのsessionKeyに対して新しいsessionIdを作成します。 - 日次リセット(デフォルトでは Gateway ホストのローカル時刻で午前 4:00)は、リセット境界を超えた次のメッセージで新しい
sessionIdを作成します。 - アイドル期限切れ(
session.reset.idleMinutesまたは旧session.idleMinutes)は、アイドル時間枠の後にメッセージが到着すると新しいsessionIdを作成します。日次 + アイドルの両方が設定されている場合は、先に期限切れになる方が優先されます。 - スレッド親フォークガード(
session.parentForkMaxTokens、デフォルト100000)は、親セッションがすでに大きすぎる場合に親 transcript のフォークをスキップします。新しいスレッドは新規に開始されます。無効にするには0を設定します。
src/auto-reply/reply/session.ts の initSessionState() で行われます。
セッションストアのスキーマ(sessions.json)
ストアの値型は src/config/sessions.ts の SessionEntry です。
主なフィールド(網羅ではありません):
sessionId: 現在の transcript id(sessionFileが設定されていない限り、ファイル名はこれから導出されます)updatedAt: 最終アクティビティのタイムスタンプsessionFile: 任意の明示的な transcript パス上書きchatType:direct | group | room(UI と送信ポリシーに役立ちます)provider,subject,room,space,displayName: グループ/チャンネル表示用のメタデータ- トグル:
thinkingLevel,verboseLevel,reasoningLevel,elevatedLevelsendPolicy(セッションごとの上書き)
- モデル選択:
providerOverride,modelOverride,authProfileOverride
- トークンカウンター(ベストエフォート / プロバイダー依存):
inputTokens,outputTokens,totalTokens,contextTokens
compactionCount: この session key に対して自動圧縮が完了した回数memoryFlushAt: 直近の圧縮前メモリフラッシュのタイムスタンプmemoryFlushCompactionCount: 直近のフラッシュ実行時の圧縮回数
transcript の構造(*.jsonl)
transcript は @mariozechner/pi-coding-agent の SessionManager によって管理されます。
ファイルは JSONL です。
- 1 行目: セッションヘッダー(
type: "session"、id、cwd、timestamp、任意のparentSessionを含む) - 以降:
id+parentIdを持つセッションエントリ(ツリー)
message: user/assistant/toolResult メッセージcustom_message: model context に 入る 拡張注入メッセージ(UI では非表示にできる)custom: model context に 入らない 拡張状態compaction:firstKeptEntryIdとtokensBeforeを持つ永続化された圧縮サマリーbranch_summary: ツリーブランチ移動時の永続化されたサマリー
SessionManager を使ってそれらを読み書きします。
コンテキストウィンドウと追跡トークンの違い
重要なのは 2 つの異なる概念です。- モデルのコンテキストウィンドウ: モデルごとのハード上限(モデルに見えるトークン数)
- セッションストアのカウンター:
sessions.jsonに書き込まれるローリング統計(/statusとダッシュボードで使用)
- コンテキストウィンドウはモデルカタログから取得されます(設定で上書きすることもできます)。
- ストア内の
contextTokensはランタイム時の推定値/報告値です。厳密な保証として扱わないでください。
圧縮: それが何か
圧縮は、古い会話を transcript 内の永続化されたcompaction エントリに要約し、最近のメッセージはそのまま保持します。
圧縮後、将来のターンでは以下が見えるようになります。
- 圧縮サマリー
firstKeptEntryId以降のメッセージ
圧縮チャンク境界とツールのペアリング
OpenClaw が長い transcript を圧縮チャンクに分割するとき、assistant のツール呼び出しと対応するtoolResult エントリのペアが保たれます。
- トークン比率ベースの分割位置がツール呼び出しとその結果の間に来る場合、OpenClaw はペアを分離する代わりに、境界を assistant のツール呼び出しメッセージ側へ移動します。
- 末尾の tool-result ブロックがそのままだとチャンクを目標サイズ超過にしてしまう場合、OpenClaw はその保留中ツールブロックを保持し、未要約の末尾をそのまま維持します。
- 中断/エラーになったツール呼び出しブロックは、保留中の分割を維持しません。
自動圧縮がいつ発生するか(Pi ランタイム)
組み込み Pi エージェントでは、自動圧縮は 2 つのケースで発動します。- オーバーフロー回復: モデルがコンテキストオーバーフローエラーを返した場合
(
request_too_large、context length exceeded、input exceeds the maximum number of tokens、input token count exceeds the maximum number of input tokens、input is too long for the model、ollama error: context length exceeded、および同様のプロバイダー固有バリアント)→ 圧縮 → 再試行。 - しきい値メンテナンス: 正常なターンの後で、以下のとき
contextTokens > contextWindow - reserveTokens
ここで:
contextWindowはモデルのコンテキストウィンドウreserveTokensはプロンプト + 次のモデル出力のために予約されたヘッドルーム
圧縮設定(reserveTokens, keepRecentTokens)
Pi の圧縮設定は Pi settings にあります。
compaction.reserveTokens < reserveTokensFloorの場合、OpenClaw はその値を引き上げます。- デフォルトの下限は
20000トークンです。 - 下限を無効にするには
agents.defaults.compaction.reserveTokensFloor: 0を設定します。 - すでにそれより高い場合、OpenClaw はそのままにします。
src/agents/pi-settings.ts の ensurePiCompactionReserveTokens()
(src/agents/pi-embedded-runner.ts から呼び出されます)。
ユーザーに見えるサーフェス
圧縮とセッション状態は以下から確認できます。/status(任意のチャットセッション内)openclaw status(CLI)openclaw sessions/sessions --json- Verbose モード:
🧹 Auto-compaction complete+ 圧縮回数
サイレントなハウスキーピング(NO_REPLY)
OpenClaw は、ユーザーが中間出力を見るべきでないバックグラウンドタスク向けに「サイレント」ターンをサポートしています。
慣例:
- assistant は、正確なサイレントトークン
NO_REPLY/no_replyで出力を始めることで、「ユーザーに返信を配信しない」ことを示します。 - OpenClaw は配信レイヤーでこれを取り除くか抑制します。
- 正確なサイレントトークン抑制は大文字小文字を区別しないため、ペイロード全体がそのサイレントトークンだけである場合、
NO_REPLYとno_replyの両方が該当します。 - これは真のバックグラウンド/非配信ターン専用であり、 通常の実行可能なユーザー要求の近道ではありません。
2026.1.10 時点で、OpenClaw は partial chunk が NO_REPLY で始まると draft/typing streaming も抑制するため、サイレント操作でターン途中の部分出力が漏れません。
圧縮前の「メモリフラッシュ」(実装済み)
目的: 自動圧縮が起きる前に、永続的な状態をディスクに書き込むサイレントな agentic turn を実行することです(例: エージェントワークスペース内のmemory/YYYY-MM-DD.md)。これにより、圧縮で重要なコンテキストが失われるのを防ぎます。
OpenClaw は しきい値前フラッシュ アプローチを使用します。
- セッションのコンテキスト使用量を監視します。
- それが「ソフトしきい値」(Pi の圧縮しきい値より下)を超えたら、サイレントな 「今すぐメモリを書き込む」指示をエージェントに実行させます。
- ユーザーに何も見せないよう、正確なサイレントトークン
NO_REPLY/no_replyを使用します。
agents.defaults.compaction.memoryFlush):
enabled(デフォルト:true)softThresholdTokens(デフォルト:4000)prompt(フラッシュターン用の user message)systemPrompt(フラッシュターン用に追加される extra system prompt)
- デフォルトの prompt/system prompt には、配信を抑制するための
NO_REPLYヒントが含まれています。 - フラッシュは圧縮サイクルごとに 1 回実行されます(
sessions.jsonで追跡)。 - フラッシュは組み込み Pi セッションでのみ実行されます(CLI バックエンドではスキップされます)。
- セッションワークスペースが読み取り専用(
workspaceAccess: "ro"または"none")の場合、フラッシュはスキップされます。 - ワークスペースのファイル配置と書き込みパターンについては Memory を参照してください。
session_before_compact フックも公開していますが、OpenClaw のフラッシュロジックは現在 Gateway 側にあります。
トラブルシューティングチェックリスト
- session key が間違っている? まず /concepts/session を確認し、
/statusのsessionKeyを確認してください。 - ストアと transcript が一致しない? Gateway ホストと、
openclaw statusから得られるストアパスを確認してください。 - 圧縮が多すぎる? 以下を確認してください:
- モデルのコンテキストウィンドウ(小さすぎないか)
- 圧縮設定(モデルウィンドウに対して
reserveTokensが高すぎると、早めに圧縮が起きることがあります) - tool-result の肥大化: セッション剪定を有効化/調整する
- サイレントターンが漏れる? 返信が
NO_REPLY(大文字小文字を区別しない正確なトークン)で始まっていること、および streaming 抑制修正を含むビルドを使用していることを確認してください。