{
  "title": "Command Queue (2026-01-16)",
  "content": "We serialize inbound auto-reply runs (all channels) through a tiny in-process queue to prevent multiple agent runs from colliding, while still allowing safe parallelism across sessions.\n\n* Auto-reply runs can be expensive (LLM calls) and can collide when multiple inbound messages arrive close together.\n* Serializing avoids competing for shared resources (session files, logs, CLI stdin) and reduces the chance of upstream rate limits.\n\n* A lane-aware FIFO queue drains each lane with a configurable concurrency cap (default 1 for unconfigured lanes; main defaults to 4, subagent to 8).\n* `runEmbeddedPiAgent` enqueues by **session key** (lane `session:<key>`) to guarantee only one active run per session.\n* Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agents.defaults.maxConcurrent`.\n* When verbose logging is enabled, queued runs emit a short notice if they waited more than \\~2s before starting.\n* Typing indicators still fire immediately on enqueue (when supported by the channel) so user experience is unchanged while we wait our turn.\n\n## Queue modes (per channel)\n\nInbound messages can steer the current run, wait for a followup turn, or do both:\n\n* `steer`: inject immediately into the current run (cancels pending tool calls after the next tool boundary). If not streaming, falls back to followup.\n* `followup`: enqueue for the next agent turn after the current run ends.\n* `collect`: coalesce all queued messages into a **single** followup turn (default). If messages target different channels/threads, they drain individually to preserve routing.\n* `steer-backlog` (aka `steer+backlog`): steer now **and** preserve the message for a followup turn.\n* `interrupt` (legacy): abort the active run for that session, then run the newest message.\n* `queue` (legacy alias): same as `steer`.\n\nSteer-backlog means you can get a followup response after the steered run, so\nstreaming surfaces can look like duplicates. Prefer `collect`/`steer` if you want\none response per inbound message.\nSend `/queue collect` as a standalone command (per-session) or set `messages.queue.byChannel.discord: \"collect\"`.\n\nDefaults (when unset in config):\n\n* All surfaces → `collect`\n\nConfigure globally or per channel via `messages.queue`:\n\nOptions apply to `followup`, `collect`, and `steer-backlog` (and to `steer` when it falls back to followup):\n\n* `debounceMs`: wait for quiet before starting a followup turn (prevents “continue, continue”).\n* `cap`: max queued messages per session.\n* `drop`: overflow policy (`old`, `new`, `summarize`).\n\nSummarize keeps a short bullet list of dropped messages and injects it as a synthetic followup prompt.\nDefaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.\n\n## Per-session overrides\n\n* Send `/queue <mode>` as a standalone command to store the mode for the current session.\n* Options can be combined: `/queue collect debounce:2s cap:25 drop:summarize`\n* `/queue default` or `/queue reset` clears the session override.\n\n## Scope and guarantees\n\n* Applies to auto-reply agent runs across all inbound channels that use the gateway reply pipeline (WhatsApp web, Telegram, Slack, Discord, Signal, iMessage, webchat, etc.).\n* Default lane (`main`) is process-wide for inbound + main heartbeats; set `agents.defaults.maxConcurrent` to allow multiple sessions in parallel.\n* Additional lanes may exist (e.g. `cron`, `subagent`) so background jobs can run in parallel without blocking inbound replies.\n* Per-session lanes guarantee that only one agent run touches a given session at a time.\n* No external dependencies or background worker threads; pure TypeScript + promises.\n\n* If commands seem stuck, enable verbose logs and look for “queued for …ms” lines to confirm the queue is draining.\n* If you need queue depth, enable verbose logs and watch for queue timing lines.",
  "code_samples": [],
  "headings": [
    {
      "level": "h2",
      "text": "Why",
      "id": "why"
    },
    {
      "level": "h2",
      "text": "How it works",
      "id": "how-it-works"
    },
    {
      "level": "h2",
      "text": "Queue modes (per channel)",
      "id": "queue-modes-(per-channel)"
    },
    {
      "level": "h2",
      "text": "Queue options",
      "id": "queue-options"
    },
    {
      "level": "h2",
      "text": "Per-session overrides",
      "id": "per-session-overrides"
    },
    {
      "level": "h2",
      "text": "Scope and guarantees",
      "id": "scope-and-guarantees"
    },
    {
      "level": "h2",
      "text": "Troubleshooting",
      "id": "troubleshooting"
    }
  ],
  "url": "llms-txt#command-queue-(2026-01-16)",
  "links": []
}