会话管理

OpenClaw 为每个 agent 维护一个主直聊会话。直聊会收拢到 agent:<agentId>:<mainKey>(默认 main),群聊/频道聊天则各有独立的 key。session.mainKey 配置会被尊重。

通过 session.dmScope 控制私信的分组方式:

  • main(默认):所有私信共享主会话,保持对话连贯。
  • per-peer:按发送者 ID 跨渠道隔离。
  • per-channel-peer:按渠道 + 发送者隔离(推荐多用户收件箱场景)。
  • per-account-channel-peer:按账号 + 渠道 + 发送者隔离(推荐多账号收件箱场景)。 使用 session.identityLinks 可以把带有服务商前缀的 peer ID 映射到统一身份,这样在 per-peerper-channel-peerper-account-channel-peer 模式下,同一个人跨渠道也能共享同一个私信会话。

安全私信模式(多用户场景建议开启)

安全警告: 如果你的 agent 会收到多人的私信,强烈建议启用安全私信模式。不启用的话,所有用户共享同一个对话上下文,可能导致用户之间的信息泄露。

默认设置下的问题示例:

  • Alice(<SENDER_A>)给你的 agent 发了一条涉及隐私的消息(比如一个医疗预约)
  • Bob(<SENDER_B>)问你的 agent:“我们刚才聊的是什么?”
  • 因为两人的私信共享同一个会话,模型可能会用 Alice 的上下文来回答 Bob

解决方案: 设置 dmScope 来隔离每个用户的会话:

// ~/.openclaw/openclaw.json
{
  session: {
    // 安全私信模式:按渠道 + 发送者隔离私信上下文。
    dmScope: "per-channel-peer",
  },
}

何时需要启用:

  • 你对多个发送者做了配对审批
  • 你的 DM 允许列表有多条记录
  • 你设置了 dmPolicy: "open"
  • 多个手机号或账号可以给你的 agent 发消息

注意事项:

  • 默认是 dmScope: "main" 以保持连贯性(所有私信共享主会话)。单用户场景下没问题。
  • 本地 CLI 引导流程在未设置时默认写入 session.dmScope: "per-channel-peer"(已有显式值会被保留)。
  • 同一渠道的多账号收件箱,建议用 per-account-channel-peer
  • 如果同一个人通过不同渠道联系你,可以用 session.identityLinks 把他们的私信会话归并到一个统一身份。
  • 你可以通过 openclaw security audit 验证当前的私信设置(参见 security)。

Gateway 是权威数据源

所有会话状态都由 Gateway 持有(即”主” OpenClaw)。UI 客户端(macOS 应用、WebChat 等)必须向 Gateway 查询会话列表和 token 统计,而不是读取本地文件。

  • 远程模式下,你关心的会话存储在远程 Gateway 主机上,不在你的 Mac 上。
  • UI 中显示的 token 数来自 Gateway 存储字段(inputTokensoutputTokenstotalTokenscontextTokens)。客户端不会通过解析 JSONL 对话记录来”修正”总数。

状态存放位置

  • Gateway 主机上:
    • 存储文件:~/.openclaw/agents/<agentId>/sessions/sessions.json(按 agent)。
  • 对话记录:~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl(Telegram topic 会话使用 .../<SessionId>-topic-<threadId>.jsonl)。
  • 存储是一个 sessionKey -> { sessionId, updatedAt, ... } 的映射。删除条目是安全的,下次消息到来时会重建。
  • 群组条目可能包含 displayNamechannelsubjectroomspace,用于在 UI 中标注会话。
  • 会话条目包含 origin 元数据(标签 + 路由提示),帮助 UI 展示会话来源。
  • OpenClaw 不会读取旧版 Pi/Tau 会话目录。

维护

OpenClaw 对会话存储执行自动维护,控制 sessions.json 和对话记录的增长。

默认值

  • session.maintenance.mode: warn
  • session.maintenance.pruneAfter: 30d
  • session.maintenance.maxEntries: 500
  • session.maintenance.rotateBytes: 10mb
  • session.maintenance.resetArchiveRetention: 默认同 pruneAfter30d
  • session.maintenance.maxDiskBytes: 未设置(禁用)
  • session.maintenance.highWaterBytes: 启用磁盘预算时默认为 maxDiskBytes80%

工作原理

