Appearance
本页详解 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:
Session 存储(
sessions.json)- 键值映射:
sessionKey -> SessionEntry - 体积小、可编辑,可以安全修改或删除条目
- 追踪 session 元数据:当前 session id、最后活动时间、开关状态、token 计数器等
- 键值映射:
Transcript(
<sessionId>.jsonl)- 仅追加的 transcript,具有树形结构(条目包含
id+parentId) - 存储实际对话 + 工具调用 + 压缩摘要
- 用于重建未来轮次的模型上下文
- 大 transcript 在预压缩检查点阶段会跳过复制,避免出现第二个巨量
.checkpoint.*.jsonl文件
- 仅追加的 transcript,具有树形结构(条目包含
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
- Telegram 话题 session:
OpenClaw 通过 src/config/sessions.ts 解析上述路径。
存储维护与磁盘控制
Session 持久化具有自动维护控制(session.maintenance),覆盖 sessions.json、transcript 文件和轨迹 sidecar:
mode:warn(默认)或enforcepruneAfter:旧条目年龄阈值(默认30d)maxEntries:sessions.json条目上限(默认500)resetArchiveRetention:*.reset.<timestamp>transcript 归档文件的保留期(默认与pruneAfter相同;设为false禁用清理)maxDiskBytes:可选的 sessions 目录总容量上限highWaterBytes:清理后的目标用量(默认为maxDiskBytes的80%)
Gateway 正常写入走每条 session 的写入器,串行化进程内变更,不占用运行时文件锁。热路径补丁助手在持有写入器槽位时借用已验证的可变缓存,避免大文件每次元数据更新都克隆或重新读取。运行时代码应优先使用 updateSessionStore(...) 或 updateSessionStoreEntry(...);直接全存储保存是兼容性和离线维护工具。当 Gateway 可达时,openclaw sessions cleanup 和 openclaw agents delete 的非 dry-run 模式将存储变更委托给 Gateway,使其加入同一写入器队列;--store <path> 是直接文件维护的离线修复路径。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_MS、OPENCLAW_SESSION_WRITE_LOCK_STALE_MS、OPENCLAW_SESSION_WRITE_LOCK_MAX_HOLD_MS。
磁盘预算清理的执行顺序(mode: "enforce"):
- 首先删除最旧的归档、孤立 transcript 或孤立轨迹文件。
- 若仍高于目标,驱逐最旧的 session 条目及其 transcript/trajectory 文件。
- 持续清理直到用量降至
highWaterBytes以下。
在 mode: "warn" 下,OpenClaw 报告可能的驱逐操作,但不修改存储/文件。
手动触发维护:
bash
openclaw sessions cleanup --dry-run
openclaw sessions cleanup --enforceCron 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>:<channel>:group:<id> - 房间/频道(Discord/Slack):
agent:<agentId>:<channel>:channel:<id>或...:room:<id> - Cron:
cron:<job.id> - Webhook:
hook:<uuid>(除非被覆盖)
权威规则文档见 /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.ts 的 initSessionState() 中。
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 路径覆盖chatType:direct | group | room(帮助 UI 和发送策略)provider、subject、room、space、displayName:群组/频道标签元数据- 开关:
thinkingLevel、verboseLevel、reasoningLevel、elevatedLevelsendPolicy(每 session 覆盖)
- 模型选择:
providerOverride、modelOverride、authProfileOverride
- Token 计数器(尽力估算 / 取决于提供商):
inputTokens、outputTokens、totalTokens、contextTokens
compactionCount:该 session key 完成自动压缩的次数memoryFlushAt:上次 pre-compaction 内存刷新的时间戳memoryFlushCompactionCount:上次刷新时的压缩计数
存储可以安全编辑,但 Gateway 是最终权威:它可能在 session 运行时重写或重新填充条目。
Transcript 结构(*.jsonl)
Transcripts 由 @earendil-works/pi-coding-agent 的 SessionManager 管理。
文件为 JSONL 格式:
- 第一行:session 头部(
type: "session",包含id、cwd、timestamp,可选parentSession) - 之后:包含
id+parentId的 session 条目(树形结构)
主要条目类型:
message:用户/助手/工具结果消息custom_message:扩展注入的消息,_会_进入模型上下文(可对 UI 隐藏)custom:不进入模型上下文的扩展状态compaction:持久化的压缩摘要,包含firstKeptEntryId和tokensBeforebranch_summary:导航树形分支时持久化的摘要
OpenClaw 刻意不"修复" transcript;Gateway 使用 SessionManager 读写 transcript。
上下文窗口 vs 已追踪 Token 数
两个概念需要区分:
- 模型上下文窗口:每个模型的硬性上限(模型可见的 token 数)
- Session 存储计数器:写入
sessions.json的滚动统计(用于 /status 和仪表板)
调整限制时:
- 上下文窗口来自模型目录(可通过配置覆盖)。
- 存储中的
contextTokens是运行时估算/报告值;不要将其视为严格保证。
更多内容见 Token 使用参考。
压缩机制:是什么
压缩将较旧的对话汇总为 transcript 中一个持久化的 compaction 条目,并保留最近的消息完整。
压缩后,后续轮次将看到:
- 压缩摘要
firstKeptEntryId之后的消息
压缩是持久化的(与 session 修剪不同)。参见 Session 修剪。
压缩块边界与工具配对
当 OpenClaw 将长 transcript 拆分为压缩块时,它会保持助手工具调用与对应的 toolResult 条目成对。
- 如果 token 份额分割落在工具调用和其结果之间,OpenClaw 会将边界移到助手工具调用消息,而不是分离配对。
- 如果尾部工具结果块会使块超目标,OpenClaw 会保留该挂起的工具块,并保持未汇总的尾部完整。
- 中止/错误的工具调用块不会保持挂起分割开放。
自动压缩触发时机(Pi 运行时)
在嵌入式 Pi Agent 中,自动压缩在两种情况下触发:
- 溢出恢复:模型返回上下文溢出错误(
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以及类似提供商标记的错误)→ 压缩 → 重试。如果溢出恢复仍然失败,OpenClaw 会向用户显示明确的指导,并保留当前 session 映射,而不是静默地将 session key 旋转到新的 session id。下一步由操作员控制:重试消息、运行/compact或运行/new以开启新 session。 - 阈值维护:成功轮次结束后,当
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 钩子内部压缩。它会引发一个结构化的中轮预检信号,停止当前提示提交,并让外部运行循环使用现有的恢复路径:在适当时截断过大的工具结果,或触发配置的压缩模式并重试。该选项默认禁用,与 default 和 safeguard 压缩模式都兼容,包括提供商支持的 safeguard 压缩。这与 maxActiveTranscriptBytes 独立:字节大小保护在轮次开始前运行,而中轮预检在嵌入式 Pi 工具循环中、追加新工具结果之后运行。
压缩设置(reserveTokens、keepRecentTokens)
Pi 的压缩配置存在于 Pi 设置中:
json5
{
compaction: {
enabled: true,
reserveTokens: 16384,
keepRecentTokens: 20000,
},
}OpenClaw 还对嵌入式运行强制设置安全下限:
- 如果
compaction.reserveTokens < reserveTokensFloor,OpenClaw 会将其提升。 - 默认下限为
20000token。 - 设置
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.ts、src/agents/pi-hooks/compaction-safeguard.ts。
用户可见的入口
你可以通过以下方式观察压缩和 session 状态:
/status(在任意聊天 session 中)openclaw status(CLI)openclaw sessions/sessions --json- Gateway 日志(
pnpm gateway:watch或openclaw logs --follow):embedded run auto-compaction start+complete - 详细模式:
🧹 Auto-compaction complete+ 压缩计数
静默后台任务(NO_REPLY)
OpenClaw 支持用于后台任务的"静默"轮次,用户不应看到中间输出。
约定:
- 助手以
NO_REPLY/no_reply开头输出,表示"不要向用户发送回复"。 - OpenClaw 在传递层过滤/抑制此内容。
- 确切静默令牌的抑制不区分大小写,因此
NO_REPLY和no_reply都算,只要整个 payload 就是该静默令牌。 - 仅用于真正的后台/无传递轮次;不适用于普通可操作的用户请求。
自 2026.1.10 起,当部分块以 NO_REPLY 开头时,OpenClaw 还会抑制草稿/输入中流式传输,防止静默操作在轮次中间泄漏部分输出。
Pre-Compaction "内存刷新"(已实现)
目标:在自动压缩发生前,运行一个静默的 agentic 轮次,将持久化状态写入磁盘(例如,Agent 工作区中的 memory/YYYY-MM-DD.md),防止压缩抹去关键上下文。
OpenClaw 采用预阈值刷新方式:
- 监控 session 上下文使用量。
- 当使用量越过"软阈值"(低于 Pi 的压缩阈值)时,向 Agent 发出静默的"立即写入内存"指令。
- 使用
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 才能完全抑制流式传输中的泄漏。升级后如果问题仍然存在,检查日志确认是否存在其他输出前缀。