跳转至

命令队列

OpenClaw 通过一个小型进程内队列串行化入站自动回复运行(所有渠道),以防止多个代理运行发生冲突,同时仍允许跨会话安全并行。

原因

  • 自动回复运行可能开销较大(LLM 调用),并且当多条入站消息几乎同时到达时可能发生冲突。
  • 串行化可避免争用共享资源(会话状态、日志、CLI stdin),并降低触发上游速率限制的可能性。

工作原理

  • 一个感知泳道的 FIFO 队列以可配置的并发上限排空每个泳道(未配置泳道的默认值为 1;main 使用 max(8, available CPU parallelism * 4),普通子代理队列默认每个生成会话为 8,Swarm 收集器队列默认每个组为 32)。
  • CLI、嵌入式和 Codex 运行共享同一个会话键泳道(session:<key>)。每一轮次都会先在那里等待,然后才获取会话的执行声明,因此切换运行时无法启动相互竞争的轮次。
  • 入站会话运行随后进入全局 main 泳道,其并行度受 agents.defaults.maxConcurrent 限制。普通子代理运行则使用其直接生成/控制器会话的预算,由 agents.defaults.subagents.maxConcurrent 设置。Swarm 收集器子项使用其组单独的预算,由 tools.swarm.maxConcurrent 设置。
  • 嵌入式尝试准备在 16 个阶段启动或每个切片至少 8 ms 的同步分派工作后让出事件循环,因此并发启动会为 Gateway 请求留出空间。正在运行的阶段不会被抢占。异步阶段工作仍可能重叠,并且不计入该时间预算;这不会降低运行并发限制,也不会改变会话串行化。
  • 启用详细日志时,如果排队运行在启动前等待超过约 2 秒,会发出简短通知。
  • 输入指示器仍会在入队时立即触发(如果渠道支持),因此在运行等待轮次期间用户体验保持不变。

默认值

未设置时,所有入站渠道界面使用:

  • mode: "steer"
  • 内置的 500ms 防抖,用于 steer、followup 和 collect 批处理
  • cap: 20
  • drop: "summarize"

同轮转向是默认行为。运行中途到达的提示词会在运行可接受转向时注入到活动运行时,因此不会启动第二个会话运行。如果活动运行无法接受转向,OpenClaw 会等待活动运行结束后再启动该提示词。

队列模式

/queue 控制当会话已有活动运行时,普通入站消息如何处理:

  • steer:将消息注入活动运行时,包括其正在执行工具时。OpenClaw 会让已在运行的工具完成,跳过尚未开始的顺序调用,并在下一次工具启动或模型决策之前使转向可见。一旦其批次越过启动检查点,并行调用将继续。Codex app-server 接收一个批处理的 turn/steer,并在下一个模型边界处应用它。如果转向不可用,OpenClaw 会等待活动运行结束后再启动提示词。
  • followup:不转向。将每条消息入队,以便在当前运行结束后进行后续的代理轮次。
  • collect:不转向。在静默窗口后将排队消息合并为单个后续轮次。如果消息指向不同渠道/线程,则分别排空以保留路由。
  • interrupt:中止该会话的活动运行,然后运行最新消息。

有关运行时特定的时序和依赖行为,请参阅 转向队列。有关显式 /steer <message> 命令,请参阅 转向。

Gateway 输入在排队或委托给子项时,会保留其已认证操作员和原始范围上限。收集消息或转向活动运行 需要兼容的操作员来源和工具权限;其他输入按 FIFO 顺序等待,而不是借用活动发送者或最新发送者的权限。

已接受的轮次可以在其请求返回或客户端断开连接后继续。 这不会扩展已撤销的设备权限,也不会扩展被当前 操作员角色 移除的权限。后续操作 仍会检查原始来源,包括已接受子项持有的工作。

通过 messages.queue 全局或按渠道配置:

{
  messages: {
    queue: {
      mode: "steer",
      cap: 20,
      drop: "summarize",
      byChannel: { discord: "collect" },
      debounceMsByChannel: { discord: 1000 },
    },
  },
}

队列选项

每个会话的 /queue 选项适用于排队投递。debounce 选项还会在 steer 模式下设置 Codex 转向静默窗口:

  • debounce:排空排队后续项或 collect 批次之前的静默窗口;在 Codex steer 模式下,发送批处理 turn/steer 之前的静默窗口。纯数字表示毫秒;接受单位 ms、s、m、h 和 d。
  • cap:每个会话的最大排队消息数。低于 1 的值会被忽略。
  • drop: "summarize"(默认):按需丢弃最旧的排队条目,保留紧凑摘要,并将其作为合成后续提示词注入。
  • drop: "old":按需丢弃最旧的排队条目,不保留摘要。
  • drop: "new":当队列已满时拒绝最新消息。

