セッション管理

OpenClawはエージェントごとに1つのダイレクトチャットセッションをプライマリとして扱います。ダイレクトチャットはagent:<agentId>:<mainKey>(デフォルトはmain)に集約され、グループ/チャネルチャットは独自のキーを持ちます。session.mainKeyは反映されます。

ダイレクトメッセージのグループ化はsession.dmScopeで制御します:

  • main(デフォルト):すべてのDMが継続性のためにメインセッションを共有。
  • per-peer:チャネルをまたいで送信者IDごとに分離。
  • per-channel-peer:チャネル+送信者ごとに分離(マルチユーザー受信箱に推奨)。
  • per-account-channel-peer:アカウント+チャネル+送信者ごとに分離(マルチアカウント受信箱に推奨)。 session.identityLinksを使って、プロバイダプレフィックス付きのピアIDを正規IDにマッピングすると、per-peerper-channel-peerper-account-channel-peer使用時に同一人物がチャネルをまたいでDMセッションを共有できます。

セキュアDMモード(マルチユーザー環境に推奨)

セキュリティ警告: エージェントが複数の人物からDMを受信する可能性がある場合、セキュアDMモードの有効化を強く検討してください。有効にしないと、すべてのユーザーが同じ会話コンテキストを共有するため、ユーザー間で個人情報が漏洩する恐れがあります。

デフォルト設定での問題例:

  • Alice(<SENDER_A>)がプライベートな話題(例:医療の予約)についてエージェントにメッセージを送る
  • Bob(<SENDER_B>)がエージェントに「さっき何の話をしてた?」と聞く
  • 両方のDMが同じセッションを共有しているため、モデルがAliceのコンテキストを使ってBobに回答する可能性がある

解決策: dmScopeを設定してユーザーごとにセッションを分離する:

// ~/.openclaw/openclaw.json
{
  session: {
    // セキュアDMモード:チャネル+送信者ごとにDMコンテキストを分離
    dmScope: "per-channel-peer",
  },
}

有効にすべき場面:

  • 複数の送信者に対してペアリング承認がある
  • 複数エントリのDM許可リストを使用している
  • dmPolicy: "open"を設定している
  • 複数の電話番号やアカウントがエージェントにメッセージを送れる

補足:

  • デフォルトは継続性のためdmScope: "main"(すべてのDMがメインセッションを共有)。シングルユーザー環境ではこれで問題ない。
  • ローカルCLIオンボーディングでは、未設定の場合にデフォルトでsession.dmScope: "per-channel-peer"を書き込む(既存の明示的な値は保持される)。
  • 同一チャネルのマルチアカウント受信箱にはper-account-channel-peerが推奨。
  • 同一人物が複数チャネルからコンタクトする場合、session.identityLinksでDMセッションを1つの正規IDに統合できる。
  • DMの設定はopenclaw security auditで検証できる(security参照)。

Gatewayが正規データソース

すべてのセッション状態はGatewayが所有しています(「マスター」OpenClaw)。UIクライアント(macOSアプリ、WebChatなど)は、ローカルファイルを読む代わりにGatewayにセッション一覧やトークン数を問い合わせる必要があります。

  • リモートモードでは、重要なセッションストアはMac上ではなくリモートのGatewayホスト上にある。
  • UIに表示されるトークン数はGatewayのストアフィールド(inputTokensoutputTokenstotalTokenscontextTokens)から取得される。クライアントはJSONLトランスクリプトをパースして合計値を「修正」することはない。

状態の保存先

  • Gatewayホスト上:
    • ストアファイル:~/.openclaw/agents/<agentId>/sessions/sessions.json(エージェント単位)。
  • トランスクリプト:~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl(Telegramのトピックセッションは.../<SessionId>-topic-<threadId>.jsonl)。
  • ストアはsessionKey -> { sessionId, updatedAt, ... }のマップ。エントリの削除は安全で、次のメッセージで再作成される。
  • グループエントリにはUIでセッションにラベルを付けるためのdisplayNamechannelsubjectroomspaceが含まれる場合がある。
  • セッションエントリにはoriginメタデータ(ラベル+ルーティングヒント)が含まれ、UIでセッションの由来を説明できる。
  • OpenClawはレガシーのPi/Tauセッションフォルダを読み取りません

