乐于分享
好东西不私藏

OpenClaw 会话管理解读

OpenClaw 会话管理解读

一、核心认知:OpenClaw 眼中的“会话”是什么?

1. 主会话 & 会话键(session key)

  • OpenClaw 把“一个智能体的一个直接聊天窗口”视为它的主会话
  • 默认主会话键格式:
    agent:<agentId>:<mainKey>   # mainKey 默认是 main
  • 直接私聊
    默认都折叠进这个主会话键里;群组/频道聊天则各有各的键,互相隔离。

理解这一点很重要:“会话”本质上就是一个sessionKey → sessionId的映射,加上一堆元数据和对应的 JSONL 历史记录文件。


二、私信如何分组:session.dmScope

session.dmScope决定“同一个人从不同渠道/不同账号发来的消息,是一个会话还是多个会话”

常用模式可以记成这张表:

dmScope
会话键模式示例
适用场景
main
(默认)
agent:a1:main
个人助手,想要“一个长对话”
per-peeragent:a1:dm:<peerId>
按人隔离,跨渠道合并
per-channel-peeragent:a1:<channel>:dm:<peerId>
多用户收件箱(推荐)
per-account-channel-peeragent: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:123456789discord:987654321012345678
    • 会话键里的<peerId>会统一替换成规范键alice
    • 结果:Alice 在 Telegram + Discord 发私信,落在同一个私信会话上下文里。

实战建议:

  • 做“同一个人跨渠道,一个大线程”:用per-peer+identityLinks
  • 做“共享客服收件箱”:用per-channel-peerper-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 指向远程 Gateway

2. 聊天内调试命令

  • /status
    :看
    • Agent 是否在线可用
    • 当前会话的上下文占用
    • 思考/详细模式开关
    • 一些通道凭证状态(如 WhatsApp Web)
  • /context list
    //context detail
    • 看系统提示、注入的工作区文件
    • 看哪些部分占了最多上下文容量
  • /stop
    • 中止当前会话正在进行的运行
    • 清理排队任务 & 子智能体运行
  • /compact
    • 对旧上下文做总结压缩,释放窗口

3. 直接看 JSONL

  • 如果你要排查“为什么模型会得出这个结论”,最原始、最可靠的方式就是:
    • 打开~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
    • 一行一条记录,把每轮对话 + 工具调用全看一遍。

十、最佳实践总结

  1. 主键只给 1:1 私聊

    • session.mainKey = "main"保留给你和 Agent 的一对一长对话;
    • 群聊、话题、定时任务一律走专属键,避免互相污染。
  2. 配置好 dmScope + identityLinks

    • per-channel-peer/per-account-channel-peer
    • per-peer+ 维护好identityLinks
    • 想让“同一个人跨渠道仍是一个会话”:
    • 想做运营收件箱或客服中心:
  3. 重置策略:日常用法推荐

    • 把全局session.reset设为每日 + 适度 idleMinutes;
    • threadgroup可以单独设更短的 idle,防止群聊无限拉长上下文;
    • 对一些“长周期项目 DM”可以把 idleMinutes 适当拉长。
  4. 清理会话时:删“键”,别删“整个 store”

    • 只删除sessions.json里个别 sessionKey 对应条目或单个 JSONL 文件;
    • 这样不会影响别的会话上下文。
  5. 快溢出前先“记忆刷新 + 压缩”

    • 打开“压缩前记忆刷新”(如果已有记忆系统);
    • 定期使用/compact或相关压缩机制,避免对话超过模型窗口。
  6. 用发送策略做好“会说不做”与“能做能说”的区分

    • 用发送策略deny对应的channel+chatType
    • 不希望 Agent 在某些渠道/会话类型主动回消息(比如只做监听):