Skip to content

本页详解 OpenClaw 的会话存储与压缩内部机制,适合需要调试 sessionKey、调整自动压缩行为或实现内存刷新的开发者。关键操作包括:通过 openclaw sessions cleanup 手动维护存储、配置 session.maintenance 限制条目数量和磁盘预算、设置 compaction.reserveTokens 防止上下文溢出、启用 memoryFlush 在压缩前自动写入内存。静默任务用 NO_REPLY 标记,自 2026.1.10 起流式传输也受抑制。

OpenClaw Session 管理深探:压缩、存储与静默任务

OpenClaw 端到端管理 session,覆盖以下方面:

  • Session 路由:入站消息如何映射到 sessionKey
  • Session 存储sessions.json)及其追踪内容
  • Transcript 持久化*.jsonl)及其结构
  • Transcript 清理:运行前针对不同提供商的修正
  • 上下文限制:上下文窗口 vs 已追踪 token 数
  • 压缩机制(手动 + 自动),以及如何在压缩前挂载预处理工作
  • 静默后台任务:内存写入等不应产生用户可见输出的操作

如需先了解高层概览,请参考:


权威数据源:Gateway

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

  • UI 端(macOS 应用、Web 控制台、TUI)应通过 Gateway 查询 session 列表和 token 计数。
  • 在远程模式下,session 文件存在于远程主机上;"检查本地 Mac 文件"无法反映 Gateway 实际使用的状态。

两层持久化