メンテナンス

OpenClawはsessions.jsonとトランスクリプト成果物を時間とともに制限するためにセッションストアメンテナンスを適用します。

デフォルト値

  • session.maintenance.modewarn
  • session.maintenance.pruneAfter30d
  • session.maintenance.maxEntries500
  • session.maintenance.rotateBytes10mb
  • session.maintenance.resetArchiveRetentionpruneAfterのデフォルト(30d
  • session.maintenance.maxDiskBytes:未設定(無効)
  • session.maintenance.highWaterBytes:バジェット有効時はmaxDiskBytes80%がデフォルト

動作の仕組み

メンテナンスはセッションストアの書き込み時に実行され、openclaw sessions cleanupでオンデマンドでもトリガーできます。

  • mode: "warn":削除対象を報告するが、エントリ/トランスクリプトは変更しない。
  • mode: "enforce":以下の順序でクリーンアップを適用:
    1. pruneAfterより古い期限切れエントリを刈り込む
    2. エントリ数をmaxEntriesに制限(古い順)
    3. 参照されなくなった削除済みエントリのトランスクリプトファイルをアーカイブ
    4. 保持ポリシーに基づいて古い*.deleted.<timestamp>および*.reset.<timestamp>アーカイブを削除
    5. sessions.jsonrotateBytesを超えた場合にローテーション
    6. maxDiskBytesが設定されている場合、highWaterBytesに向けてディスクバジェットを適用(古い成果物→古いセッションの順)

大規模ストアのパフォーマンスに関する注意

大規模なセッションストアは高ボリューム環境では一般的です。メンテナンス処理は書き込みパスで実行されるため、非常に大きなストアでは書き込みレイテンシが増加する可能性があります。

コストが高くなる要因:

  • session.maintenance.maxEntriesの値が非常に大きい
  • pruneAfterのウィンドウが長く、期限切れエントリが残り続ける
  • ~/.openclaw/agents/<agentId>/sessions/に多くのトランスクリプト/アーカイブ成果物がある
  • 適切なプルーニング/上限なしでディスクバジェット(maxDiskBytes)を有効にする

対処法:

  • 本番環境ではmode: "enforce"を使い、増加を自動的に制限する
  • 時間と件数の両方の制限を設定する(pruneAfter + maxEntries)。片方だけにしない
  • 大規模デプロイではmaxDiskBytes + highWaterBytesでハード上限を設定
  • highWaterBytesmaxDiskBytesより十分に低くする(デフォルトは80%)
  • 設定変更後はopenclaw sessions cleanup --dry-run --jsonで想定される影響を検証してから適用
  • 頻繁にアクティブなセッションには、手動クリーンアップ時に--active-keyを渡す

カスタマイズ例

保守的なenforceポリシー:

{
  session: {
    maintenance: {
      mode: "enforce",
      pruneAfter: "45d",
      maxEntries: 800,
      rotateBytes: "20mb",
      resetArchiveRetention: "14d",
    },
  },
}

セッションディレクトリのハードディスクバジェットを有効化:

{
  session: {
    maintenance: {
      mode: "enforce",
      maxDiskBytes: "1gb",
      highWaterBytes: "800mb",
    },
  },
}

大規模インストール向けのチューニング(例):

{
  session: {
    maintenance: {
      mode: "enforce",
      pruneAfter: "14d",
      maxEntries: 2000,
      rotateBytes: "25mb",
      maxDiskBytes: "2gb",
      highWaterBytes: "1.6gb",
    },
  },
}

CLIからプレビューまたは強制メンテナンス:

openclaw sessions cleanup --dry-run
openclaw sessions cleanup --enforce

セッションプルーニング

OpenClawはLLM呼び出しの直前に、デフォルトでインメモリコンテキストから古いツール結果をトリミングします。 JSONL履歴の書き換えは行いません。詳細は/concepts/session-pruningを参照。

コンパクション前のメモリフラッシュ

セッションが自動コンパクションに近づくと、OpenClawはサイレントメモリフラッシュターンを実行し、モデルに永続的なメモをディスクに書き込むよう促します。ワークスペースが書き込み可能な場合のみ実行されます。MemoryCompactionを参照。

トランスポートからセッションキーへのマッピング

  • ダイレクトチャットはsession.dmScopeに従う(デフォルトはmain)。
    • mainagent:<agentId>:<mainKey>(デバイス/チャネルをまたいだ継続性)。
      • 複数の電話番号やチャネルが同じエージェントメインキーにマッピングされる場合がある。1つの会話へのトランスポートとして機能する。
    • per-peeragent:<agentId>:direct:<peerId>
    • per-channel-peeragent:<agentId>:<channel>:direct:<peerId>
    • per-account-channel-peeragent:<agentId>:<channel>:<accountId>:direct:<peerId>(accountIdのデフォルトはdefault)。
    • session.identityLinksがプロバイダプレフィックス付きピアID(例:telegram:123)にマッチすると、正規キーが<peerId>を置換し、同一人物がチャネルをまたいでセッションを共有する。
  • グループチャットは状態を分離:agent:<agentId>:<channel>:group:<id>(ルーム/チャネルはagent:<agentId>:<channel>:channel:<id>)。
    • Telegramのフォーラムトピックは分離のためにグループIDに:topic:<threadId>を付加する。
    • レガシーのgroup:<id>キーはマイグレーション用に認識される。
  • 受信コンテキストはgroup:<id>を使う場合がある。チャネルはProviderから推論され、正規形agent:<agentId>:<channel>:group:<id>に正規化される。
  • その他のソース:
    • Cronジョブ:cron:<job.id>
    • Webhook:hook:<uuid>(フックで明示的に設定されない限り)
    • ノード実行:node-<nodeId>

ライフサイクル

  • リセットポリシー:セッションは有効期限が切れるまで再利用され、有効期限は次の受信メッセージで評価される。
  • デイリーリセット:デフォルトはGatewayホストのローカルタイム午前4時。最後の更新が最新のデイリーリセット時刻より前の場合、セッションは期限切れとなる。
  • アイドルリセット(オプション):idleMinutesでスライディングアイドルウィンドウを追加。デイリーリセットとアイドルリセットの両方が設定されている場合、先に期限切れになった方が新しいセッションを強制する。
  • レガシーアイドルのみ:session.reset/resetByType設定なしでsession.idleMinutesを設定すると、後方互換性のためにアイドルのみモードが維持される。
  • タイプ別オーバーライド(オプション):resetByTypedirectgroupthreadセッションのポリシーをオーバーライドできる(thread = Slack/Discordスレッド、Telegramトピック、コネクタ提供時のMatrixスレッド)。
  • チャネル別オーバーライド(オプション):resetByChannelでチャネルのリセットポリシーをオーバーライド(そのチャネルのすべてのセッションタイプに適用され、reset/resetByTypeより優先)。
  • リセットトリガー:正確な/newまたは/reset(およびresetTriggersの追加分)でセッションIDが新規作成され、メッセージの残りが渡される。/new <model>はモデルエイリアス、provider/model、またはプロバイダ名(ファジーマッチ)を受け付けて新しいセッションモデルを設定。/new/resetが単独で送信された場合、OpenClawはリセットを確認する短い「hello」グリーティングターンを実行する。
  • 手動リセット:ストアから特定のキーを削除するか、JSONLトランスクリプトを削除する。次のメッセージで再作成される。
  • 分離されたCronジョブは常に実行ごとに新しいsessionIdを発行する(アイドル再利用なし)。

送信ポリシー(オプション)

個別IDをリストアップせずに、特定のセッションタイプへの配信をブロックします。

{
  session: {
    sendPolicy: {
      rules: [
        { action: "deny", match: { channel: "discord", chatType: "group" } },
        { action: "deny", match: { keyPrefix: "cron:" } },
        // 生のセッションキー(`agent:<id>:`プレフィックスを含む)でマッチ。
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ],
      default: "allow",
    },
  },
}

ランタイムオーバーライド(オーナーのみ):

  • /send on → このセッションで許可
  • /send off → このセッションで拒否
  • /send inherit → オーバーライドをクリアし、設定ルールを使用 単独メッセージとして送信すると反映されます。

設定(オプション、リネーム例)

// ~/.openclaw/openclaw.json
{
  session: {
    scope: "per-sender", // グループキーを分離
    dmScope: "main", // DM継続性(共有受信箱にはper-channel-peer/per-account-channel-peerを設定)
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"],
    },
    reset: {
      // デフォルト:mode=daily、atHour=4(Gatewayホストのローカルタイム)。
      // idleMinutesも設定すると、先に期限切れになった方が適用される。
      mode: "daily",
      atHour: 4,
      idleMinutes: 120,
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      direct: { mode: "idle", idleMinutes: 240 },
      group: { mode: "idle", idleMinutes: 120 },
    },
    resetByChannel: {
      discord: { mode: "idle", idleMinutes: 10080 },
    },
    resetTriggers: ["/new", "/reset"],
    store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
    mainKey: "main",
  },
}

