会话管理和压缩(深度探讨)

本文档解?OpenClaw 如何端到端管理会话:

  • 会话路由(入站消息如何映射到 sessionKey?
  • 会话存储sessions.json)及其追踪的内容
  • **转录持久?*(*.jsonl)及其结?
  • 转录卫生(运行前的提供商特定修复?
  • **上下文限?*(上下文窗口 vs 追踪?token?
  • 压缩(手?+ 自动压缩)以及在哪里挂接压缩前工?
  • 静默清理(例如不应产生用户可见输出的内存写入?

如果你想要更高级别的概述,请从以下内容开始:


真相来源:网?

OpenClaw 设计围绕拥有会话状态的单个网关进程?

  • UI(macOS 应用、Web 控制 UI、TUI)应该查询网关以获取会话列表?token 计数?
  • 在远程模式下,会话文件在远程主机上;“检查你的本?Mac 文件”不会反映网关正在使用的内容?

两个持久化层

OpenClaw 以两层持久化会话?

  1. *会话存储(sessions.json?

    • ?值映射:sessionKey -> SessionEntry
    • 小型、可变、安全可编辑(或删除条目?
    • 追踪会话元数据(当前会话 ID、最后活动、开关、token 计数器等?
  2. *转录(<sessionId>.jsonl?

    • 带有树结构的仅追加转录(条目?id + parentId?
    • 存储实际对话 + 工具调用 + 压缩摘要
    • 用于为未来轮次重建模型上下文

磁盘位置

在网关主机上,每?agent?

  • 存储:~/.openclaw/agents/<agentId>/sessions/sessions.json
  • 转录:~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
    • Telegram 主题会话:.../<sessionId>-topic-<threadId>.jsonl

OpenClaw 通过 src/config/sessions.ts 解析这些?


存储维护和磁盘控?

会话持久化有自动维护控制(session.maintenance)用?sessions.json 和转录产物:

  • modewarn(默认)?enforce
  • pruneAfter:过期条目年龄截止(默认 30d?
  • maxEntriessessions.json 中的条目上限(默?500?
  • rotateBytes:过大时轮换 sessions.json(默?10mb?
  • resetArchiveRetention*.reset.<timestamp> 转录存档的保留期(默认:?pruneAfter 相同;false 禁用清理?
  • maxDiskBytes:可选会话目录预?
  • highWaterBytes:清理后的可选目标(默认 maxDiskBytes ?80%?

磁盘预算清理的执行顺序(mode: "enforce"):

  1. 首先删除最旧的存档或孤立转录产物?
  2. 如果仍高于目标,驱逐最旧的会话条目及其转录文件?
  3. 继续直到使用量达到或低于 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:可选的显式转录路径覆盖
  • chatTypedirect | group | room(帮?UI 和发送策略)
  • providersubjectroomspacedisplayName:群?渠道标记的元数据
  • 开关:
    • thinkingLevelverboseLevelreasoningLevelelevatedLevel
    • sendPolicy(每会话覆盖?
  • 模型选择?
    • providerOverridemodelOverrideauthProfileOverride
  • Token 计数器(尽力而为/提供商相关)?
    • inputTokensoutputTokenstotalTokenscontextTokens
  • compactionCount:此会话密钥完成自动压缩的次?
  • memoryFlushAt:上次预压缩内存刷新的时间戳
  • memoryFlushCompactionCount:上次运行刷新时的压缩计?

存储可以安全编辑,但网关是权威:它可能会在会话运行时重写或重新填充条目?


转录结构(*.jsonl?

转录?@mariozechner/pi-coding-agent ?SessionManager 管理?

文件?JSONL?

  • 第一行:会话头(type: "session",包?idcwdtimestamp、可选的 parentSession?
  • 然后:带?id + parentId 的会话条目(树)

值得注意的条目类型:

  • message:用?助手/toolResult 消息
  • custom_message:进入模型上下文的扩展注入消息(可对 UI 隐藏?
  • custom:不进入模型上下文的扩展状?
  • compaction:带?firstKeptEntryId ?tokensBefore 的持久化压缩摘要
  • branch_summary:导航树分支时的持久化摘?

OpenClaw 故意**?*“修复”转录;网关使?SessionManager 来读写它们?


上下文窗?vs 追踪?token

两个不同的概念很重要?

  1. **模型上下文窗?*:每个模型的硬性限制(模型可见?token?
  2. **会话存储计数?*:写?sessions.json 的滚动统计(用于 /status 和仪表板?

如果你正在调整限制:

  • 上下文窗口来自模型目录(可以通过配置覆盖)?
  • 存储中的 contextTokens 是运行时估算/报告值;不要将其视为严格保证?

更多信息请参?/zh/token-use?


压缩:是什?

压缩将较旧的对话总结为转录中的持久化 compaction 条目,并保持最近的消息完整?

压缩后,未来轮次看到?

  • 压缩摘要
  • firstKeptEntryId 之后的消?

压缩?持久化的*(与会话剪枝不同)。请参阅 /zh/docs/concepts/session-pruning?


何时发生自动压缩(Pi 运行时)

在嵌入式 Pi agent 中,自动压缩在两种情况下触发?

  1. 溢出恢复:模型返回上下文溢出错误 ?压缩 ?重试?
  2. **阈值维?*:成功轮次后,当?

contextTokens > contextWindow - reserveTokens

其中?

  • contextWindow 是模型的上下文窗?
  • reserveTokens 是为提示 + 下一个模型输出保留的头部空间

这些?Pi 运行时语义(OpenClaw 消费事件,但 Pi 决定何时压缩)?


压缩设置(reserveTokenskeepRecentTokens?

Pi 的压缩设置位?Pi 设置中:

{
  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 还会抑制草稿/打字流式传输,因此静默操作不会在轮次中途泄露部分输出?


预压?内存刷新”(已实现?

目标:在自动压缩发生之前,运行一个静默的 agentic 轮次,将持久化状态写入磁盘(例如 agent 工作区中?memory/YYYY-MM-DD.md),这样压缩就不能擦除关键上下文?

OpenClaw 使用阈值前刷新方法?

  1. 监控会话上下文使用?
  2. 当它跨越”软阈?(低?Pi 的压缩阈值)时,运行一个静默的”立即写入内存”指令?agent?
  3. 使用 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 开头(精确令牌),并且你正在使用的版本包含流式传输抑制修复?