会话管理与压缩(深入解析)
本文档解释 OpenClaw 如何端到端管理会话:- 会话路由(入站消息如何映射到
sessionKey) - 会话存储(
sessions.json)及其跟踪的内容 - 对话记录持久化(
*.jsonl)及其结构 - 对话记录清理(运行前的提供商特定修复)
- 上下文限制(上下文窗口 vs 跟踪的 token 数)
- 压缩(手动 + 自动压缩)以及压缩前工作的挂钩位置
- 静默清理(例如不应产生用户可见输出的记忆写入)
数据源:Gateway网关
OpenClaw 围绕单个 Gateway网关进程设计,该进程拥有会话状态。- UI(macOS 应用、Web 控制界面、TUI)应向 Gateway网关查询会话列表和 token 计数。
- 在远程模式下,会话文件位于远程主机上;“检查本地 Mac 文件”不会反映 Gateway网关正在使用的内容。
两个持久化层
OpenClaw 通过两个层持久化会话:-
会话存储(
sessions.json)- 键值映射:
sessionKey -> SessionEntry - 体积小、可变、可安全编辑(或删除条目)
- 跟踪会话元数据(当前会话 ID、最后活动时间、开关、token 计数器等)
- 键值映射:
-
对话记录(
<sessionId>.jsonl)- 具有树结构的追加写入对话记录(条目包含
id+parentId) - 存储实际对话 + 工具调用 + 压缩摘要
- 用于重建未来轮次的模型上下文
- 具有树结构的追加写入对话记录(条目包含
磁盘位置
每个智能体,在 Gateway网关主机上:- 存储:
~/.openclaw/agents/<agentId>/sessions/sessions.json - 对话记录:
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl- Telegram 话题会话:
.../<sessionId>-topic-<threadId>.jsonl
- Telegram 话题会话:
src/config/sessions.ts 解析这些路径。
会话键(sessionKey)
sessionKey 标识你所在的对话桶(路由 + 隔离)。
常见模式:
- 主/直接聊天(每个智能体):
agent:<agentId>:<mainKey>(默认main) - 群组:
agent:<agentId>:<channel>:group:<id> - 房间/频道(Discord/Slack):
agent:<agentId>:<channel>:channel:<id>或...:room:<id> - 定时任务:
cron:<job.id> - Webhook:
hook:<uuid>(除非被覆盖)
会话 ID(sessionId)
每个 sessionKey 指向一个当前的 sessionId(继续对话的对话记录文件)。
经验法则:
- 重置(
/new、/reset)为该sessionKey创建新的sessionId。 - 每日重置(默认为 Gateway网关主机本地时间凌晨 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、elevatedLevelsendPolicy(每会话覆盖)
- 模型选择:
providerOverride、modelOverride、authProfileOverride
- Token 计数器(尽力而为 / 依赖提供商):
inputTokens、outputTokens、totalTokens、contextTokens
compactionCount:该会话键的自动压缩完成次数memoryFlushAt:上次压缩前记忆刷写的时间戳memoryFlushCompactionCount:上次刷写运行时的压缩计数
对话记录结构(*.jsonl)
对话记录由 @mariozechner/pi-coding-agent 的 SessionManager 管理。
文件格式为 JSONL:
- 第一行:会话头(
type: "session",包含id、cwd、timestamp、可选parentSession) - 之后:带
id+parentId的会话条目(树结构)
message:用户/助手/工具结果消息custom_message:扩展注入的消息,会进入模型上下文(可从 UI 隐藏)custom:扩展状态,不会进入模型上下文compaction:持久化的压缩摘要,包含firstKeptEntryId和tokensBeforebranch_summary:导航树分支时的持久化摘要
SessionManager 来读写它们。
上下文窗口 vs 跟踪的 token
两个不同的概念很重要:- 模型上下文窗口:每个模型的硬上限(模型可见的 token 数)
- 会话存储计数器:写入
sessions.json的滚动统计(用于 /status 和仪表板)
- 上下文窗口来自模型目录(可通过配置覆盖)。
- 存储中的
contextTokens是运行时估算/报告值;不要将其视为严格保证。
压缩:什么是压缩
压缩将较旧的对话总结为对话记录中的持久化compaction 条目,并保持最近的消息不变。
压缩后,后续轮次会看到:
- 压缩摘要
firstKeptEntryId之后的消息
自动压缩何时触发(Pi 运行时)
在嵌入式 Pi 智能体中,自动压缩在两种情况下触发:- 溢出恢复:模型返回上下文溢出错误 → 压缩 → 重试。
- 阈值维护:在成功的轮次之后,当:
contextTokens > contextWindow - reserveTokens
其中:
contextWindow是模型的上下文窗口reserveTokens是为提示词 + 下一次模型输出预留的余量
压缩设置(reserveTokens、keepRecentTokens)
Pi 的压缩设置位于 Pi 设置中:
- 如果
compaction.reserveTokens < reserveTokensFloor,OpenClaw 会将其提升。 - 默认下限为
20000token。 - 设置
agents.defaults.compaction.reserveTokensFloor: 0可禁用下限。 - 如果已经更高,OpenClaw 不做修改。
src/agents/pi-settings.ts 中的 ensurePiCompactionReserveTokens()
(从 src/agents/pi-embedded-runner.ts 调用)。
用户可见界面
你可以通过以下方式观察压缩和会话状态:/status(在任何聊天会话中)openclaw status(CLI)openclaw sessions/sessions --json- 详细模式:
🧹 Auto-compaction complete+ 压缩计数
静默清理(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(刷写轮次追加的额外系统提示词)
- 默认的 prompt/systemPrompt 包含
NO_REPLY提示以抑制传递。 - 每个压缩周期刷写运行一次(在
sessions.json中跟踪)。 - 刷写仅在嵌入式 Pi 会话中运行(CLI 后端跳过)。
- 当会话工作区为只读(
workspaceAccess: "ro"或"none")时跳过刷写。 - 工作区文件布局和写入模式参阅记忆。
session_before_compact 钩子,但 OpenClaw 的刷写逻辑目前在 Gateway网关侧。
故障排除清单
- 会话键错误?从 /concepts/session 开始,确认
/status中的sessionKey。 - 存储与对话记录不匹配?确认 Gateway网关主机以及
openclaw status中的存储路径。 - 压缩过于频繁?检查:
- 模型上下文窗口(太小)
- 压缩设置(
reserveTokens相对于模型窗口过高可能导致更早触发压缩) - 工具结果膨胀:启用/调整会话修剪
- 静默轮次泄露?确认回复以
NO_REPLY(精确 token)开头,且你使用的构建版本包含流式传输抑制修复。