OpenClaw 在以下这些方面端到端管理会话:Documentation Index
Fetch the complete documentation index at: https://openclaw.zhcndoc.com/llms.txt
Use this file to discover all available pages before exploring further.
- 会话路由(传入消息如何映射到
sessionKey) - 会话存储(
sessions.json)以及它记录的内容 - 转录持久化(
*.jsonl)及其结构 - 转录清理(运行前针对特定 provider 的修复)
- 上下文限制(context window 与已跟踪 token)
- 压缩(手动和自动压缩)以及在哪里挂接预压缩工作
- 静默清理(不应产生用户可见输出的内存写入)
事实来源:Gateway
OpenClaw 围绕一个拥有会话状态的单一 Gateway 进程 设计。- UI(macOS 应用、网页 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:warn(默认)或enforcepruneAfter:陈旧条目的年龄截止阈值(默认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 启动期间不会在会话存储读取时修剪或限制条目;请使用写入或 openclaw sessions cleanup --enforce 来清理。openclaw sessions cleanup --enforce 仍会立即应用配置上限。
维护会保留持久的外部对话指针,例如群组会话和线程范围的聊天会话,但当 cron、hook、heartbeat、ACP 和 sub-agent 的合成运行时条目超过配置的年龄、数量或磁盘预算时,仍然可以将它们移除。
OpenClaw 不再在 Gateway 写入期间创建自动的 sessions.json.bak.* 轮转备份。旧的 session.maintenance.rotateBytes 键会被忽略,且 openclaw doctor --fix 会将其从旧配置中移除。
转录变更在转录文件上使用 session 写锁。获取锁会等待最多
session.writeLock.acquireTimeoutMs,之后才会抛出忙碌会话错误;默认值是 60000
毫秒。只有在慢机器上合法的预处理、清理、压缩或转录镜像工作持续竞争更久时才提高这个值。陈旧锁检测和最大持有时长警告仍然是独立策略。
磁盘预算清理(mode: "enforce")的执行顺序:
- 先移除最旧的归档、孤立转录或孤立 trajectory 工件。
- 如果仍高于目标值,则逐出最旧的会话条目及其转录/trajectory 文件。
- 持续进行,直到使用量低于或等于
highWaterBytes。
mode: "warn" 下,OpenClaw 会报告潜在的逐出,但不会修改存储/文件。
按需运行维护:
Cron 会话与运行日志
隔离的 cron 运行也会创建会话条目/转录,并且它们有专门的保留控制:cron.sessionRetention(默认24h)会从会话存储中清理旧的隔离 cron 运行会话(false可禁用)。cron.runLog.maxBytes+cron.runLog.keepLines会清理~/.openclaw/cron/runs/<jobId>.jsonl文件(默认:2_000_000字节和2000行)。
cron:<jobId> 会话条目。它会保留安全的偏好设置,例如思考/快速/详细设置、标签,以及用户明确选择的模型/认证覆盖项。它会丢弃环境中的对话上下文,例如频道/群组路由、发送或排队策略、提权、来源以及 ACP
运行时绑定,这样一个新的隔离运行就不会从更早的运行中继承过时的传递或运行时权限。
Session keys(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>(除非被覆盖)
Session ids(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 记账)可能会修改 session 行,但不会延长 daily/idle 重置的新鲜度。重置滚动会在构建新的提示词之前丢弃前一个会话的排队系统事件通知。
- Parent fork policy 在创建 thread 或 subagent fork 时会使用 PI 的 active branch。如果该分支过大,OpenClaw 会以隔离上下文启动子进程,而不是失败或继承不可用的历史。大小策略是自动的;旧的
session.parentForkMaxTokens配置已被openclaw doctor --fix移除。
src/auto-reply/reply/session.ts 中的 initSessionState()。
Session store 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:上一次刷新运行时的压缩计数
Transcript structure(*.jsonl)
转录由 @mariozechner/pi-coding-agent 的 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:它是什么
压缩会把较早的对话总结为转录中的一个持久化compaction 条目,并保留最近的消息不变。
压缩后,后续回合会看到:
- 压缩摘要
firstKeptEntryId之后的消息
压缩块边界与工具配对
当 OpenClaw 将一段较长的转录拆分为压缩块时,它会保持 assistant 工具调用与其匹配的toolResult 条目成对。
- 如果按 token 占比分割的位置落在工具调用和其结果之间,OpenClaw 会将边界移到 assistant 的工具调用消息处,而不是把这对内容分开。
- 如果尾随的工具结果块本会把分块推过目标大小,OpenClaw 会保留该 待处理的工具块,并保持未总结的尾部完整。
- 被中止/出错的工具调用块不会使待处理分割继续保持开启。
自动压缩何时发生(Pi 运行时)
在嵌入式 Pi 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 形态变体) → 压缩 → 重试。 - 阈值维护:在一次成功的轮次后,当:
contextTokens > contextWindow - reserveTokens
其中:
contextWindow是模型的上下文窗口reserveTokens是为提示 + 下一次模型输出预留的余量
agents.defaults.compaction.maxActiveTranscriptBytes,并且活跃转录文件达到该大小时,OpenClaw 也可以在打开下一次运行之前触发一次预检的本地压缩。这是用于本地重开成本的文件大小保护,不是原始归档:OpenClaw 仍然执行正常的语义压缩,并且它要求 truncateAfterCompaction,这样压缩后的摘要才能成为新的后继转录。
对于嵌入式 Pi 运行,agents.defaults.compaction.midTurnPrecheck.enabled: true
会添加一个可选的工具循环保护。在追加工具结果之后、下一次模型调用之前,
OpenClaw 会使用与回合开始时相同的预检预算逻辑来估算提示压力。如果上下文不再适配,
该保护不会在 Pi 的 transformContext 钩子内进行压缩。它会发出结构化的
中途预检信号,停止当前提示提交,并让外层运行循环使用现有的恢复路径:
在这样做足够时截断过大的工具结果,或者触发配置的压缩模式并重试。该
选项默认禁用,并且适用于 default 和 safeguard 两种压缩模式,
包括基于 provider 的 safeguard 压缩。这与 maxActiveTranscriptBytes
无关:字节大小保护在一轮开始前运行,而中途预检则在嵌入式 Pi 工具
循环中、在新增工具结果已被追加之后稍后运行。
压缩设置(reserveTokens、keepRecentTokens)
Pi 的压缩设置位于 Pi 设置中:
- 如果
compaction.reserveTokens < reserveTokensFloor,OpenClaw 会将其提高。 - 默认下限为
20000tokens。 - 设置
agents.defaults.compaction.reserveTokensFloor: 0可禁用该下限。 - 如果它本来就更高,OpenClaw 会保持不变。
- 手动
/compact会尊重显式的agents.defaults.compaction.keepRecentTokens并保留 Pi 的最近尾部截断点。如果没有显式的保留预算, 手动压缩仍然是一个硬检查点,重建的上下文从新的摘要开始。 - 设置
agents.defaults.compaction.midTurnPrecheck.enabled: true可在 新的工具结果之后、下一次模型调用之前运行可选的工具循环预检。 这只是一个触发器;摘要生成仍然使用配置的压缩路径。它与maxActiveTranscriptBytes相互独立,后者是回合开始时的活跃转录字节大小保护。 - 设置
agents.defaults.compaction.maxActiveTranscriptBytes为字节值或 类似"20mb"的字符串,可在一轮开始前、活跃转录变大时运行本地压缩。 该保护仅在同时启用truncateAfterCompaction时生效。保留不设置,或设置为0可禁用。 - 当启用
agents.defaults.compaction.truncateAfterCompaction时, OpenClaw 会在压缩后将活跃转录轮换为压缩后的后继 JSONL。 旧的完整转录会保留为归档,并从压缩检查点链接,而不是就地重写。
src/agents/pi-settings.ts 中的 ensurePiCompactionReserveTokens()
(由 src/agents/pi-embedded-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/pi-hooks/compaction-safeguard.ts。
用户可见的表面
你可以通过以下方式观察压缩和会话状态:/status(在任意聊天会话中)openclaw status(CLI)openclaw sessions/sessions --json- 详细模式:
🧹 Auto-compaction complete+ 压缩次数
静默事务处理(NO_REPLY)
OpenClaw 支持用于后台任务的“静默”轮次,在这些任务中用户不应看到中间输出。
约定:
- assistant 以精确的静默 token
NO_REPLY/no_reply开始其输出,以表示“不要向用户发送回复”。 - OpenClaw 会在交付层中剥离/抑制这一内容。
- 精确静默 token 的抑制不区分大小写,因此当整个负载只是静默 token 时,
NO_REPLY和no_reply都算。 - 这仅用于真正的后台/不交付轮次;它不是普通可执行用户请求的快捷方式。
2026.1.10,OpenClaw 还会抑制 草稿/打字流式输出,当
一个部分块以 NO_REPLY 开头时,因此静默操作不会在轮次中途泄露部分
输出。
预压缩“内存刷新”(已实现)
目标:在自动压缩发生之前,运行一个静默的 agentic 轮次,将持久状态写入磁盘(例如 agent workspace 中的memory/YYYY-MM-DD.md),以便压缩不会擦除关键上下文。
OpenClaw 使用 预阈值刷新 方法:
- 监控会话上下文使用量。
- 当它超过一个“软阈值”(低于 Pi 的压缩阈值)时,向 agent 运行一个静默的 “立即写入内存”指令。
- 使用精确的静默 token
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中跟踪)。 - 刷新仅对嵌入式 Pi 会话运行(CLI 后端会跳过它)。
- 当会话 workspace 为只读(
workspaceAccess: "ro"或"none")时会跳过刷新。 - 有关 workspace 文件布局和写入模式,请参见 内存。
session_before_compact 钩子,但 OpenClaw 的
刷新逻辑目前位于 Gateway 侧。
故障排查清单
- 会话 key 错了?先从 /concepts/session 开始,并确认
/status中的sessionKey。 - 存储与转录不匹配?确认
openclaw status中的 Gateway 主机和存储路径。 - 压缩过于频繁?检查:
- 模型上下文窗口(太小)
- 压缩设置(
reserveTokens对模型窗口来说可能过高,导致更早压缩) - 工具结果膨胀:启用/调优会话剪枝
- 静默轮次泄露了内容?确认回复以
NO_REPLY开头(不区分大小写的精确 token),并且你使用的是包含流式抑制修复的构建版本。