会话管理与压缩(深入探讨)
本文档解释了 OpenClaw 如何端到端地管理会话:
- 会话路由(入站消息如何映射到 sessionKey)
- 会话存储(sessions.json)及其跟踪内容
- 记录持久化(*.jsonl)及其结构
- 记录清理(运行前特定于提供者的修复)
- 上下文限制(上下文窗口 vs 跟踪令牌)
- 压缩(手动 + 自动压缩)以及在哪里挂钩预压缩工作
- 静默管理(例如不应产生用户可见输出的内存写入)
如果您想先了解更高级别的概述,请从以下内容开始:
真实来源:Gateway
OpenClaw 围绕拥有会话状态的单个 Gateway 进程设计。
- UI(macOS 应用、web Control UI、TUI)应查询 Gateway 以获取会话列表和令牌计数
- 在远程模式下,会话文件位于远程主机上;"检查您的本地 Mac 文件"不会反映 Gateway 正在使用的内容
两个持久化层
OpenClaw 在两层中持久化会话:
会话存储(sessions.json)
- 键/值映射:sessionKey -> SessionEntry
- 小型、可变、可安全编辑(或删除条目)
- 跟踪会话元数据(当前会话 id、上次活动、切换、令牌计数器等)
记录(.jsonl)
- 具有树结构的仅追加记录(条目具有 id + parentId)
- 存储实际对话 + 工具调用 + 压缩摘要
- 用于为未来回合重建模型上下文
磁盘位置
每个代理,在 Gateway 主机上:
- 存储:~/.openclaw/agents/<agent>/sessions/sessions.json
- 记录:~/.openclaw/agents/<agent>/sessions/<sessionId>.jsonl
Telegram 主题会话:.../<sessionKey>-topic-<topicId>.jsonl
OpenClaw 通过 src/config/sessions.ts 解析这些。
会话键(sessionKey)
sessionKey 标识您所在的对话桶(路由 + 隔离)。
常见模式:
- 主要/直接聊天(每个代理):agent:<agent>:<provider>(默认 main)
- 群组:agent:<agent>:<provider>:group:<groupId>
- 房间/频道(Discord/Slack):agent:<agent>:<provider>:channel:<channelId> 或 ...:room:<roomId>
- Cron:cron:<jobId>
- Webhook:hook:<hookId>(除非覆盖)
规范规则记录在 /concepts/session。
会话 ID(sessionId)
每个 sessionKey 指向一个当前 sessionId(继续对话的记录文件)。
经验法则:
- 重置(/new、/reset)为该 sessionKey 创建新的 sessionId
- 每日重置(默认为网关主机本地时间上午 4:00)在重置边界后的下一条消息上创建新的 sessionId
- 空闲过期(session.reset.idleMinutes 或旧版 session.idleMinutes)在空闲窗口后到达消息时创建新的 sessionId。当同时配置每日 + 空闲时,首先过期的获胜
实现细节:决策发生在 src/auto-reply/reply/session.ts 中的 initSessionState() 中。
会话存储架构(sessions.json)
存储的值类型是 src/config/sessions.ts 中的 SessionEntry。
关键字段(非详尽):
- sessionId:当前记录 id(文件名派生自此,除非设置了 sessionFile)
- updatedAt:上次活动时间戳
- sessionFile:可选的显式记录路径覆盖
- chatType:direct | group | room(帮助 UI 和发送策略)
- provider、subject、room、space、displayName:用于群组/频道标记的元数据
- 切换: thinkingLevel、verboseLevel、reasoningLevel、elevatedLevel
- sendPolicy(每个会话覆盖)
- 模型选择: providerOverride、modelOverride、authProfileOverride
- 令牌计数器(尽力而为/取决于提供者):inputTokens、outputTokens、totalTokens、contextTokens
- compactionCount:此会话键的自动压缩完成次数
- memoryFlushAt:上次预压缩内存刷新的时间戳
- memoryFlushCompactionCount:上次刷新运行时的压缩计数
存储可以安全编辑,但 Gateway 是权威:它可能会在会话运行时重写或重新水合条目。
记录结构(*.jsonl)
记录由 @mariozechner/pi-coding-agent 的 SessionManager 管理。
文件是 JSONL:
- 第一行:会话头(type: "session",包括 id、cwd、时间戳、可选 parentSession)
- 然后:具有 id + parentId 的会话条目(树)
值得注意的条目类型:
- message:用户/助手/toolResult 消息
- custom_message:扩展注入的消息,确实进入模型上下文(可以从 UI 隐藏)
- custom:不进入模型上下文的扩展状态
- compaction:带有 firstKeptEntryId 和 tokensBefore 的持久化压缩摘要
- branch_summary:导航树分支时的持久化摘要
OpenClaw 故意不"修复"记录;Gateway 使用 SessionManager 读取/写入它们。
上下文窗口 vs 跟踪令牌
两个不同的概念很重要:
- 模型上下文窗口: 每个模型的硬上限(模型可见的令牌)
- 会话存储计数器: 写入 sessions.json 的滚动统计信息(用于 /status 和仪表板)
如果您正在调整限制:
- 上下文窗口来自模型目录(可以通过配置覆盖)
- 存储中的 contextTokens 是运行时估计/报告值;不要将其视为严格保证
有关更多信息,请参见 /token-use。
压缩:它是什么
压缩将旧对话总结为记录中的持久化压缩条目,并保持最近消息完整。
压缩后,未来回合看到:
- 压缩摘要
- firstKeptEntryId 之后的消息
压缩是持久的(与会话修剪不同)。参见 /concepts/session-pruning。
何时发生自动压缩(Pi 运行时)
在嵌入的 Pi 代理中,自动压缩在两种情况下触发:
- 溢出恢复: 模型返回上下文溢出错误 → 压缩 → 重试
- 阈值维护: 在成功回合后,当:
contextTokens > contextWindow - reserveTokens
其中:
- contextWindow 是模型的上下文窗口
- reserveTokens 是为提示 + 下一个模型输出保留的余量
这些是 Pi 运行时语义(OpenClaw 消费事件,但 Pi 决定何时压缩)。
压缩设置(reserveTokens, keepRecentTokens)
Pi 的压缩设置位于 Pi 设置中:
\{
"compaction": \{
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
\}
\}
OpenClaw 还为嵌入式运行强制执行安全下限:
- 如果 compaction.reserveTokens < 16384:将其提高到 16384
原因: 在压缩变得不可避免之前,为多回合"管理"(如内存写入)留下足够的余量。
实现:src/agents/pi-settings.ts 中的 ensurePiCompactionReserveTokens()(从 src/agents/pi-embedded-runner.ts 调用)。
用户可见表面
您可以通过以下方式观察压缩和会话状态:
- /status(在任何聊天会话中)
- openclaw status(CLI)
- openclaw sessions / sessions --json
- 详细模式:🧹 自动压缩完成 + 压缩计数
静默管理(NO_REPLY)
OpenClaw 支持后台任务的"静默"回合,用户不应看到中间输出。
约定:
- 助手以 NO_REPLY 开始其输出以指示"不要向用户传递回复"
- OpenClaw 在传递层中剥离/抑制此内容
从 2026.1.10 开始,当部分块以 NO_REPLY 开头时,OpenClaw 还抑制草稿/输入流,因此静默操作不会在回合中途泄漏部分输出。
预压缩"内存刷新"(已实现)
目标: 在自动压缩发生之前,运行一个静默的代理式回合,将持久状态写入磁盘(例如代理工作区中的 memory/YYYY-MM-DD.md),以便压缩不会擦除关键上下文。
OpenClaw 使用预阈值刷新方法:
- 监视会话上下文使用情况
- 当它跨越"软阈值"(低于 Pi 的压缩阈值)时,向代理运行静默的"立即写入内存"指令
- 使用 NO_REPLY,因此用户看不到任何内容
配置(agents.defaults.compaction.memoryFlush):
- enabled(默认:true)
- softThresholdTokens(默认:4000)
- prompt(刷新回合的用户消息)
- systemPrompt(为刷新回合附加的额外系统提示)
注意:
- 默认提示/系统提示包含 NO_REPLY 提示以抑制传递
- 刷新每个压缩周期运行一次(在 sessions.json 中跟踪)
- 刷新仅适用于嵌入式 Pi 会话(CLI 后端跳过它)
- 当会话工作区为只读时跳过刷新(workspaceAccess: "ro" 或 "none")
- 有关工作区文件布局和写入模式,请参见 Memory
Pi 还在扩展 API 中公开了 session_before_compact 钩子,但 OpenClaw 的刷新逻辑今天位于 Gateway 端。
故障排除检查清单
- 会话键错误? 从 /concepts/session 开始并确认 /status 中的 sessionKey
- 存储 vs 记录不匹配? 从 openclaw status 确认 Gateway 主机和存储路径
- 压缩垃圾邮件? 检查:
- 模型上下文窗口(太小)
- 压缩设置(对于模型窗口,reserveTokens 太高可能导致更早的压缩)
- 工具结果膨胀:启用/调整会话修剪
- 静默回合泄漏? 确认回复以 NO_REPLY(精确令牌)开头,并且您使用的构建包括流抑制修复