会话管理和压缩(深度探讨)
本文档解?OpenClaw 如何端到端管理会话:
- 会话路由(入站消息如何映射到
sessionKey? - 会话存储(
sessions.json)及其追踪的内容 - **转录持久?*(
*.jsonl)及其结? - 转录卫生(运行前的提供商特定修复?
- **上下文限?*(上下文窗口 vs 追踪?token?
- 压缩(手?+ 自动压缩)以及在哪里挂接压缩前工?
- 静默清理(例如不应产生用户可见输出的内存写入?
如果你想要更高级别的概述,请从以下内容开始:
- /zh/docs/concepts/session
- /zh/docs/concepts/compaction
- /zh/docs/concepts/session-pruning
- /zh/docs/reference/transcript-hygiene
真相来源:网?
OpenClaw 设计围绕拥有会话状态的单个网关进程?
- UI(macOS 应用、Web 控制 UI、TUI)应该查询网关以获取会话列表?token 计数?
- 在远程模式下,会话文件在远程主机上;“检查你的本?Mac 文件”不会反映网关正在使用的内容?
两个持久化层
OpenClaw 以两层持久化会话?
-
*会话存储(
sessions.json?- ?值映射:
sessionKey -> SessionEntry - 小型、可变、安全可编辑(或删除条目?
- 追踪会话元数据(当前会话 ID、最后活动、开关、token 计数器等?
- ?值映射:
-
*转录(
<sessionId>.jsonl?- 带有树结构的仅追加转录(条目?
id+parentId? - 存储实际对话 + 工具调用 + 压缩摘要
- 用于为未来轮次重建模型上下文
- 带有树结构的仅追加转录(条目?
磁盘位置
在网关主机上,每?agent?
- 存储:
~/.openclaw/agents/<agentId>/sessions/sessions.json - 转录:
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl- Telegram 主题会话:
.../<sessionId>-topic-<threadId>.jsonl
- Telegram 主题会话:
OpenClaw 通过 src/config/sessions.ts 解析这些?
存储维护和磁盘控?
会话持久化有自动维护控制(session.maintenance)用?sessions.json 和转录产物:
mode:warn(默认)?enforcepruneAfter:过期条目年龄截止(默认30d?maxEntries:sessions.json中的条目上限(默?500?rotateBytes:过大时轮换sessions.json(默?10mb?resetArchiveRetention:*.reset.<timestamp>转录存档的保留期(默认:?pruneAfter相同;false禁用清理?maxDiskBytes:可选会话目录预?highWaterBytes:清理后的可选目标(默认maxDiskBytes?80%?
磁盘预算清理的执行顺序(mode: "enforce"):
- 首先删除最旧的存档或孤立转录产物?
- 如果仍高于目标,驱逐最旧的会话条目及其转录文件?
- 继续直到使用量达到或低于
highWaterBytes?
?mode: "warn" 中,OpenClaw 报告潜在驱逐但不改变存?文件?
按需运行维护?
openclaw sessions cleanup --dry-run
openclaw sessions cleanup --enforce
Cron 会话和运行日?
隔离?cron 运行也会创建会话条目/转录,它们有专门的保留控制:
cron.sessionRetention(默?24h)从会话存储中剪枝旧的隔?cron 运行会话(false禁用)?cron.runLog.maxBytes+cron.runLog.keepLines剪枝~/.openclaw/cron/runs/<jobId>.jsonl文件(默认:2,000,000 字节?2000 行)?
会话密钥(sessionKey?
sessionKey 标识你所在的_对话桶_(路?+ 隔离)?
常见模式?
- ?直接聊天(每?agent):
agent:<agentId>:<mainKey>(默?main? -agent:<agentId 群组:>:group:<id> - 房间/渠道(Discord/Slack):
agent:<agentId>:<channel>:channel:<id>?...:room:<id> - Cron:
cron:<job.id> - Webhook:
hook:<uuid>(除非覆盖)
规范规则记录?/zh/docs/concepts/session?
会话 ID(sessionId?
每个 sessionKey 指向一个当?sessionId(继续对话的转录文件)?
经验法则?
- 重置(
/new、/reset)为?sessionKey创建一个新?sessionId? - 每日重置(默认在网关主机上的凌晨 4:00)在重置边界后的下一条消息时创建一个新?
sessionId? - 空闲过期(
session.reset.idleMinutes或旧?session.idleMinutes)在空闲窗口后收到消息时创建一个新?sessionId。当同时配置了每日和空闲时,以先到期的为准? - **线程父分叉保?*(
session.parentForkMaxTokens,默?100000)在父会话已经过大时跳过父转录分叉;新线程全新开始。设置为0禁用?
实现细节:决定发生在 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:用?助手/toolResult 消息custom_message:进入模型上下文的扩展注入消息(可对 UI 隐藏?custom:不进入模型上下文的扩展状?compaction:带?firstKeptEntryId?tokensBefore的持久化压缩摘要branch_summary:导航树分支时的持久化摘?
OpenClaw 故意**?*“修复”转录;网关使?SessionManager 来读写它们?
上下文窗?vs 追踪?token
两个不同的概念很重要?
- **模型上下文窗?*:每个模型的硬性限制(模型可见?token?
- **会话存储计数?*:写?
sessions.json的滚动统计(用于 /status 和仪表板?
如果你正在调整限制:
- 上下文窗口来自模型目录(可以通过配置覆盖)?
- 存储中的
contextTokens是运行时估算/报告值;不要将其视为严格保证?
更多信息请参?/zh/token-use?
压缩:是什?
压缩将较旧的对话总结为转录中的持久化 compaction 条目,并保持最近的消息完整?
压缩后,未来轮次看到?
- 压缩摘要
firstKeptEntryId之后的消?
压缩?持久化的*(与会话剪枝不同)。请参阅 /zh/docs/concepts/session-pruning?
何时发生自动压缩(Pi 运行时)
在嵌入式 Pi agent 中,自动压缩在两种情况下触发?
- 溢出恢复:模型返回上下文溢出错误 ?压缩 ?重试?
- **阈值维?*:成功轮次后,当?
contextTokens > contextWindow - reserveTokens
其中?
contextWindow是模型的上下文窗?reserveTokens是为提示 + 下一个模型输出保留的头部空间
这些?Pi 运行时语义(OpenClaw 消费事件,但 Pi 决定何时压缩)?
压缩设置(reserveTokens、keepRecentTokens?
Pi 的压缩设置位?Pi 设置中:
{
compaction: {
enabled: true,
reserveTokens: 16384,
keepRecentTokens: 20000,
},
}
OpenClaw 还为嵌入式运行强制执行安全地板:
- 如果
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 还会抑制草稿/打字流式传输,因此静默操作不会在轮次中途泄露部分输出?
预压?内存刷新”(已实现?
目标:在自动压缩发生之前,运行一个静默的 agentic 轮次,将持久化状态写入磁盘(例如 agent 工作区中?memory/YYYY-MM-DD.md),这样压缩就不能擦除关键上下文?
OpenClaw 使用阈值前刷新方法?
- 监控会话上下文使用?
- 当它跨越”软阈?(低?Pi 的压缩阈值)时,运行一个静默的”立即写入内存”指令?agent?
- 使用
NO_REPLY使用户看不到任何内容?
配置(agents.defaults.compaction.memoryFlush):
enabled(默认:true?softThresholdTokens(默认:4000?prompt(刷新轮次的用户消息?systemPrompt(刷新轮次的额外系统提示?
注意事项?
- 默认 prompt/system prompt 包含
NO_REPLY提示以抑制传递? - 刷新在每个压缩周期运行一次(?
sessions.json中追踪)? - 刷新仅针对嵌入式 Pi 会话运行(CLI 后端跳过它)?
- 当会话工作区为只读时跳过刷新(
workspaceAccess: "ro"?"none")? - 有关工作区文件布局和写入模式,请参阅内存?
Pi 还在扩展 API 中公开?session_before_compact 钩子,但 OpenClaw 的刷新逻辑今天位于网关端?
故障排除清单
- 会话密钥错误?从 /zh/docs/concepts/session 开始并确认
/status中的sessionKey? - 存储 vs 转录不匹配?确认网关主机?
openclaw status中的存储路径? - 压缩垃圾邮件?检查:
- 模型上下文窗口(太小?
- 压缩设置(
reserveTokens对于模型窗口过高可能导致更早压缩? - 工具结果膨胀:启?调整会话剪枝
- 静默轮次泄露?确认回复以
NO_REPLY开头(精确令牌),并且你正在使用的版本包含流式传输抑制修复?