维护在会话存储写入时执行,也可以通过 openclaw sessions cleanup 手动触发。

  • mode: "warn":报告哪些内容会被清理,但不实际修改条目或对话记录。
  • mode: "enforce":按以下顺序执行清理:
    1. 清除超过 pruneAfter 的过期条目
    2. 将条目数限制在 maxEntries 以内(最旧的优先)
    3. 将已移除条目的对话记录文件归档(如果不再被引用)
    4. 按保留策略清除旧的 *.deleted.<timestamp>*.reset.<timestamp> 归档
    5. sessions.json 超过 rotateBytes 时进行轮转
    6. 如果设置了 maxDiskBytes,按磁盘预算清理到 highWaterBytes(最旧的文件优先,然后是最旧的会话)

大型存储的性能提示

高流量场景下,会话存储可能会变得很大。维护是写入路径上的操作,因此特别大的存储可能增加写入延迟。

什么因素影响最大:

  • session.maintenance.maxEntries 值设得太高
  • pruneAfter 窗口太长,保留了大量过期条目
  • ~/.openclaw/agents/<agentId>/sessions/ 中积累了大量对话记录/归档文件
  • 启用了磁盘预算(maxDiskBytes)但没有合理的清理/上限设置

建议做法:

  • 生产环境使用 mode: "enforce" 让增长受控
  • 同时设置时间和数量限制(pruneAfter + maxEntries),不要只设一个
  • 大规模部署设置 maxDiskBytes + highWaterBytes 作为硬上限
  • highWaterBytes 应明显低于 maxDiskBytes(默认是 80%)
  • 修改配置后运行 openclaw sessions cleanup --dry-run --json 验证预期效果
  • 对于频繁活跃的会话,手动清理时传入 --active-key

自定义示例

保守的强制策略:

{
  session: {
    maintenance: {
      mode: "enforce",
      pruneAfter: "45d",
      maxEntries: 800,
      rotateBytes: "20mb",
      resetArchiveRetention: "14d",
    },
  },
}

为会话目录启用磁盘硬限制:

{
  session: {
    maintenance: {
      mode: "enforce",
      maxDiskBytes: "1gb",
      highWaterBytes: "800mb",
    },
  },
}

针对更大规模部署的调优示例:

{
  session: {
    maintenance: {
      mode: "enforce",
      pruneAfter: "14d",
      maxEntries: 2000,
      rotateBytes: "25mb",
      maxDiskBytes: "2gb",
      highWaterBytes: "1.6gb",
    },
  },
}

从 CLI 预览或强制执行维护:

openclaw sessions cleanup --dry-run
openclaw sessions cleanup --enforce

会话修剪

OpenClaw 默认会在 LLM 调用前裁剪较早的工具结果,从内存上下文中移除。 这不会改写 JSONL 历史。详见 /concepts/session-pruning

压缩前的内存刷新

当会话接近自动压缩时,OpenClaw 可以执行一次静默的内存刷新轮次,提醒模型把重要笔记写入磁盘。这仅在工作空间可写时执行。参见 MemoryCompaction

传输层 → 会话 key 的映射

  • 直聊遵循 session.dmScope(默认 main)。
    • mainagent:<agentId>:<mainKey>(跨设备/渠道保持连贯)。
      • 多个手机号和渠道可以映射到同一个 agent main key,它们只是同一个对话的不同传输通道。
    • per-peeragent:<agentId>:direct:<peerId>
    • per-channel-peeragent:<agentId>:<channel>:direct:<peerId>
    • per-account-channel-peeragent:<agentId>:<channel>:<accountId>:direct:<peerId>(accountId 默认为 default)。
    • 如果 session.identityLinks 匹配到带服务商前缀的 peer ID(比如 telegram:123),统一 key 会替换 <peerId>,使同一个人跨渠道共享会话。
  • 群聊隔离状态:agent:<agentId>:<channel>:group:<id>(频道/房间使用 agent:<agentId>:<channel>:channel:<id>)。
    • Telegram 论坛话题在群组 ID 后追加 :topic:<threadId> 来隔离。
    • 旧版 group:<id> key 仍兼容迁移。
  • 入站上下文可能仍使用 group:<id>;渠道信息从 Provider 推断,并标准化为 agent:<agentId>:<channel>:group:<id> 格式。
  • 其他来源:
    • 定时任务:cron:<job.id>
    • Webhook:hook:<uuid>(除非 hook 显式指定)
    • 节点运行:node-<nodeId>

