指令佇列(2026-01-16)

我們透過一個小型的行程內佇列,將所有頻道的自動回覆執行序列化處理,避免多個 Agent 執行之間互相碰撞,同時仍允許跨 Session 的安全平行處理。

為什麼需要佇列

  • 自動回覆執行可能很昂貴(LLM 呼叫),當多則訊息在短時間內接連到達時容易發生衝突。
  • 序列化避免了共用資源(Session 檔案、日誌、CLI stdin)的競爭,也降低觸發上游速率限制的機率。

運作方式

  • 一個具 Lane 感知的 FIFO 佇列,針對每個 Lane 以可設定的並行上限進行消耗(未設定的 Lane 預設為 1;main 預設為 4,subagent 為 8)。
  • runEmbeddedPiAgentSession Key 入列(Lane 為 session:<key>),確保每個 Session 同時只有一個執行中的任務。
  • 每個 Session 的執行接著被排入全域 Lane(預設為 main),整體平行度受 agents.defaults.maxConcurrent 限制。
  • 啟用詳細日誌時,若排隊等待超過約 2 秒才開始執行,會輸出一則簡短通知。
  • 輸入指示器在入列時就會立即觸發(當頻道支援時),使用者體驗在等待期間不受影響。

佇列模式(依頻道設定)

收到的訊息可以導引當前執行、排隊等待下一輪,或兩者兼具:

  • steer:立即注入當前執行(在下一個工具邊界後取消待處理的工具呼叫)。若非串流模式,會退回為 followup。
  • followup:在當前執行結束後排入下一輪 Agent 處理。
  • collect:將所有排隊訊息合併為單一後續處理輪(預設)。若訊息指向不同頻道/討論串,會個別消耗以保留路由。
  • steer-backlog(又名 steer+backlog):立即導引同時保留訊息供後續處理。
  • interrupt(舊版):中止該 Session 的作用中執行,然後處理最新的訊息。
  • queue(舊版別名):等同 steer

使用 steer-backlog 時,在被導引的執行之後你還會收到一個後續回應,因此串流介面看起來可能像是重複了。如果你希望每則收到的訊息只對應一個回應,請使用 collectsteer。 以獨立指令送出 /queue collect(依 Session 設定)或在設定中設定 messages.queue.byChannel.discord: "collect"

預設值(未在設定中指定時):

  • 所有介面 → collect

透過 messages.queue 進行全域或依頻道設定:

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

佇列選項

以下選項適用於 followupcollectsteer-backlog(以及 steer 退回為 followup 時):

  • debounceMs:等待靜默後再開始後續處理(防止連續的「繼續、繼續」)。
  • cap:每個 Session 的最大排隊訊息數。
  • drop:溢出策略(oldnewsummarize)。

summarize 會保留被捨棄訊息的簡短重點清單,並作為合成的後續提示注入。 預設值:debounceMs: 1000cap: 20drop: summarize

依 Session 覆蓋設定

  • 以獨立指令送出 /queue <mode> 即可為當前 Session 儲存該模式。
  • 可組合選項:/queue collect debounce:2s cap:25 drop:summarize
  • /queue default/queue reset 會清除 Session 的覆蓋設定。

適用範圍與保證

  • 適用於所有使用 Gateway 回覆管線的自動回覆 Agent 執行(WhatsApp web、Telegram、Slack、Discord、Signal、iMessage、webchat 等)。
  • 預設 Lane(main)在行程範圍內適用於收到的訊息和主要心跳;設定 agents.defaults.maxConcurrent 可允許多個 Session 平行處理。
  • 可能存在額外的 Lane(例如 cronsubagent),讓背景工作可以平行執行而不阻塞收到的回覆。
  • 依 Session 的 Lane 保證同一時間只有一個 Agent 執行會存取特定的 Session。
  • 沒有外部依賴或背景工作執行緒;純 TypeScript + Promise 實作。

疑難排解

  • 如果指令看起來卡住了,啟用詳細日誌並尋找「queued for …ms」的記錄來確認佇列有在正常消耗。
  • 如果你需要了解佇列深度,啟用詳細日誌並觀察佇列計時的記錄。