OpenClaw 白皮书
会话管理与压缩(深入了解)
本文档解释 OpenClaw 如何端到端管理会话:
- ✦会话路由(入站消息如何映射到
sessionKey) - ✦会话存储(
sessions.json)及其跟踪的内容 - ✦记录持久化(
*.jsonl)及其结构 - ✦记录清理(运行前的提供商特定修复)
- ✦上下文限制(上下文窗口 vs 跟踪的 token 数)
- ✦压缩(手动 + 自动压缩)以及在何处挂接压缩前工作
- ✦静默内务处理(例如不应产生用户可见输出的记忆写入)
如果你想先了解更高层次的概述,请从以下内容开始:
事实来源:Gateway 网关
OpenClaw 围绕一个拥有会话状态的单一 Gateway 网关进程设计。
- ✦UI(macOS 应用、web 控制 UI、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 话题会话:
OpenClaw 通过
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>(除非被覆盖)
规范规则记录在 /concepts/session。
会话 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、elevatedLevel - ✦
sendPolicy(每会话覆盖)
- ✦
- ✦模型选择:
- ✦
providerOverride、modelOverride、authProfileOverride
- ✦
- ✦Token 计数器(尽力而为/依赖提供商):
- ✦
inputTokens、outputTokens、totalTokens、contextTokens
- ✦
- ✦
compactionCount:此会话键完成自动压缩的次数 - ✦
memoryFlushAt:最后一次压缩前记忆刷新的时间戳 - ✦
memoryFlushCompactionCount:最后一次刷新运行时的压缩计数
存储可以安全编辑,但 Gateway 网关是权威:它可能会在会话运行时重写或重新水合条目。
记录结构(*.jsonl)
记录由
@mariozechner/pi-coding-agent 的 SessionManager 管理。文件是 JSONL 格式:
- ✦第一行:会话头(
type: "session",包括id、cwd、timestamp、可选的parentSession) - ✦然后:带有
id+parentId的会话条目(树形结构)
值得注意的条目类型:
- ✦
message:用户/助手/工具结果消息 - ✦
custom_message:扩展注入的消息,确实进入模型上下文(可以从 UI 隐藏) - ✦
custom:不进入模型上下文的扩展状态 - ✦
compaction:持久化的压缩摘要,带有firstKeptEntryId和tokensBefore - ✦
branch_summary:导航树分支时的持久化摘要
OpenClaw 有意不"修复"记录;Gateway 网关使用
SessionManager 来读/写它们。上下文窗口 vs 跟踪的 token
两个不同的概念很重要:
- ✦模型上下文窗口:每个模型的硬上限(模型可见的 token)
- ✦会话存储计数器:写入
sessions.json的滚动统计(用于 /status 和仪表板)
如果你在调整限制:
- ✦上下文窗口来自模型目录(可以通过配置覆盖)。
- ✦存储中的
contextTokens是运行时估计/报告值;不要将其视为严格保证。
更多信息,参见 /token-use。
压缩:它是什么
压缩将较旧的对话总结为记录中的持久化
compaction 条目,并保持最近的消息不变。压缩后,未来的回合会看到:
- ✦压缩摘要
- ✦
firstKeptEntryId之后的消息
压缩是持久化的(与会话修剪不同)。参见 /concepts/session-pruning。
自动压缩何时发生(Pi 运行时)
在嵌入式 Pi 智能体中,自动压缩在两种情况下触发:
- ✦溢出恢复:模型返回上下文溢出错误 → 压缩 → 重试。
- ✦阈值维护:在成功的回合后,当:
contextTokens > contextWindow - reserveTokens其中:
- ✦
contextWindow是模型的上下文窗口 - ✦
reserveTokens是为提示 + 下一个模型输出保留的空间
这些是 Pi 运行时语义(OpenClaw 消费事件,但 Pi 决定何时压缩)。
压缩设置(reserveTokens、keepRecentTokens)
Pi 的压缩设置位于 Pi 设置中:
json5{ compaction: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000, }, }
OpenClaw 还为嵌入式运行强制执行安全下限:
- ✦如果
compaction.reserveTokens < reserveTokensFloor,OpenClaw 会提升它。 - ✦默认下限是
20000个 token。 - ✦设置
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(为刷新回合附加的额外系统提示)
说明:
- ✦默认的提示/系统提示包含
NO_REPLY提示以抑制投递。 - ✦刷新每个压缩周期运行一次(在
sessions.json中跟踪)。 - ✦刷新仅对嵌入式 Pi 会话运行(CLI 后端跳过它)。
- ✦当会话工作空间是只读时(
workspaceAccess: "ro"或"none"),刷新会被跳过。 - ✦参见记忆了解工作空间文件布局和写入模式。
Pi 还在扩展 API 中公开了
session_before_compact 钩子,但 OpenClaw 的刷新逻辑目前位于 Gateway 网关端。故障排除检查清单
- ✦会话键错误?从 /concepts/session 开始,并在
/status中确认sessionKey。 - ✦存储 vs 记录不匹配?从
openclaw status确认 Gateway 网关主机和存储路径。 - ✦压缩过于频繁?检查:
- ✦模型上下文窗口(太小)
- ✦压缩设置(
reserveTokens对于模型窗口来说太高会导致更早的压缩) - ✦工具结果膨胀:启用/调整会话修剪
- ✦静默回合泄漏?确认回复以
NO_REPLY(精确 token)开头,并且你使用的构建版本包含流式输出抑制修复。