OpenClaw 通过两层结构持久化 session:

  1. Session 存储(sessions.json

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

    • 仅追加的 transcript,具有树形结构(条目包含 id + parentId
    • 存储实际对话 + 工具调用 + 压缩摘要
    • 用于重建未来轮次的模型上下文
    • 大 transcript 在预压缩检查点阶段会跳过复制,避免出现第二个巨量 .checkpoint.*.jsonl 文件

Gateway 历史读取器应避免物化整个 transcript,除非界面需要任意历史访问。首页历史、嵌入的聊天历史、重启恢复以及 token/用量检查使用 bounded tail 读取。全 transcript 扫描走异步 transcript 索引,按文件路径加 mtimeMs/size 缓存,并发读取器共享。


磁盘存储位置

每个 Agent,存于 Gateway 主机:

  • 存储:~/.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 持久化具有自动维护控制(session.maintenance),覆盖 sessions.json、transcript 文件和轨迹 sidecar:

  • modewarn(默认)或 enforce
  • pruneAfter:旧条目年龄阈值(默认 30d
  • maxEntriessessions.json 条目上限(默认 500
  • resetArchiveRetention*.reset.&lt;timestamp&gt; transcript 归档文件的保留期(默认与 pruneAfter 相同;设为 false 禁用清理)
  • maxDiskBytes:可选的 sessions 目录总容量上限
  • highWaterBytes:清理后的目标用量(默认为 maxDiskBytes80%

Gateway 正常写入走每条 session 的写入器,串行化进程内变更,不占用运行时文件锁。热路径补丁助手在持有写入器槽位时借用已验证的可变缓存,避免大文件每次元数据更新都克隆或重新读取。运行时代码应优先使用 updateSessionStore(...)updateSessionStoreEntry(...);直接全存储保存是兼容性和离线维护工具。当 Gateway 可达时,openclaw sessions cleanupopenclaw agents delete 的非 dry-run 模式将存储变更委托给 Gateway,使其加入同一写入器队列;--store &lt;path&gt; 是直接文件维护的离线修复路径。maxEntries 清理仍对生产规模上限进行批处理,因此存储可能短暂超过配置上限,直到下次高水位清理将其降回。Session 存储读取在 Gateway 启动时不会修剪或限制条目;使用写入或 openclaw sessions cleanup --enforce 进行清理。openclaw sessions cleanup --enforce 会立即应用配置的上限,并删除旧的无引用 transcript、检查点和轨迹文件,即使未配置磁盘预算。

维护会保留持久的对外部对话指针,例如群组 session 和线程级聊天 session,但 cron、hooks、heartbeat、ACP 和子 Agent 的合成运行时条目在超过配置的年龄、数量或磁盘预算时仍可被删除。

OpenClaw 不再在 Gateway 写入时自动创建 sessions.json.bak.* 轮换备份。旧版 session.maintenance.rotateBytes 键被忽略,openclaw doctor --fix 会从旧配置中删除它。

Transcript 变更加载 session 写入锁,等待时间上限为 session.writeLock.acquireTimeoutMs(默认 60000 ms),超时后报忙 session 错误。仅在正当的准备、清理、压缩或 transcript 镜像工作争抢较长时间时才需要调大。session.writeLock.staleMs 控制何时可回收旧锁(默认 1800000 ms)。session.writeLock.maxHoldMs 控制进程内看门狗释放阈值(默认 300000 ms)。紧急环境变量覆盖:OPENCLAW_SESSION_WRITE_LOCK_ACQUIRE_TIMEOUT_MSOPENCLAW_SESSION_WRITE_LOCK_STALE_MSOPENCLAW_SESSION_WRITE_LOCK_MAX_HOLD_MS

磁盘预算清理的执行顺序(mode: "enforce"):

  1. 首先删除最旧的归档、孤立 transcript 或孤立轨迹文件。
  2. 若仍高于目标,驱逐最旧的 session 条目及其 transcript/trajectory 文件。
  3. 持续清理直到用量降至 highWaterBytes 以下。

mode: "warn" 下,OpenClaw 报告可能的驱逐操作,但不修改存储/文件。

手动触发维护:

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

Cron Session 与运行日志

独立 cron 运行也会创建 session 条目/transcripts,并拥有专属保留控制:

  • cron.sessionRetention(默认 24h):从 session 存储中清理旧的独立 cron 运行 session(设为 false 禁用)
  • cron.runLog.maxBytes + cron.runLog.keepLines:清理 ~/.openclaw/cron/runs/<jobId>.jsonl 文件(默认:2_000_000 字节和 2000 行)

当 cron 强制创建新的独立运行 session 时,它会在写入新行前清理之前的 cron:<jobId> session 条目。它会携带安全的偏好设置,如 thinking/fast/verbose 设置、标签和显式的用户选择模型/认证覆盖。它会丢弃环境对话上下文,如频道/群组路由、发送或队列策略、提升、来源和 ACP 运行时绑定,以确保新的独立运行不会从旧运行继承过时的传递或运行时权限。


Session 键(sessionKey

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

常见格式:

  • 主/直接聊天(每个 Agent):agent:<agentId>:<mainKey>(默认 main
  • 群组:agent:<agentId>:&lt;channel&gt;:group:&lt;id&gt;
  • 房间/频道(Discord/Slack):agent:<agentId>:&lt;channel&gt;:channel:&lt;id&gt;...:room:&lt;id&gt;
  • Cron:cron:<job.id>
  • Webhook:hook:&lt;uuid&gt;(除非被覆盖)

权威规则文档见 /openclaw/concepts/session


Session ID(sessionId

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

基本规则:

  • 重置/new/reset):为该 sessionKey 创建新的 sessionId
  • 每日重置(默认 Gateway 主机本地时间凌晨 4:00):在重置边界后收到下一条消息时创建新的 sessionId
  • 空闲过期session.reset.idleMinutes 或旧版 session.idleMinutes):在空闲窗口后收到消息时创建新的 sessionId。若每日重置和空闲过期同时配置,先到期者优先。
  • 系统事件(heartbeat、cron 唤醒、exec 通知、Gateway 记账)可能变更 session 行,但不会延长每日/空闲重置的新鲜度。重置回滚会丢弃旧 session 的排队系统事件通知,然后构建新提示。
  • 父分叉策略:创建线程或子 Agent 分叉时使用 PI 的活动分支。如果该分支过大,OpenClaw 会用隔离上下文启动子节点,而不是失败或继承不可用的历史。大小策略自动;旧版 session.parentForkMaxTokens 配置由 openclaw doctor --fix 移除。

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


Session 存储 Schema(sessions.json

存储的值类型为 src/config/sessions.ts 中的 SessionEntry

关键字段(不完整):

  • sessionId:当前 transcript id(文件名由此派生,除非设置了 sessionFile
  • sessionStartedAt:当前 sessionId 的开始时间戳;每日重置新鲜度依赖这个时间。旧版行可能从 JSONL session 头部推导。
  • lastInteractionAt:最后一次真实用户/频道交互的时间戳;空闲重置新鲜度依赖这个时间,因此 heartbeat、cron 和 exec 事件不会保持 session 存活。旧版行没有此字段时,会用 session 开始时间作为空闲新鲜度。
  • updatedAt:存储行最后一次变更的时间戳,用于列表、修剪和记账。它不是每日/空闲重置新鲜度的权威。
  • sessionFile:可选的显式 transcript 路径覆盖
  • chatTypedirect | group | room(帮助 UI 和发送策略)
  • providersubjectroomspacedisplayName:群组/频道标签元数据
  • 开关:
    • thinkingLevelverboseLevelreasoningLevelelevatedLevel
    • sendPolicy(每 session 覆盖)
  • 模型选择:
    • providerOverridemodelOverrideauthProfileOverride
  • Token 计数器(尽力估算 / 取决于提供商):
    • inputTokensoutputTokenstotalTokenscontextTokens
  • compactionCount:该 session key 完成自动压缩的次数
  • memoryFlushAt:上次 pre-compaction 内存刷新的时间戳
  • memoryFlushCompactionCount:上次刷新时的压缩计数

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


Transcript 结构(*.jsonl

Transcripts 由 @earendil-works/pi-coding-agentSessionManager 管理。

文件为 JSONL 格式:

  • 第一行:session 头部(type: "session",包含 idcwdtimestamp,可选 parentSession
  • 之后:包含 id + parentId 的 session 条目(树形结构)

主要条目类型:

  • message:用户/助手/工具结果消息
  • custom_message:扩展注入的消息,_会_进入模型上下文(可对 UI 隐藏)
  • custom:不进入模型上下文的扩展状态
  • compaction:持久化的压缩摘要,包含 firstKeptEntryIdtokensBefore
  • branch_summary:导航树形分支时持久化的摘要

OpenClaw 刻意不"修复" transcript;Gateway 使用 SessionManager 读写 transcript。


上下文窗口 vs 已追踪 Token 数

两个概念需要区分:

  1. 模型上下文窗口:每个模型的硬性上限(模型可见的 token 数)
  2. Session 存储计数器:写入 sessions.json 的滚动统计(用于 /status 和仪表板)

调整限制时:

  • 上下文窗口来自模型目录(可通过配置覆盖)。
  • 存储中的 contextTokens 是运行时估算/报告值;不要将其视为严格保证。

更多内容见 Token 使用参考


压缩机制:是什么

压缩将较旧的对话汇总为 transcript 中一个持久化的 compaction 条目,并保留最近的消息完整。

压缩后,后续轮次将看到:

  • 压缩摘要
  • firstKeptEntryId 之后的消息

压缩是持久化的(与 session 修剪不同)。参见 Session 修剪

压缩块边界与工具配对

当 OpenClaw 将长 transcript 拆分为压缩块时,它会保持助手工具调用与对应的 toolResult 条目成对。

  • 如果 token 份额分割落在工具调用和其结果之间,OpenClaw 会将边界移到助手工具调用消息,而不是分离配对。
  • 如果尾部工具结果块会使块超目标,OpenClaw 会保留该挂起的工具块,并保持未汇总的尾部完整。
  • 中止/错误的工具调用块不会保持挂起分割开放。

自动压缩触发时机(Pi 运行时)

在嵌入式 Pi Agent 中,自动压缩在两种情况下触发:

  1. 溢出恢复:模型返回上下文溢出错误(request_too_largecontext length exceededinput exceeds the maximum number of tokensinput token count exceeds the maximum number of input tokensinput is too long for the modelollama error: context length exceeded 以及类似提供商标记的错误)→ 压缩 → 重试。如果溢出恢复仍然失败,OpenClaw 会向用户显示明确的指导,并保留当前 session 映射,而不是静默地将 session key 旋转到新的 session id。下一步由操作员控制:重试消息、运行 /compact 或运行 /new 以开启新 session。
  2. 阈值维护:成功轮次结束后,当 contextTokens > contextWindow - reserveTokens 时。

其中:

  • contextWindow 是模型的上下文窗口大小
  • reserveTokens 是为提示词 + 下一次模型输出预留的余量

这些是 Pi 运行时的语义(OpenClaw 消费事件,但 Pi 决定何时压缩)。

OpenClaw 还可以在打开下一次运行前触发预检本地压缩:当 agents.defaults.compaction.maxActiveTranscriptBytes 已设置且活动 transcript 文件达到该大小时。这是本地重新打开成本的字节大小保护,而不是原始归档:OpenClaw 仍运行正常的语义压缩,并且需要启用 truncateAfterCompaction,以便压缩摘要可以成为新的后继 transcript。

对于嵌入式 Pi 运行,agents.defaults.compaction.midTurnPrecheck.enabled: true 添加了一个可选的工具循环保护。在工具结果追加后、下一次模型调用前,OpenClaw 使用与轮次开始时相同的预检预算逻辑评估提示压力。如果上下文不再适配,保护不会在 Pi 的 transformContext 钩子内部压缩。它会引发一个结构化的中轮预检信号,停止当前提示提交,并让外部运行循环使用现有的恢复路径:在适当时截断过大的工具结果,或触发配置的压缩模式并重试。该选项默认禁用,与 defaultsafeguard 压缩模式都兼容,包括提供商支持的 safeguard 压缩。这与 maxActiveTranscriptBytes 独立:字节大小保护在轮次开始前运行,而中轮预检在嵌入式 Pi 工具循环中、追加新工具结果之后运行。


压缩设置(reserveTokenskeepRecentTokens

Pi 的压缩配置存在于 Pi 设置中:

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

OpenClaw 还对嵌入式运行强制设置安全下限:

  • 如果 compaction.reserveTokens < reserveTokensFloor,OpenClaw 会将其提升。
  • 默认下限为 20000 token。
  • 设置 agents.defaults.compaction.reserveTokensFloor: 0 可禁用下限。
  • 如果已经更高,OpenClaw 不做修改。

手动 /compact 会尊重显式的 agents.defaults.compaction.keepRecentTokens 并保持 Pi 的近期尾剪切点。如果没有显式的保留预算,手动压缩仍然是硬检查点,重建的上下文从新摘要开始。

json5
// 可选配置示例
{
  agents: {
    defaults: {
      compaction: {
        midTurnPrecheck: { enabled: true },
        maxActiveTranscriptBytes: "20mb",
        truncateAfterCompaction: true,
      },
    },
  },
}

启用 agents.defaults.compaction.truncateAfterCompaction 时,OpenClaw 在压缩后将活动 transcript 轮换为压缩后的后继 JSONL。旧完整 transcript 仍作为归档保留,并从压缩检查点链接,而不是原地重写。

原因:为多轮"后台任务"(如内存写入)保留足够的余量,避免在压缩迫在眉睫前无法完成。

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


可插拔压缩提供商

插件可以通过插件 API 的 registerCompactionProvider() 注册压缩提供商。当 agents.defaults.compaction.provider 设置为已注册的提供商 id 时,safeguard 扩展会将摘要任务委派给该提供商,而不是内置的 summarizeInStages 流水线。

  • provider:已注册的压缩提供商插件 id。留空使用默认 LLM 摘要。
  • 设置 provider 会强制 mode: "safeguard"
  • 提供商接收与内置路径相同的压缩指令和标识符保留策略。
  • Safeguard 仍然在提供商输出后保留近期和分割轮次的尾部上下文。
  • 内置的 safeguard 摘要会将先前的摘要与新的消息共同重新蒸馏,而不是完整保留上一摘要。
  • safeguard 模式默认启用摘要质量审计;设置 qualityGuard.enabled: false 可跳过重试异常输出行为。
  • 如果提供商失败或返回空结果,OpenClaw 会自动回退到内置 LLM 摘要。
  • 中止/超时信号会重新抛出(不吞咽),以尊重调用方取消。

来源:src/plugins/compaction-provider.tssrc/agents/pi-hooks/compaction-safeguard.ts


用户可见的入口

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

  • /status(在任意聊天 session 中)
  • openclaw status(CLI)
  • openclaw sessions / sessions --json
  • Gateway 日志(pnpm gateway:watchopenclaw logs --follow):embedded run auto-compaction start + complete
  • 详细模式:🧹 Auto-compaction complete + 压缩计数

静默后台任务(NO_REPLY

OpenClaw 支持用于后台任务的"静默"轮次,用户不应看到中间输出。

约定:

  • 助手以 NO_REPLY / no_reply 开头输出,表示"不要向用户发送回复"。
  • OpenClaw 在传递层过滤/抑制此内容。
  • 确切静默令牌的抑制不区分大小写,因此 NO_REPLYno_reply 都算,只要整个 payload 就是该静默令牌。
  • 仅用于真正的后台/无传递轮次;不适用于普通可操作的用户请求。

2026.1.10 起,当部分块以 NO_REPLY 开头时,OpenClaw 还会抑制草稿/输入中流式传输,防止静默操作在轮次中间泄漏部分输出。


Pre-Compaction "内存刷新"(已实现)

目标:在自动压缩发生前,运行一个静默的 agentic 轮次,将持久化状态写入磁盘(例如,Agent 工作区中的 memory/YYYY-MM-DD.md),防止压缩抹去关键上下文。

OpenClaw 采用预阈值刷新方式:

  1. 监控 session 上下文使用量。
  2. 当使用量越过"软阈值"(低于 Pi 的压缩阈值)时,向 Agent 发出静默的"立即写入内存"指令。
  3. 使用 NO_REPLY / no_reply,用户看不到任何输出。

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

  • enabled(默认:true
  • model(可选的精确提供商/模型覆盖用于刷新轮次,例如 ollama/qwen3:8b
  • softThresholdTokens(默认:4000
  • prompt(刷新轮次的用户消息)
  • systemPrompt(刷新轮次追加的额外系统提示词)

注意事项:

  • 默认的 prompt/systemPrompt 包含 NO_REPLY 提示以抑制传递。
  • 当设置了 model 时,刷新轮次使用该模型,不继承活动 session 的回退链,因此仅本地的后台任务不会静默回退到付费对话模型。
  • 每次压缩周期只刷新一次(在 sessions.json 中追踪)。
  • 刷新仅在嵌入式 Pi session 中运行(CLI 后端跳过)。
  • 当 session 工作区为只读时(workspaceAccess: "ro""none")跳过刷新。
  • 工作区文件布局和写入模式见 Memory

Pi 还在扩展 API 中暴露了 session_before_compact 钩子,但 OpenClaw 的刷新逻辑目前在 Gateway 侧实现。


故障排查检查清单

  • Session 键错误?Session 管理 入手,通过 /status 确认 sessionKey
  • 存储与 transcript 不匹配? 通过 openclaw status 确认 Gateway 主机和存储路径。
  • 压缩过于频繁? 检查:
    • 模型上下文窗口(是否太小)
    • 压缩配置(reserveTokens 相对于模型窗口过高会导致更早压缩)
    • 工具结果膨胀:启用/调整 session 修剪
  • 静默轮次泄漏? 确认回复确实以 NO_REPLY(不区分大小写的精确 token)开头,且你使用的版本已包含流式传输抑制修复(2026.1.10 以后)。

常见问题

sessionKey 怎么查?

在任意聊天 session 中发送 /status,第一行会显示当前 sessionKey。也可以通过 CLI 运行 openclaw sessions --json 查看所有 sessionKey 及其映射。

压缩太频繁怎么办?

检查模型上下文窗口是否太小,或者 compaction.reserveTokens 是否设置得过高(相对于模型窗口)。过高的 reserveTokens 会让压缩更早触发。也可以降低 keepRecentTokens 以减少保留的上下文量。如果工具结果膨胀,启用 session 修剪。

静默任务还是会输出内容怎么办?

确保助手的回复以 NO_REPLY(不区分大小写)开头,并且整个 payload 只有 NO_REPLY。你的 OpenClaw 版本需要 ≥ 2026.1.10 才能完全抑制流式传输中的泄漏。升级后如果问题仍然存在,检查日志确认是否存在其他输出前缀。