生命周期

  • 重置策略:会话持续复用直到过期,过期判定在下一条入站消息到达时进行。
  • 每日重置:默认 Gateway 主机本地时间凌晨 4:00。如果会话最后更新时间早于最近一次每日重置时间点,则视为过期。
  • 空闲重置(可选):idleMinutes 添加一个滑动的空闲窗口。同时配置每日和空闲重置时,先到期的那个触发新会话。
  • 旧版纯空闲模式:如果你设置了 session.idleMinutes 但没有配置 session.reset/resetByType,OpenClaw 保持纯空闲模式以向后兼容。
  • 按类型覆盖(可选):resetByType 可以为 directgroupthread 会话分别设置重置策略(thread = Slack/Discord 线程、Telegram topic、连接器提供的 Matrix 线程)。
  • 按渠道覆盖(可选):resetByChannel 为某个渠道覆盖重置策略(适用于该渠道的所有会话类型,优先级高于 reset/resetByType)。
  • 重置触发词:精确匹配 /new/reset(以及 resetTriggers 中的额外配置)会创建新的 session ID,消息剩余部分会继续传递。/new <model> 接受模型别名、provider/model 或服务商名称(模糊匹配)来设置新会话的模型。单独发送 /new/reset 时,OpenClaw 会运行一轮简短的问候来确认重置。
  • 手动重置:从存储中删除特定 key 或删除 JSONL 对话记录;下条消息会重新创建它们。
  • 隔离的定时任务每次运行都会生成新的 sessionId(不复用空闲会话)。

发送策略(可选)

按会话类型阻止投递,无需列出具体 ID。

{
  session: {
    sendPolicy: {
      rules: [
        { action: "deny", match: { channel: "discord", chatType: "group" } },
        { action: "deny", match: { keyPrefix: "cron:" } },
        // 匹配原始会话 key(包含 `agent:<id>:` 前缀)。
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ],
      default: "allow",
    },
  },
}

运行时覆盖(仅所有者):

  • /send on → 允许当前会话发送
  • /send off → 拒绝当前会话发送
  • /send inherit → 清除覆盖,使用配置规则 作为独立消息发送以使其生效。

配置(可选的重命名示例)

// ~/.openclaw/openclaw.json
{
  session: {
    scope: "per-sender", // 群组 key 保持独立
    dmScope: "main", // 私信连贯性(共享收件箱用 per-channel-peer/per-account-channel-peer)
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"],
    },
    reset: {
      // 默认:mode=daily, atHour=4(Gateway 主机本地时间)。
      // 如果同时设了 idleMinutes,先到期的那个生效。
      mode: "daily",
      atHour: 4,
      idleMinutes: 120,
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      direct: { mode: "idle", idleMinutes: 240 },
      group: { mode: "idle", idleMinutes: 120 },
    },
    resetByChannel: {
      discord: { mode: "idle", idleMinutes: 10080 },
    },
    resetTriggers: ["/new", "/reset"],
    store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
    mainKey: "main",
  },
}

查看状态

  • openclaw status — 显示存储路径和最近的会话。
  • openclaw sessions --json — 输出所有条目(可用 --active <minutes> 过滤)。
  • openclaw gateway call sessions.list --params '{}' — 从运行中的 Gateway 获取会话(远程访问使用 --url/--token)。
  • 在聊天中发送 /status 作为独立消息,可以查看 agent 是否可达、会话上下文使用情况、当前思考/快速/详细切换状态,以及 WhatsApp web 凭据的上次刷新时间(便于发现需要重新链接的情况)。
  • 发送 /context list/context detail 查看系统 prompt 和注入的工作空间文件(以及最大的上下文贡献者)。
  • 发送 /stop(或独立的中止短语如 stopstop actionstop runstop openclaw)中止当前运行、清除该会话的排队 followup,并停止由它创建的所有子 agent 运行(回复中包含已停止的数量)。
  • 发送 /compact(可选指令)作为独立消息,总结较早的上下文并释放窗口空间。参见 /concepts/compaction
  • JSONL 对话记录可以直接打开查看完整的对话轮次。

使用建议

  • 把主 key 专门用于 1:1 对话;群聊让它们保持各自的 key。
  • 自动化清理时,删除单个 key 而不是整个存储,以保留其他上下文。

会话来源元数据

每个会话条目在 origin 中记录了它的来源(尽力而为):

  • label:可读标签(由对话标签 + 群组主题/频道生成)
  • provider:标准化的渠道 ID(含扩展)
  • from/to:入站信封中的原始路由 ID
  • accountId:服务商账号 ID(多账号场景)
  • threadId:线程/话题 ID(渠道支持时提供) 来源字段对私信、频道和群组都会填充。如果连接器只更新投递路由(比如为了保持 DM 主会话的活跃),它仍应提供入站上下文以保留会话的解释性元数据。扩展可以通过在入站上下文中发送 ConversationLabelGroupSubjectGroupChannelGroupSpaceSenderName,并调用 recordSessionMetaFromInbound(或将相同上下文传给 updateLastRoute)来实现。