一、核心认知:OpenClaw 眼中的“会话”是什么?
1. 主会话 & 会话键(session key)
OpenClaw 把“一个智能体的一个直接聊天窗口”视为它的主会话。 默认主会话键格式: agent:<agentId>:<mainKey> # mainKey 默认是 main- 直接私聊
默认都折叠进这个主会话键里;群组/频道聊天则各有各的键,互相隔离。
理解这一点很重要:“会话”本质上就是一个sessionKey → sessionId的映射,加上一堆元数据和对应的 JSONL 历史记录文件。
二、私信如何分组:session.dmScope
session.dmScope决定“同一个人从不同渠道/不同账号发来的消息,是一个会话还是多个会话”。
常用模式可以记成这张表:
main | agent:a1:main | |
per-peer | agent:a1:dm:<peerId> | |
per-channel-peer | agent:a1:<channel>:dm:<peerId> | |
per-account-channel-peer | agent:a1:<channel>:<accountId>:dm:<peerId> |
1. 搭配 identityLinks:跨渠道当成“同一个人”
session.identityLinks用来把不同渠道的 ID 映射到一个规范身份,例如:
"session.identityLinks":{"alice":["telegram:123456789","discord:987654321012345678"]}当 dmScope = per-peer/per-channel-peer/per-account-channel-peer时:如果入站的 peerId 是 telegram:123456789或discord:987654321012345678,会话键里的 <peerId>会统一替换成规范键alice,结果:Alice 在 Telegram + Discord 发私信,落在同一个私信会话上下文里。
实战建议:
做“同一个人跨渠道,一个大线程”:用 per-peer+identityLinks。做“共享客服收件箱”:用 per-channel-peer或per-account-channel-peer,方便分多渠道、多账户运营。
三、群聊 / 话题 会话键规则
群组/频道会话必须彼此隔离,否则上下文乱套。OpenClaw 使用这样的模式:
普通群组: agent:<agentId>:<channel>:group:<id>频道 / 房间: agent:<agentId>:<channel>:channel:<id>Telegram 论坛话题: agent:<agentId>:<channel>:group:<id>:topic:<threadId>
注意点:
老版本曾经只用 group:<id>,现在仍会识别做迁移,但会规范化为带channel的新格式。连接器可以从 Provider 的原始群/话题上下文推断出这些键,统一到 agent:<agentId>:<channel>:...形式。
实战经验:给单聊一个稳定主键(比如main),让群聊/话题都用各自独立键,避免把一群人的对话糊在一起。
四、会话状态到底存在哪?
所有会话状态的权威来源是Gateway 网关:
1. 在服务器上的文件结构
以某个 agentId 为例:
~/.openclaw/agents/<agentId>/sessions/ ├── sessions.json # sessionKey → { sessionId, updatedAt, origin... } ├── <SessionId>.jsonl # 对话记录(按行存 JSON) └── <SessionId>-topic-<threadId>.jsonl # Telegram 话题等特殊情况特点:
sessions.json是一个映射表,删掉某条记录是安全的,下次有消息会自动重建。 每个会话条目上会带 origin元数据:label(人类可读标签) provider(渠道) from/to(路由 ID) accountId threadId 等
2. 客户端不能自作主张
- UI 客户端不要自己解析 JSONL 去算 token 数
,而应直接读 Gateway 提供的: inputTokens/ outputTokens/totalTokens/contextTokens远程部署时,状态都在远程 Gateway 上,本地只相当于纯前端壳。
五、会话生命周期:何时复用?何时新开?
OpenClaw 会话的生命周期逻辑主要靠时间策略 + 命令触发共同决定。
1. 时间策略:daily / idle
1)每日重置(默认)
默认在 Gateway 主机本地时间凌晨 4:00。 判定:如果会话的 updatedAt早于最近一次 4:00 刻度,则视为已过期,下一条入站消息会启用一个新的sessionId(键相同,id 新)。
2)空闲重置(idleMinutes)
增加一个「滑动空闲窗口」: 当当前时间 - 最后更新时间 > idleMinutes,则视为过期。 常见配置形态: "session.reset":{"mode":"daily","atHour":4,"idleMinutes":120}- 每日 + 空闲同时存在时
:谁先命中谁生效,即“谁先到期谁先重置”。
3)按类型/按渠道覆盖
可以局部覆盖session.reset:
session.resetByType对 dm/group/thread单独设置策略。thread 包括:Slack/Discord 线程、Telegram 话题、Matrix 线程等。 session.resetByChannel针对某个渠道(如 Discord)的全部会话类型统一覆写策略。 优先级: resetByChannel>resetByType>reset。
4)旧版仅空闲模式兼容
如果只设置了 session.idleMinutes,没有session.reset/session.resetByType:将保持旧式“仅空闲模式”,以向后兼容。
2. 命令触发:/new /reset
用户可以用命令手动新开会话:
/new或 /reset:立即开启新 sessionId,把这条消息的剩余文本当作新会话的首条输入。 /new <model>: 同时切换模型,例如使用模型别名、 provider/model、或仅 provider 名称。如果单独发送 /new或/reset(后面没内容):OpenClaw 会跑一轮“问候”来确认重置成功。
3. 特殊:定时任务(cron)
定时任务会话每次运行都新建 sessionId,从不复用。 键形式一般为: cron:<job.id>
六、上下文长度 & 压缩:如何避免“聊长了就失忆”
1. 会话修剪(不改历史,改“当前上下文”)
在每次 LLM 调用前,会对内存中的上下文做修剪: 重点是删除旧的工具调用结果等“噪声内容”,减小 context。 但不会去改 JSONL 文件,历史仍然完整保留。
2. 自动压缩前的“记忆刷新”
当会话即将靠近自动压缩阈值时,可以启用:
- 静默记忆刷新轮次
: 不对用户“发言”,只是偷偷提示模型把“长期有用的信息”写入持久化记忆。 需要工作区目录可写。 这一步和“记忆系统 / context 压缩机制”配合,可以理解成: “把旧聊天里真正重要的三五条信息抄一份到记忆本,然后对旧聊天做压缩/舍弃。”
七、发送策略:不仅“如何收”,还可以“决定不发”
OpenClaw 可以通过发送策略阻止某类会话类型的发送,而不用一个个列 ID:
示例(简化):
{"rules":[{"action":"deny","match":{"channel":"discord","chatType":"group"}},{"action":"deny","match":{"keyPrefix":"cron:"}}],"default":"allow"}含义: 禁止 Discord 群组类消息的发送(只收不发)。 禁止所有定时任务会话的主动发送。 其他情况默认允许。
运行时覆盖(只对当前会话)
所有者可以在会话里发:
/send on:本会话允许发送 /send off:本会话拒绝发送 /send inherit:取消覆盖,回到配置策略
注意:这些命令需要作为独立消息发送才生效。
八、如何配置
在~/.openclaw/openclaw.json中,你一般会看到类似配置:
{"session.scope":"per-sender","session.dmScope":"main","session.identityLinks":{"alice":["telegram:123456789","discord:987654321012345678"]},"session.reset":{"mode":"daily","atHour":4,"idleMinutes":120},"session.resetByType":{"thread":{"mode":"daily","atHour":4},"dm":{"mode":"idle","idleMinutes":240},"group":{"mode":"idle","idleMinutes":120}},"session.resetByChannel":{"discord":{"mode":"idle","idleMinutes":10080}},"session.resetTriggers":["/new","/reset"],"session.store":"~/.openclaw/agents/{agentId}/sessions/sessions.json","session.mainKey":"main"}实务建议:
个人“贾维斯”型助手: dmScope = main,主会话保持连续;群聊各自独立。 客服 / 收件箱型: dmScope = per-channel-peer或 per-account-channel-peer,再配合identityLinks做多渠道合并。长期项目协作: 提高 dm空闲时间窗口(如 240 min),但仍保留每日重置,避免对话上下文无限拉长。
九、运维与调试
1. 命令行工具
# 查看当前 Agent 状态(会话存储路径、最近会话等)openclaw status# 列出会话(导出 JSON)openclaw sessions --jsonopenclaw sessions --json--active60# 只看 60 分钟内活跃的远程 Gateway:
openclaw gateway call sessions.list --params'{}'# 可配合 --url/--token 指向远程 Gateway2. 聊天内调试命令
/status:看 Agent 是否在线可用 当前会话的上下文占用 思考/详细模式开关 一些通道凭证状态(如 WhatsApp Web) /context list/ /context detail:看系统提示、注入的工作区文件 看哪些部分占了最多上下文容量 /stop: 中止当前会话正在进行的运行 清理排队任务 & 子智能体运行 /compact: 对旧上下文做总结压缩,释放窗口
3. 直接看 JSONL
如果你要排查“为什么模型会得出这个结论”,最原始、最可靠的方式就是: 打开 ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl,一行一条记录,把每轮对话 + 工具调用全看一遍。
十、最佳实践总结
主键只给 1:1 私聊
把 session.mainKey = "main"保留给你和 Agent 的一对一长对话;群聊、话题、定时任务一律走专属键,避免互相污染。 配置好 dmScope + identityLinks
用 per-channel-peer/per-account-channel-peer。用 per-peer+ 维护好identityLinks。想让“同一个人跨渠道仍是一个会话”: 想做运营收件箱或客服中心: 重置策略:日常用法推荐
把全局 session.reset设为每日 + 适度 idleMinutes;对 thread、group可以单独设更短的 idle,防止群聊无限拉长上下文;对一些“长周期项目 DM”可以把 idleMinutes 适当拉长。 清理会话时:删“键”,别删“整个 store”
只删除 sessions.json里个别 sessionKey 对应条目或单个 JSONL 文件;这样不会影响别的会话上下文。 快溢出前先“记忆刷新 + 压缩”
打开“压缩前记忆刷新”(如果已有记忆系统); 定期使用 /compact或相关压缩机制,避免对话超过模型窗口。用发送策略做好“会说不做”与“能做能说”的区分
用发送策略 deny对应的channel+chatType。不希望 Agent 在某些渠道/会话类型主动回消息(比如只做监听):
夜雨聆风