- 会话路由(传入消息如何映射到
sessionKey) - 会话存储(
sessions.json)以及它记录的内容 - 转录持久化(
*.jsonl)及其结构 - 转录清理(运行前针对特定 provider 的修复)
- 上下文限制(context window 与已跟踪 token)
- 压缩(手动和自动压缩)以及在哪里挂接预压缩工作
- 静默清理(不应产生用户可见输出的内存写入)
事实来源:Gateway
OpenClaw 围绕一个拥有会话状态的单一 Gateway 进程 设计。- UIs(macOS app、web Control UI、TUI)应向 Gateway 查询会话列表和 token 计数。
- 在远程模式下,会话文件位于远程主机上;“检查你本地的 Mac 文件”并不会反映 Gateway 正在使用的内容。
两层持久化
OpenClaw 通过两层来持久化会话:-
会话存储(
sessions.json)- 键值映射:
sessionKey -> SessionEntry - 体积小、可变、可安全编辑(或删除条目)
- 跟踪会话元数据(当前 session id、最后活动时间、开关、token 计数等)
- 键值映射:
-
转录(
<sessionId>.jsonl)- 追加写入式转录,具有树状结构(条目带有
id+parentId) - 存储实际对话 + 工具调用 + 压缩摘要
- 用于为未来轮次重建模型上下文
- 压缩检查点是压缩后继转录上的元数据。新的压缩不会再写入第二份
.checkpoint.*.jsonl副本。
- 追加写入式转录,具有树状结构(条目带有
mtimeMs/size 缓存,并在并发读取器之间共享。
磁盘上的位置
在 Gateway 主机上,每个 agent 对应:- 存储:
~/.openclaw/agents/<agentId>/sessions/sessions.json - 转录:
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl- Telegram 话题会话:
.../<sessionId>-topic-<threadId>.jsonl
- Telegram 话题会话:
src/config/sessions.ts 解析这些路径。
存储维护与磁盘控制
会话持久化带有自动维护控制(session.maintenance),用于 sessions.json、转录工件以及 trajectory sidecars:
mode:enforce(默认) 或warnpruneAfter: 过期条目的年龄截止时间(默认30d)maxEntries:sessions.json中条目上限(默认500)resetArchiveRetention:*.reset.<timestamp>转录归档的保留期(默认与pruneAfter相同;false可禁用清理)maxDiskBytes: 可选的 sessions 目录预算highWaterBytes: 清理后的可选目标值(默认maxDiskBytes的80%)
sessions.json 文件不会在每次元数据更新时都被克隆或重新读取。运行时代码应优先使用 updateSessionStore(...) 或 updateSessionStoreEntry(...);直接整库保存仅用于兼容性和离线维护工具。当 Gateway 可达时,非 dry-run 的 openclaw sessions cleanup 和 openclaw agents delete 会把存储修改委托给 Gateway,这样清理就会加入同一个 writer 队列;--store <path> 是直接文件维护的显式离线路径。maxEntries 清理在生产级规模上仍然是批处理的,因此在下一次高水位清理将其重写回去之前,存储可能会短暂超过配置上限。Gateway 启动期间,session store 读取不会修剪或限制条目;如需清理,请使用写入操作或 openclaw sessions cleanup --enforce。openclaw sessions cleanup --enforce 仍会立即应用配置的上限,并在未配置磁盘预算时清理旧的、未被引用的转录、检查点和 trajectory 工件。
维护会保留持久的外部对话指针,例如群组会话和线程范围的聊天会话,但当 cron、hook、heartbeat、ACP 和 sub-agent 的合成运行时条目超过配置的年龄、数量或磁盘预算时,仍然可以将它们移除。
OpenClaw 不再在 Gateway 写入期间创建自动的 sessions.json.bak.* 轮转备份。旧的 session.maintenance.rotateBytes 键会被忽略,且 openclaw doctor --fix 会将其从旧配置中移除。
转录修改会在转录文件上使用 session 写锁。获取锁会最多等待
session.writeLock.acquireTimeoutMs,之后才会抛出会话繁忙错误;默认值是 60000
毫秒。只有在低速机器上确实存在较长时间的准备、清理、压缩或转录镜像工作竞争时,才应提高这个值。session.writeLock.staleMs 控制现有锁何时可以
被回收为陈旧锁;默认值是 1800000 毫秒。session.writeLock.maxHoldMs 控制
进程内看门狗的释放阈值;默认值是 300000 毫秒。紧急环境变量覆盖为
OPENCLAW_SESSION_WRITE_LOCK_ACQUIRE_TIMEOUT_MS、OPENCLAW_SESSION_WRITE_LOCK_STALE_MS 和
OPENCLAW_SESSION_WRITE_LOCK_MAX_HOLD_MS。
磁盘预算清理(mode: "enforce")的执行顺序:
- 先移除最旧的归档、孤立转录或孤立 trajectory 工件。
- 如果仍高于目标值,则逐出最旧的会话条目及其转录/trajectory 文件。
- 持续进行,直到使用量低于或等于
highWaterBytes。
mode: "warn" 下,OpenClaw 会报告潜在的逐出,但不会修改存储/文件。
按需运行维护:
Cron 会话与运行日志
隔离的 cron 运行也会创建会话条目/转录,并且它们有专门的保留控制:cron.sessionRetention(默认24h) 会从会话存储中清理旧的隔离 cron 运行会话(false可禁用)。cron.runLog.keepLines会按 cron 作业清理保留的 SQLite 运行历史行(默认:2000)。cron.runLog.maxBytes仍被接受,以兼容旧的文件式运行日志。
cron:<jobId> 会话条目。它会保留安全的偏好设置,例如思考/快速/详细设置、标签,以及用户明确选择的模型/认证覆盖项。它会丢弃环境中的对话上下文,例如频道/群组路由、发送或排队策略、提权、来源以及 ACP
运行时绑定,这样一个新的隔离运行就不会从更早的运行中继承过时的传递或运行时权限。
会话键(sessionKey)
sessionKey 用于标识 你处于哪个会话桶(路由 + 隔离)。
常见模式:
- 主/直接聊天(每个 agent 一个):
agent:<agentId>:<mainKey>(默认main) - 群组:
agent:<agentId>:<channel>:group:<id> - 房间/频道(Discord/Slack):
agent:<agentId>:<channel>:channel:<id>或...:room:<id> - Cron:
cron:<job.id> - Webhook:
hook:<uuid>(除非被覆盖)
会话 id(sessionId)
每个 sessionKey 都指向一个当前 sessionId(继续对话的转录文件)。
经验法则:
- Reset (
/new,/reset) 会为该sessionKey创建一个新的sessionId。 - Daily reset(默认在 gateway 主机本地时间凌晨 4:00)会在重置边界后的下一条消息时创建新的
sessionId。 - Idle expiry(
session.reset.idleMinutes或旧版session.idleMinutes)会在消息于空闲窗口之后到达时创建新的sessionId。当 daily 和 idle 同时配置时,哪个先过期就以哪个为准。 - System events(heartbeat、cron 唤醒、exec 通知、gateway 记账)可能会修改会话行,但不会延长 daily/idle reset 的新鲜度。重置轮换会在构建新提示词之前丢弃上一会话的排队系统事件通知。
- Parent fork policy 在创建线程或 subagent fork 时使用 OpenClaw 的活动分支。如果该分支过大,OpenClaw 会为子级启动隔离上下文,而不是失败或继承不可用的历史。分支大小策略是自动的;旧的
session.parentForkMaxTokens配置已被openclaw doctor --fix移除。
src/auto-reply/reply/session.ts 中的 initSessionState()。
会话存储 schema(sessions.json)
存储的值类型是 src/config/sessions.ts 中的 SessionEntry。
关键字段(非完整):
sessionId:当前转录 id(除非设置了sessionFile,否则文件名由此派生)sessionStartedAt:当前sessionId的开始时间戳;daily reset 新鲜度使用它。旧行可能从 JSONL 会话头部中推导出它。lastInteractionAt:最后一次真实用户/频道交互时间戳;idle reset 新鲜度使用它,因此 heartbeat、cron 和 exec 事件不会让会话 保持活跃。没有此字段的旧行会回退到恢复出的会话开始时间 来判断 idle 新鲜度。updatedAt:最后一次存储行修改时间戳,用于列表、清理和 记账。它不是 daily/idle reset 新鲜度的权威依据。sessionFile:可选的显式转录路径覆盖chatType:direct | group | room(帮助 UI 和发送策略)provider、subject、room、space、displayName:用于群组/频道标记的元数据- 开关:
thinkingLevel、verboseLevel、reasoningLevel、elevatedLevelsendPolicy(每会话覆盖)
- 模型选择:
providerOverride、modelOverride、authProfileOverride
- token 计数器(尽力而为 / 依赖 provider):
inputTokens、outputTokens、totalTokens、contextTokens
compactionCount:该 sessionKey 的自动压缩完成了多少次memoryFlushAt:上一次预压缩内存刷新时间戳memoryFlushCompactionCount:上一次刷新运行时的压缩计数
转录结构(*.jsonl)
转录由 openclaw/plugin-sdk/agent-sessions 的 SessionManager 管理。
文件采用 JSONL 格式:
- 第一行:会话头部(
type: "session",包含id、cwd、timestamp、可选的parentSession) - 然后:带有
id+parentId的会话条目(树状)
message:用户/assistant/toolResult 消息custom_message:扩展注入的消息,这些消息 会 进入模型上下文(可以在 UI 中隐藏)custom:扩展状态,这些内容 不会 进入模型上下文compaction:持久化的压缩摘要,包含firstKeptEntryId和tokensBeforebranch_summary:在导航树分支时持久化的摘要
SessionManager 读写它们。
上下文窗口 vs 已跟踪 token
有两个不同的概念很重要:- 模型上下文窗口:每个模型的硬上限(对模型可见的 token)
- 会话存储计数器:写入
sessions.json的滚动统计(用于 /status 和仪表盘)
- 上下文窗口来自模型目录(也可以通过配置覆盖)。
- 存储中的
contextTokens是运行时估算/报告值;不要把它当作严格保证。
压缩:它是什么
压缩会把较早的对话总结为转录中的一个持久化compaction 条目,并保留最近的消息不变。
压缩后,后续回合会看到:
- 压缩摘要
firstKeptEntryId之后的消息
agents.defaults.compaction.postCompactionSections; when unset or [],
OpenClaw does not append AGENTS.md excerpts on top of the compaction summary.
压缩是持久化的(不同于会话修剪)。参见 /concepts/session-pruning。
压缩块边界与工具配对
当 OpenClaw 将一段较长的转录拆分为压缩块时,它会保持 assistant 工具调用与其匹配的toolResult 条目成对。
- 如果按 token 占比分割的位置落在工具调用和其结果之间,OpenClaw 会将边界移到 assistant 的工具调用消息处,而不是把这对内容分开。
- 如果尾随的工具结果块本会把分块推过目标大小,OpenClaw 会保留该 待处理的工具块,并保持未总结的尾部完整。
- 被中止/出错的工具调用块不会使待处理分割继续保持开启。
When auto-compaction happens (OpenClaw runtime)
在嵌入式 OpenClaw agent 中,自动压缩会在两种情况下触发:- 溢出恢复:模型返回上下文溢出错误
(
request_too_large,context length exceeded,input exceeds the maximum number of tokens,input token count exceeds the maximum number of input tokens,input is too long for the model,ollama error: context length exceeded, 以及类似的 provider 变体) → 压缩 → 重试。 当 provider 报告尝试使用的 token 数时,OpenClaw 会将该观察到的计数传递到溢出恢复压缩中。如果 provider 确认了溢出但没有提供可解析的计数,OpenClaw 会向压缩引擎和诊断传递一个仅略微超出预算的合成计数。 如果溢出恢复仍然失败,OpenClaw 会向用户显示明确的指导,并保留当前会话映射,而不是静默地把 session key 轮换为一个新的 session id。下一步由操作员控制:重试该消息、运行/compact,或者在需要新会话时运行/new。 - 阈值维护:在一次成功回合之后,当:
contextTokens > contextWindow - reserveTokens
其中:
contextWindow是模型的上下文窗口reserveTokens是为提示词 + 下一次模型输出预留的头部空间
agents.defaults.compaction.maxActiveTranscriptBytes,并且活跃转录文件达到该大小时,OpenClaw 也可以在打开下一次运行之前触发一次预检的本地压缩。这是用于本地重开成本的文件大小保护,不是原始归档:OpenClaw 仍然执行正常的语义压缩,并且它要求 truncateAfterCompaction,这样压缩后的摘要才能成为新的后继转录。
对于嵌入式 OpenClaw 运行,agents.defaults.compaction.midTurnPrecheck.enabled: true
会添加一个可选的工具循环防护。在追加工具结果之后、下一次模型调用之前,
OpenClaw 会使用与轮次开始时相同的预检预算逻辑来估算提示压力。如果上下文不再能容纳,它
不会在 OpenClaw 运行时的 transformContext 钩子中进行压缩。它会发出一个结构化的
mid-turn precheck 信号,停止当前提示提交,并让外层运行循环使用现有的恢复路径:
在足够时截断过大的工具结果,或者触发配置的压缩模式并重试。该选项默认禁用,
并且同时适用于 default 和 safeguard 压缩模式,包括基于 provider 的 safeguard 压缩。
这与 maxActiveTranscriptBytes 相互独立:字节大小防护在轮次开始前运行,而 mid-turn precheck
在嵌入式 OpenClaw 工具循环中、追加了新的工具结果之后更晚执行。
压缩设置(reserveTokens、keepRecentTokens)
OpenClaw 运行时的压缩设置位于 agent 设置中:
- 如果
compaction.reserveTokens < reserveTokensFloor,OpenClaw 会将其提升。 - 默认下限为
20000个 token。 - 设置
agents.defaults.compaction.reserveTokensFloor: 0可禁用该下限。 - 如果它本来就更高,OpenClaw 会保持不变。
- 手动
/compact会遵循显式设置的agents.defaults.compaction.keepRecentTokens并保留 OpenClaw 运行时最近尾部的切点。若未显式设置保留预算, 手动压缩仍然是一个硬检查点,重建后的上下文将从新的摘要开始。 - 设置
agents.defaults.compaction.midTurnPrecheck.enabled: true可在新工具结果到达后、 下一次模型调用前运行可选的工具循环预检查。 这只是一个触发条件;摘要生成仍然使用已配置的压缩路径。 它与maxActiveTranscriptBytes相互独立,后者是一个在轮次开始时对活动转录字节大小进行保护的检查。 - 将
agents.defaults.compaction.maxActiveTranscriptBytes设置为字节值或 类似"20mb"的字符串,可以在活动转录变大时于轮次开始前执行本地压缩。 该保护仅在同时启用truncateAfterCompaction时生效。保留未设置或设为0可禁用。 - 启用
agents.defaults.compaction.truncateAfterCompaction时, OpenClaw 会在压缩后将活动转录轮换为压缩后的后继 JSONL。 分支/恢复检查点操作会使用该压缩后的后继;在被引用期间,旧的压缩前检查点文件仍可读取。
ensureAgentCompactionReserveTokens() in src/agents/agent-settings.ts
(called from src/agents/embedded-agent-runner.ts).
可插拔压缩提供方
插件可以通过插件 API 上的registerCompactionProvider() 注册一个压缩提供方。当 agents.defaults.compaction.provider 设置为一个已注册的提供方 id 时,safeguard 扩展会将摘要工作委派给该提供方,而不是内置的 summarizeInStages 管道。
provider:已注册压缩提供方插件的 id。若保留未设置,则使用默认的 LLM 摘要。- 设置
provider会强制mode: "safeguard"。 - 提供方接收与内置路径相同的压缩指令和标识保留策略。
- safeguard 在提供方输出后仍然会保留最近轮次和分割轮次的后缀上下文。
- 内置的 safeguard 摘要会用新消息重新提炼先前摘要, 而不是逐字保留完整的前一个摘要。
- safeguard 模式默认启用摘要质量审计;设置
qualityGuard.enabled: false可跳过对格式不良输出的重试行为。 - 如果提供方失败或返回空结果,OpenClaw 会自动回退到内置的 LLM 摘要。
- 中止/超时信号会被重新抛出(不会被吞掉),以尊重调用方取消。
src/plugins/compaction-provider.ts, src/agents/agent-hooks/compaction-safeguard.ts.
用户可见的表面
你可以通过以下方式观察压缩和会话状态:/status(在任意聊天会话中)openclaw status(CLI)openclaw sessions/sessions --json- Gateway 日志(
pnpm gateway:watch或openclaw logs --follow):embedded run auto-compaction start+complete - 详细模式:
🧹 自动压缩完成+ 压缩计数
静默事务处理(NO_REPLY)
OpenClaw 支持用于后台任务的“静默”轮次,在这些轮次中,用户不应看到中间输出。
约定:
- 助手以精确的静默令牌
NO_REPLY/no_reply开头输出,以表示“不要向用户发送回复”。 - OpenClaw 会在交付层中移除/抑制这一内容。
- 精确静默令牌的抑制是大小写不敏感的,因此
NO_REPLY和no_reply在整个载荷仅为静默令牌时都算有效。 - 这仅适用于真正的后台/不交付轮次;它不是普通可操作用户请求的快捷方式。
2026.1.10,当部分块以 NO_REPLY 开头时,OpenClaw 也会抑制 草稿/输入流式输出,因此静默操作不会在轮次中途泄露部分输出。
预压缩“内存刷新”(已实现)
目标:在自动压缩发生之前,运行一个静默的 agent 轮次,将持久状态写入磁盘(例如 agent 工作区中的memory/YYYY-MM-DD.md),以便压缩不会抹除关键上下文。
OpenClaw 使用 预阈值刷新 方法:
- 监控会话上下文使用量。
- 当它超过一个“软阈值”(低于 OpenClaw 运行时的压缩阈值)时,向 agent 运行一个静默的 “现在写入内存” 指令。
- 使用精确的静默令牌
NO_REPLY/no_reply,这样用户就 不会看到任何内容。
agents.defaults.compaction.memoryFlush):
enabled(默认:true)model(可选的精确 provider/model 覆盖,用于刷新轮次,例如ollama/qwen3:8b)softThresholdTokens(默认:4000)prompt(刷新轮次的用户消息)systemPrompt(为刷新轮次附加的额外系统提示)
- 默认的 prompt/system prompt 包含
NO_REPLY提示,以抑制交付。 - 当设置了
model时,刷新轮次会使用该模型,而不会继承 活动会话的回退链,因此本地仅处理的维护工作不会静默回退到付费对话模型。 - 刷新每个压缩周期运行一次(在
sessions.json中跟踪)。 - 刷新仅针对嵌入式 OpenClaw 会话运行(CLI 后端会跳过)。
- 当会话工作区为只读(
workspaceAccess: "ro"或"none")时会跳过刷新。 - 有关工作区文件布局和写入模式,请参见 Memory。
session_before_compact hook,但 OpenClaw 的
刷新逻辑目前位于 Gateway 端。
故障排查清单
- Session key 不对?先查看 /concepts/session,并在
/status中确认sessionKey。 - 存储与转录不匹配?请确认 Gateway 主机以及
openclaw status中的存储路径。 - 压缩过于频繁?检查:
- 模型上下文窗口(太小)
- 压缩设置(
reserveTokens对模型窗口来说可能过高,导致更早触发压缩) - 工具结果膨胀:启用/调整会话剪枝
- 静默轮次泄露?确认回复以
NO_REPLY开头(大小写不敏感的精确令牌),并且你使用的是包含流式抑制修复的构建版本。