確認方法

  • openclaw status --- ストアパスと最近のセッションを表示。
  • openclaw sessions --json --- すべてのエントリをダンプ(--active <minutes>でフィルタ)。
  • openclaw gateway call sessions.list --params '{}' --- 実行中のGatewayからセッションを取得(リモートGatewayアクセスには--url/--tokenを使用)。
  • チャットで/statusを単独メッセージとして送信すると、エージェントの到達性、セッションコンテキストの使用量、thinking/fast/verboseの切り替え状態、WhatsApp web認証情報の最終更新日時(再リンクの必要性の判断に有用)を確認できる。
  • /context listまたは/context detailを送信すると、システムプロンプトや注入されたワークスペースファイルの内容(およびコンテキスト消費量の大きな要因)を確認できる。
  • /stop(またはstopstop actionstop runstop openclawなどの単独中止フレーズ)を送信すると、現在の実行を中断し、そのセッションのキュー済みフォローアップをクリアし、そこからスポーンされたサブエージェント実行も停止する(返信に停止数が含まれる)。
  • /compact(オプションの指示付き)を単独メッセージとして送信すると、古いコンテキストを要約してウィンドウスペースを解放する。/concepts/compactionを参照。
  • JSONLトランスクリプトを直接開いて完全なターンを確認できる。

ヒント

  • プライマリキーは1対1のトラフィック専用にし、グループには独自のキーを持たせる。
  • クリーンアップを自動化する場合は、ストア全体ではなく個別のキーを削除して、他のコンテキストを保持する。

セッションのoriginメタデータ

各セッションエントリはoriginにその由来をベストエフォートで記録します:

  • label:人間向けラベル(会話ラベル+グループsubject/channelから解決)
  • provider:正規化されたチャネルID(拡張を含む)
  • from/to:受信エンベロープからの生ルーティングID
  • accountId:プロバイダアカウントID(マルチアカウント時)
  • threadId:チャネルがサポートしている場合のスレッド/トピックID originフィールドはダイレクトメッセージ、チャネル、グループに対して入力されます。コネクタが配信ルーティングのみを更新する場合(例:DMメインセッションを最新に保つ)でも、セッションの説明メタデータを維持するために受信コンテキストを提供する必要があります。拡張はこのために、受信コンテキストでConversationLabelGroupSubjectGroupChannelGroupSpaceSenderNameを送信し、recordSessionMetaFromInboundを呼び出す(または同じコンテキストをupdateLastRouteに渡す)ことで対応できます。