Session 管理与 Compaction(深入解析)

本文档详细解释 OpenClaw 如何端到端管理 session:

  • Session 路由(入站消息如何映射到 sessionKey
  • Session storesessions.json)及其追踪的内容
  • Transcript 持久化*.jsonl)及其结构
  • Transcript 清理(运行前针对特定 provider 的修复)
  • Context 限制(context window vs 追踪的 token)
  • Compaction(手动 + 自动 compaction)以及在哪里挂钩 pre-compaction 工作
  • 静默清理(例如不应产生用户可见输出的内存写入)

如果你想先了解更高层次的概述,可以从这些文档开始:


真相之源:Gateway

OpenClaw 围绕单个 Gateway 进程设计,由它拥有 session 状态。

  • UI(macOS 应用、Web Control UI、TUI)应该向 Gateway 查询 session 列表和 token 计数。
  • 在远程模式下,session 文件在远程主机上;“检查你本地 Mac 文件”不会反映 Gateway 正在使用的内容。

两层持久化

OpenClaw 在两层中持久化 session:

  1. Session store(sessions.json

    • 键值映射:sessionKey -> SessionEntry
    • 小巧、可变、可以安全编辑(或删除条目)
    • 追踪 session 元数据(当前 session id、最后活动时间、开关、token 计数器等)
  2. Transcript(<sessionId>.jsonl

    • 仅追加的 transcript,具有树结构(条目有 id + parentId
    • 存储实际对话 + 工具调用 + compaction 摘要
    • 用于为未来回合重建模型 context

磁盘位置

每个 agent 在 Gateway 主机上:

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

OpenClaw 通过 src/config/sessions.ts 解析这些路径。


Session keys(sessionKey

sessionKey 标识你所在的_对话桶_(路由 + 隔离)。

常见模式:

  • 主聊天/直接聊天(每个 agent):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>(除非被覆盖)

规范规则记录在 /concepts/session


Session ids(sessionId

每个 sessionKey 指向一个当前的 sessionId(继续对话的 transcript 文件)。

经验法则:

  • 重置/new/reset)为该 sessionKey 创建新的 sessionId
  • 每日重置(默认在 gateway 主机本地时间凌晨 4:00)在重置边界后的下一条消息时创建新的 sessionId
  • 空闲过期session.reset.idleMinutes 或旧版 session.idleMinutes)在空闲窗口后收到消息时创建新的 sessionId。当每日重置和空闲重置都配置时,先到期的生效。

实现细节:决策发生在 src/auto-reply/reply/session.ts 中的 initSessionState()


Session store 架构(sessions.json

store 的值类型是 src/config/sessions.ts 中的 SessionEntry

关键字段(非详尽列表):

  • sessionId:当前 transcript id(文件名从此派生,除非设置了 sessionFile
  • updatedAt:最后活动时间戳
  • sessionFile:可选的显式 transcript 路径覆盖
  • chatTypedirect | group | room(帮助 UI 和发送策略)
  • providersubjectroomspacedisplayName:用于群组/频道标签的元数据
  • 开关:
    • thinkingLevelverboseLevelreasoningLevelelevatedLevel
    • sendPolicy(每个 session 的覆盖)
  • 模型选择:
    • providerOverridemodelOverrideauthProfileOverride
  • Token 计数器(尽力而为/依赖 provider):
    • inputTokensoutputTokenstotalTokenscontextTokens
  • compactionCount:此 session key 完成自动 compaction 的次数
  • memoryFlushAt:最后一次 pre-compaction 内存刷新的时间戳
  • memoryFlushCompactionCount:最后一次刷新运行时的 compaction 计数

store 可以安全编辑,但 Gateway 是权威:它可能会在 session 运行时重写或重新填充条目。


Transcript 结构(*.jsonl

Transcript 由 @mariozechner/pi-coding-agentSessionManager 管理。

文件是 JSONL 格式:

  • 第一行:session 头(type: "session",包括 idcwdtimestamp、可选的 parentSession
  • 然后:带有 id + parentId 的 session 条目(树结构)

值得注意的条目类型:

  • message:user/assistant/toolResult 消息
  • custom_message:扩展注入的消息,_会_进入模型 context(可以从 UI 隐藏)
  • custom:扩展状态,_不会_进入模型 context
  • compaction:持久化的 compaction 摘要,带有 firstKeptEntryIdtokensBefore
  • branch_summary:导航树分支时持久化的摘要

OpenClaw 故意”修复” transcript;Gateway 使用 SessionManager 读写它们。


Context window vs 追踪的 token

两个不同的概念很重要:

  1. 模型 context window:每个模型的硬上限(模型可见的 token)
  2. Session store 计数器:写入 sessions.json 的滚动统计(用于 /status 和仪表板)

如果你要调整限制:

  • context window 来自模型目录(可以通过配置覆盖)。
  • store 中的 contextTokens 是运行时估计/报告值;不要将其视为严格保证。

更多信息,请参阅 /token-use


Compaction:它是什么

Compaction 将较旧的对话总结为 transcript 中持久化的 compaction 条目,并保持最近的消息完整。

Compaction 后,未来的回合会看到:

  • Compaction 摘要
  • firstKeptEntryId 之后的消息

Compaction 是持久的(不像 session pruning)。参见 /concepts/session-pruning


自动 compaction 何时发生(Pi runtime)

在嵌入式 Pi agent 中,自动 compaction 在两种情况下触发:

  1. 溢出恢复:模型返回 context 溢出错误 → compact → 重试。
  2. 阈值维护:成功回合后,当:

contextTokens > contextWindow - reserveTokens

其中:

  • contextWindow 是模型的 context window
  • reserveTokens 是为 prompt + 下一个模型输出预留的空间

这些是 Pi runtime 语义(OpenClaw 消费事件,但 Pi 决定何时 compact)。


Compaction 设置(reserveTokenskeepRecentTokens

Pi 的 compaction 设置位于 Pi 设置中:

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

OpenClaw 还为嵌入式运行强制执行安全下限:

  • 如果 compaction.reserveTokens < reserveTokensFloor,OpenClaw 会提升它。
  • 默认下限是 20000 token。
  • 设置 agents.defaults.compaction.reserveTokensFloor: 0 可禁用下限。
  • 如果已经更高,OpenClaw 不会管它。

原因:在 compaction 变得不可避免之前,为多回合”清理”(如内存写入)留出足够的空间。

实现:src/agents/pi-settings.ts 中的 ensurePiCompactionReserveTokens() (从 src/agents/pi-embedded-runner.ts 调用)。


用户可见界面

你可以通过以下方式观察 compaction 和 session 状态:

  • /status(在任何聊天 session 中)
  • openclaw status(CLI)
  • openclaw sessions / sessions --json
  • 详细模式:🧹 Auto-compaction complete + compaction 计数

静默清理(NO_REPLY

OpenClaw 支持”静默”回合,用于用户不应看到中间输出的后台任务。

约定:

  • assistant 以 NO_REPLY 开始其输出,表示”不要向用户传递回复”。
  • OpenClaw 在传递层剥离/抑制这个。

2026.1.10 开始,当部分块以 NO_REPLY 开头时,OpenClaw 还会抑制草稿/输入流式传输,因此静默操作不会在回合中途泄漏部分输出。


Pre-compaction “内存刷新”(已实现)

目标:在自动 compaction 发生之前,运行一个静默的 agentic 回合,将持久状态写入磁盘(例如 agent workspace 中的 memory/YYYY-MM-DD.md),这样 compaction 就不会擦除关键 context。

OpenClaw 使用预阈值刷新方法:

  1. 监控 session context 使用情况。
  2. 当它越过”软阈值”(低于 Pi 的 compaction 阈值)时,向 agent 运行静默的”立即写入内存”指令。
  3. 使用 NO_REPLY,这样用户什么都看不到。

配置(agents.defaults.compaction.memoryFlush):

  • enabled(默认:true
  • softThresholdTokens(默认:4000
  • prompt(刷新回合的用户消息)
  • systemPrompt(刷新回合附加的额外系统 prompt)

注意:

  • 默认 prompt/system prompt 包含 NO_REPLY 提示以抑制传递。
  • 刷新每个 compaction 周期运行一次(在 sessions.json 中追踪)。
  • 刷新仅针对嵌入式 Pi session 运行(CLI 后端跳过它)。
  • 当 session workspace 为只读时跳过刷新(workspaceAccess: "ro""none")。
  • 有关 workspace 文件布局和写入模式,请参阅 Memory

Pi 还在扩展 API 中公开了 session_before_compact 钩子,但 OpenClaw 的刷新逻辑目前位于 Gateway 端。


故障排除清单

  • Session key 错误?从 /concepts/session 开始,并在 /status 中确认 sessionKey
  • Store vs transcript 不匹配?从 openclaw status 确认 Gateway 主机和 store 路径。
  • Compaction 频繁?检查:
    • 模型 context window(太小)
    • compaction 设置(reserveTokens 对于模型 window 太高可能导致更早的 compaction)
    • 工具结果膨胀:启用/调整 session pruning
  • 静默回合泄漏?确认回复以 NO_REPLY(精确 token)开头,并且你使用的构建包含流式传输抑制修复。