队列使用内置的 500ms 防抖。cap 默认为 20,drop 默认为 summarize。

转向与流式

当渠道流式为 partial 或 block 时,在活动运行到达运行时边界期间,转向可能看起来像多个简短的可见回复:

  • partial:预览可能提前定稿,然后在转向被接受后开始新的预览。
  • block:草稿大小的块可能产生相同的顺序外观。
  • 没有流式时,如果运行时无法接受同轮转向,转向会回退为活动运行后的后续项。

steer 不会中止进行中的工具。被跳过的 OpenClaw 工具调用会收到合成的成对错误结果,以使转录保持有效。当最新消息应中止当前运行时,请使用 /queue interrupt。

回答待处理问题

对某个待处理代理问题的纯文本回答会优先于普通队列处理直接发送到该问题,即使原生 CLI 无法接受 steering 也是如此。OpenClaw 会根据问题创建者的权限和当前活动运行来检查该回答,而不是根据你下一轮选择的模型。权限发生变化或创建者已关闭时,会产生明确的拒绝,而不是启动另一个回合。

如果回答可能已经提交但确认信息丢失,OpenClaw 会报告这种不确定性,并且不会将其作为 steering 或 followup 重新发送。重试前请检查对话。后续投递或源清理失败不会使该回答可重放,仅凭不确定性也不会取消原始代理运行。

优先级

对于模式选择,OpenClaw 按以下顺序解析:

  1. 内联或已存储的每会话 /queue 覆盖。
  2. messages.queue.byChannel.<channel>。
  3. messages.queue.mode。
  4. 默认 steer。

对于选项,内联或已存储的 /queue 选项优先于配置。然后按顺序应用通道特定的防抖(messages.queue.debounceMsByChannel)、插件防抖默认值和内置默认值。cap 和 drop 是全局/会话选项,而不是每通道配置键。

每会话覆盖

  • 将 /queue <steer|followup|collect|interrupt> 作为独立命令发送,以存储当前会话的队列模式。
  • 选项可以组合:/queue collect debounce:0.5s cap:25 drop:summarize
  • /queue default 或 /queue reset 会清除会话覆盖。

排队回合取消

当提示位于 followup/collect 队列中时(例如在另一个回合活动时到达的 TUI 或 webchat chat.send),Gateway 会为该客户端 runId 保留一个 Gateway 拥有的取消标识,直到排队内容运行或被丢弃。该标识跟随被折叠到溢出摘要中的内容。

  • 带有特定 runId 的 chat.abort 会在该回合仍排队时取消它,前提是请求者已获授权(与活动运行相同的归属规则)。
  • 对于没有 runId 的会话,chat.abort 会先取消已授权的排队回合,然后中止已授权的活动运行。该顺序可防止队列排空将工作提升到一个半停止的会话中。
  • 在没有按请求者检查的情况下清除整个会话队列,不是多所有者会话的停止路径。
  • 排队等待不会在 sessions.list 中投影为活动代理运行,也不拥有活动运行超时语义;只有活动阶段才拥有。
  • 在执行完成前过期或被取消的排队请求只结算该请求。其保存的输入仍标记为已取消;它不会结束活动回合、暂停其目标,或向对话中添加活动运行失败。
  • 在 Control UI 中删除特定排队消息也会隐藏该待处理提示及其附件。取消记录可防止重连后重放。停止和超时取消会保留其恢复消息。

由 Gateway 支持的客户端(包括 openclaw tui)会转发运行期间的提示,并让 Gateway 应用队列模式。Esc//stop 使用会话范围的中止,因此丢失的本地句柄不会让仍排队的提示继续运行。

openclaw chat 和 openclaw tui --local 在嵌入式运行时中应用相同的四种模式。本地 steer 在该运行时接受 steering 时注入到活动嵌入式运行中,否则变为 followup;followup 和 collect 保持为本地待处理工作;interrupt 在启动最新消息之前中止活动本地运行。显式的 /steer <message> 命令不是本地模式命令。

输入持久性

通过 chat.send 发送到现有会话的普通用户输入会在 Gateway 确认之前存储到每个代理的数据库中。这包括 Control UI、TUI、CLI、原生应用和 RPC 客户端。其他已连接客户端可以在其等待时显示已接受的输入,而无需等待新的代理回合。在 collect 模式下,追加合并后的回合并将其源输入标记为已消耗发生在同一事务中。即使浏览器重连错过了它们的最终事件,也可以协调这些源输入。

聊天会将记录的非 Web 客户端来源与发送者分开显示,例如 Alice · via CLI。这些标签中会省略 Web 来源,包括同时包含来自另一客户端输入的收集消息。报告的应用名称描述提交客户端;它们不会建立人类身份或授予权限。收集消息保留其贡献客户端来源,而没有记录来源的旧消息保留其现有归属。

