Skip to content

Session 管理(Session Management)

Moltbot 将 每个 agent 的一条私聊(direct-chat)主会话视为主路径。私聊默认会折叠为 agent:<agentId>:<mainKey>(默认 main),而群聊/频道聊天会拥有各自独立的 keys。session.mainKey 会被遵循。

使用 session.dmScope 控制**私信(direct messages)**如何分组:

  • main(默认):所有私信共享主 session,以保证连续性。
  • per-peer:按发送者 id 在跨渠道范围内隔离。
  • per-channel-peer:按 channel + sender 隔离(推荐用于多用户收件箱)。
  • per-account-channel-peer:按 account + channel + sender 隔离(推荐用于多账号收件箱)。

使用 session.identityLinks 将带 provider 前缀的 peer id 映射到一个规范化身份,这样在 per-peerper-channel-peerper-account-channel-peer 下,同一个人可以跨渠道共享同一条 DM session。

Gateway 是唯一事实来源

所有 session 状态都 由 gateway 持有(“主” Moltbot)。UI clients(macOS app、WebChat 等)必须向 gateway 查询 session 列表与 token 计数,而不是读取本地文件。

  • remote mode 下,你关心的 session store 位于远端 gateway host 上,而不是你的 Mac。
  • UI 中展示的 token 计数来自 gateway store 的字段(inputTokensoutputTokenstotalTokenscontextTokens)。clients 不会解析 JSONL transcripts 去“修正”总计。

状态存放位置

  • gateway host 上:
    • Store 文件:~/.openclaw/agents/<agentId>/sessions/sessions.json(每个 agent 一份)。
  • Transcripts:~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl(Telegram topic sessions 使用 .../<SessionId>-topic-<threadId>.jsonl)。
  • Store 是一个 sessionKey -> { sessionId, updatedAt, ... } 的 map。删除条目是安全的;需要时会按需重建。
  • Group 条目可能包含 displayNamechannelsubjectroomspace,用于在 UI 中标记 sessions。
  • Session 条目包含 origin 元数据(label + 路由提示),便于 UI 解释 session 的来源。
  • Moltbot 不会读取旧版 Pi/Tau 的 session 目录。

Session pruning

Moltbot 默认会在每次 LLM 调用之前,针对内存上下文裁剪(trim)旧的 tool results。 这不会重写 JSONL 历史。参见 /concepts/session-pruning

Compaction 前的 memory flush

当 session 接近自动 compaction 时,Moltbot 可以运行一次静默的 memory flush回合,提醒模型把可持久化的笔记写入磁盘。该步骤仅在 workspace 可写时运行。参见 MemoryCompaction

Transport → session keys 映射

  • 私聊遵循 session.dmScope(默认 main)。
    • mainagent:<agentId>:<mainKey>(跨设备/渠道保持连续性)。
      • 多个电话号码与渠道可以映射到同一个 agent main key;它们相当于同一对话的多条“传输通道”。
    • per-peeragent:<agentId>:dm:<peerId>
    • per-channel-peeragent:<agentId>:<channel>:dm:<peerId>
    • per-account-channel-peeragent:<agentId>:<channel>:<accountId>:dm:<peerId>accountId 默认 default)。
    • 如果 session.identityLinks 命中某个带 provider 前缀的 peer id(例如 telegram:123),则会用规范化 key 替换 <peerId>,从而让同一个人跨渠道共享 session。
  • 群聊会隔离状态:agent:<agentId>:<channel>:group:<id>(rooms/channels 使用 agent:<agentId>:<channel>:channel:<id>)。
    • Telegram forum topics 会在 group id 末尾追加 :topic:<threadId> 用于隔离。
    • 旧版 group:<id> keys 仍会被识别以支持迁移。
  • 入站上下文仍可能使用 group:<id>;channel 会从 Provider 推断并规范化为 agent:<agentId>:<channel>:group:<id>
  • 其他来源:
    • Cron jobs:cron:<job.id>
    • Webhooks:hook:<uuid>(除非 hook 显式设置)
    • Node runs:node-<nodeId>

生命周期(Lifecycle)

  • 重置策略:session 会一直复用直到过期;是否过期会在下一条入站消息到来时评估。
  • 每日重置:默认是 gateway host 本地时间的凌晨 4:00。当 session 上次更新时间早于最近一次“每日重置时间点”时,该 session 视为 stale。
  • 空闲重置(可选):idleMinutes 提供一个滑动空闲窗口。当同时配置了 daily 与 idle 时,先过期的那条规则会强制创建新 session。
  • 仅空闲(legacy):如果你只设置了 session.idleMinutes 而没有任何 session.reset/resetByType 配置,Moltbot 会保持 idle-only 模式以兼容旧行为。
  • 按类型覆盖(可选):resetByType 允许你分别为 dmgroupthread sessions 覆盖策略(thread = Slack/Discord threads、Telegram topics、Matrix threads(当 connector 提供时))。
  • 按渠道覆盖(可选):resetByChannel 覆盖某个 channel 的重置策略(对该 channel 的全部 session types 生效,并优先于 reset/resetByType)。
  • 重置触发:精确匹配的 /new/reset(以及 resetTriggers 中额外的触发词)会创建新的 session id,并将消息余下部分继续传入。/new <model> 支持 model alias、provider/model 或 provider 名称(模糊匹配)来设置新 session 的模型。如果 /new/reset 单独发送(没有附加文本),Moltbot 会运行一段简短的 “hello” 问候回合以确认重置。
  • 手动重置:从 store 中删除指定 keys,或移除 JSONL transcript;下一条消息会重建它们。
  • 隔离的 cron jobs 总是每次 run 生成新的 sessionId(不复用 idle session)。

发送策略(Send policy,可选)

无需列举具体 id,也可以按 session 类型阻止投递。

json5
{
  session: {
    sendPolicy: {
      rules: [
        { action: "deny", match: { channel: "discord", chatType: "group" } },
        { action: "deny", match: { keyPrefix: "cron:" } }
      ],
      default: "allow"
    }
  }
}

运行时覆盖(仅 owner):

  • /send on → 对当前 session 允许发送
  • /send off → 对当前 session 禁止发送
  • /send inherit → 清除覆盖并回退到配置规则

请把它们作为独立消息发送,以便正确注册。

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

json5
// ~/.openclaw/moltbot.json
{
  session: {
    scope: "per-sender",      // keep group keys separate
    dmScope: "main",          // DM continuity (set per-channel-peer/per-account-channel-peer for shared inboxes)
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"]
    },
    reset: {
      // Defaults: mode=daily, atHour=4 (gateway host local time).
      // If you also set idleMinutes, whichever expires first wins.
      mode: "daily",
      atHour: 4,
      idleMinutes: 120
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      dm: { 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",
  }
}

如何检查(Inspecting)

  • moltbot status — 显示 store 路径与最近 sessions。
  • moltbot sessions --json — 输出所有条目(可用 --active <minutes> 过滤)。
  • moltbot gateway call sessions.list --params '{}' — 从运行中的 gateway 拉取 sessions(remote gateway 时使用 --url/--token)。
  • 在聊天中单独发送 /status,查看 agent 是否可达、session context 使用量、当前 thinking/verbose 开关,以及 WhatsApp web 凭据最近刷新时间(便于发现需要重新绑定)。
  • 发送 /context list/context detail 查看 system prompt 与注入的 workspace files(以及最大的 context 贡献者)。
  • 单独发送 /stop 中止当前 run,清除该 session 的排队 followups,并停止由它派生的所有子 agent runs(回复会包含停止数量)。
  • 单独发送 /compact(可附加说明)以总结旧上下文并释放窗口空间。参见 /concepts/compaction
  • 可以直接打开 JSONL transcripts 来审阅完整回合。

小贴士

  • 保持主 key 专用于 1:1;让群聊保留自己的 keys。
  • 做自动化清理时,优先删除单个 key,而不是整个 store,以保留其他对话上下文。

Session origin 元数据

每个 session 条目会尽力在 origin 中记录来源信息:

  • label:给人看的标签(由 conversation label + group subject/channel 解析)
  • provider:规范化后的 channel id(含扩展)
  • from/to:入站 envelope 中的原始路由 ids
  • accountId:provider account id(多账号时)
  • threadId:渠道支持 threads/topics 时的 thread/topic id

这些 origin 字段会为私信、channels 与 groups 填充。如果某个 connector 只更新了投递路由(例如,为了让 DM main session 保持活跃),它仍应提供入站上下文,以便 session 保留其“解释性”元数据。扩展可以在入站上下文中发送 ConversationLabelGroupSubjectGroupChannelGroupSpaceSenderName,并调用 recordSessionMetaFromInbound(或把相同的上下文传给 updateLastRoute)来做到这一点。