"为什么我的对话突然'失忆'了"是 OpenClaw 用户常见的困惑之一,答案往往和 Session ID 的生命周期规则有关。本文基于官方文档梳理 sessionKey 与 sessionId 的区别,以及触发新会话的四种情形。
sessionKey vs sessionId:先分清两个概念
sessionKey —— 标识"你在哪个对话桶里"(路由+隔离)
sessionId —— 每个 sessionKey 指向的"当前会话"
(对应实际的 transcript 文件)
sessionKey 的常见命名模式:
主/直接对话(按 agent):agent:<agentId>:<mainKey>
(默认 mainKey 为 "main")
群组:agent:<agentId>:<channel>:group:<id>
房间/频道(Discord/Slack):
agent:<agentId>:<channel>:channel:<id>
或 ...:room:<id>
Cron 定时任务:cron:<job.id>
Webhook:hook:<uuid>(除非被覆盖)
理解这套命名规则的价值在于——排查"为什么两个看起来相关的对话没有共享上下文"这类问题时,可以直接对照sessionKey 的构成来判断它们是否真的被路由到了同一个会话桶。
触发新 sessionId 的四种情形
1. 手动重置(/new、/reset)
为该 sessionKey 创建一个全新的 sessionId
2. 每日重置(默认 Gateway 主机本地时间凌晨4点)
在重置边界之后收到的下一条消息会创建新 sessionId
3. 空闲过期(session.reset.idleMinutes,
旧版字段名 session.idleMinutes)
空闲窗口之后到达的消息会创建新 sessionId
如果同时配置了每日重置和空闲过期,
哪个先到期就按哪个生效
4. 线程父级分叉保护(session.parentForkMaxTokens,
默认 100000)
当父会话已经太大时,跳过父级 transcript 的分叉,
新线程直接从空白开始
设为 0 可以禁用这个保护机制
第四种情形容易被忽视,但在实际使用中很关键——如果你习惯在一个已经很长的会话里频繁开新线程/子话题,一旦父会话 Token 量超过阈值,新线程就不会继承父会话的上下文,而是从零开始。如果你依赖"新线程能看到主线程历史"这个假设,这条规则值得特别注意。
Session Store 里都记录了什么
sessionId 当前 transcript id
updatedAt 最后活动时间戳
sessionFile 可选的显式 transcript 路径覆盖
chatType direct | group | room
(帮助UI和发送策略判断)
provider/subject/room/space/displayName
群组/频道标注用的元数据
开关类字段:
thinkingLevel / verboseLevel / reasoningLevel / elevatedLevel
sendPolicy(会话级覆盖)
模型选择:
providerOverride / modelOverride / authProfileOverride
Token 计数器(尽力而为,取决于Provider):
inputTokens / outputTokens / totalTokens / contextTokens
compactionCount 该 sessionKey 累计完成过多少次自动压缩
memoryFlushAt 上一次"压缩前记忆刷写"的时间戳
memoryFlushCompactionCount
上次刷写时的压缩计数
这个 Store 是安全可编辑的,但Gateway 才是权威来源——它可能在会话运行过程中重写或重新填充这些条目,直接手动改这个文件要谨慎,改完之后 Gateway 仍会按照自己的逻辑管理这些字段。
Transcript 文件结构(JSONL)
文件首行:会话头(type: "session",包含 id、cwd、
timestamp,可选 parentSession)
之后每行:带 id + parentId 的会话条目(树状结构)
主要条目类型:
message 用户/助手/工具结果消息
custom_message 扩展注入的消息,会进入模型上下文
(但可以对UI隐藏)
custom 扩展状态,不进入模型上下文
compaction 持久化的压缩摘要,带 firstKeptEntryId 和
tokensBefore
branch_summary 在树分支间导航时的持久化摘要
值得注意的一点是——OpenClaw 刻意不会对 transcript 做"修复"处理;Gateway 通过 SessionManager 读写这些文件,保持原样。
Cron 会话的独立保留策略
孤立的 Cron 定时任务运行同样会创建会话条目和 transcript,
它们有专门的保留控制:
cron.sessionRetention(默认 24h)
修剪 Session Store 中过旧的孤立 Cron 运行会话
(设为 false 可禁用)
cron.runLog.maxBytes + cron.runLog.keepLines
修剪 ~/.openclaw/cron/runs/<jobId>.jsonl 文件
(默认 2,000,000 字节和 2000 行)
实战建议
1. 遇到"对话上下文丢失"类问题,先按四种触发情形逐一排查:
是手动重置了?跨过了每日重置边界?空闲过期了?
还是父会话太大触发了分叉保护?
2. 长期运行、经常需要保留完整上下文的主会话,
注意监控其 Token 量是否接近 parentForkMaxTokens 阈值,
避免影响后续开的子线程
3. 需要跨天保留同一个对话上下文的场景,
注意每日重置的默认时间点(凌晨4点,Gateway主机本地时区),
必要时调整或结合空闲过期规则统筹考虑
4. 大量使用 Cron 定时任务的部署,留意
cron.sessionRetention 和 runLog 相关配置,
避免会话记录无限累积占用磁盘
总结
Session ID 的生命周期由手动重置、每日重置、空闲过期、父级分叉保护这四种机制共同决定——理解这套规则,是排查"上下文为什么断了"这类问题的基础,也能帮你更合理地设计长期运行的多线程会话结构。
来源:Session Management & Compaction Deep Dive — OpenClaw 官方文档