这保留的是输入,而不是执行权限。如果 Gateway 在排队输入到达对话记录之前停止,它在重启后会显示为中断的输入,并需要显式重新发送。内存队列不会被重放。保留进程的主机睡眠可以正常继续现有队列。

由持久入口保留的通道消息在排队尝试在代理回合采用之前被放弃时仍可重试。放弃会在入口重试之前释放该尝试的入站和队列去重条目。已被采用或消费的消息保留重复抑制,因此传输重发不会重复其效果。

通道与范围

  • 适用于使用 Gateway 回复管道的所有入站通道上的自动回复代理运行(WhatsApp web、Telegram、Slack、Discord、Signal、iMessage、webchat 等)。
  • 默认通道(main)对入站回合是进程范围的;设置 agents.defaults.maxConcurrent 以允许多个会话并行。
  • 心跳嵌入式运行使用有界的 cron-nested 通道进行全局准入,以便慢速后台工作不会阻塞入站回复,同时其配置的心跳会话通道仍会串行化该会话的工作。
  • 可能存在其他通道(例如 cron、cron-nested、nested),以便后台作业可以并行运行而不阻塞入站回复。隔离的 cron 代理回合持有 cron 槽位,而其内部代理执行使用 cron-nested。共享的非 cron nested 流程保留其自身的通道行为。这些分离的运行仍由其原生运行时拥有。
  • 普通子代理执行使用 subagent:<immediate session>。agents.defaults.subagents.maxConcurrent 对每个会话默认为 8;独立会话和嵌套编排器不共享这些槽位。单独的 maxChildrenPerAgent 准入限制仍然适用。Codex 原生子代理 使用 Codex 自己的调度器。
  • Swarm 收集器子代理使用 subagent:swarm:<schedulerGroupKey>,受该组解析后的 tools.swarm.maxConcurrent(默认 32)限制。它们不占用其父级的普通子代理通道。由收集器生成的普通子代理使用该收集器自身的会话通道。Swarm 的 maxChildrenPerGroup 和 maxTotalPerGroup 仍然是单独的准入限制。通道诊断会识别 swarm 通道及其组键。
  • 每会话通道保证一次只有一个代理运行接触给定会话。
  • 无外部依赖或后台工作线程;纯 TypeScript + promises。

后台工作

Skill Workshop 审查和插件后台补全(包括 dreaming)共享一个独立的 三个并发运行 预算。Workshop 审查最多使用一个槽位;每个插件最多可使用三个可用槽位。这使维护工作不会占用前台回复容量,同时限制其总并发数。这些限制是内置的,无需配置。

等待后台工作的调度器本身不占用此预算。只有已分派的工作会持有槽位,直到完成或取消清理,因此调度器不会阻塞它所等待的子任务。已取消的排队工作会在开始前被移除;Gateway 重启或运行时退役可防止过期的补全启动或返回结果。

Control UI 的 系统繁忙度 覆盖层和 diagnostics.lanes 会在一个 background 行中报告此工作。其活跃和排队计数包含所有属主;属主通道不会在动态会话通道总数中再次计数。

故障排查

  • 如果命令似乎卡住,请启用详细日志,并查找 "queued for ...ms" 行以确认队列正在排空。
  • 接受一个回合后停止发出进度的 Codex app-server 运行会被 Codex 适配器中断,以便活跃会话通道可以释放,而不是等待外层运行超时。
  • 启用诊断时,如果在内置警告阈值后仍停留在 processing 且未观察到回复、工具、状态、块或 ACP 进度,则会话会根据当前活动分类:
  • 具有近期进度日志的活跃工作记录为 session.long_running。属主静默模型调用也会保持 session.long_running,直到内置中止阈值,以免将缓慢或非流式提供商过早报告为停滞。
  • 没有近期进度日志的活跃工作记录为 session.stalled;属主模型调用、被阻塞的工具调用和停滞的嵌入式运行在中止阈值时或之后切换为 session.stalled。无属主的过期模型/工具活动不会被隐藏为长时间运行。
  • session.stuck 保留用于可恢复的过期会话簿记,包括具有过期无属主模型/工具活动的空闲排队会话。
  • session.stuck 始终会触发可释放受影响会话通道的恢复。超过中止阈值的 session.stalled 分类(被阻塞的工具调用、停滞的模型调用或停滞的嵌入式运行)也可以触发主动中止恢复,因此两种分类都可以解除队列卡住,而不仅仅是 session.stuck。
  • 没有语义进度的重复模型请求共享一个停滞时钟。新的传输字节或另一次重试不能无限期地刷新它。恢复在中止前会重新检查该证据,遵守属主工具和提供商的重试截止时间,并让现有运行属主在队列排空前完成处理。
  • 当会话保持不变时,重复的 session.stuck 和 session.long_running 警告日志行会指数退避;无论该退避如何,恢复尝试仍会在每个心跳周期运行。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw