Apus serializes 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.

Why

How it works

Defaults

When unset, all inbound channel surfaces use:

Same-turn steering is the default. A prompt that arrives mid-run is injected into the active runtime when the run can accept steering, so no second session run is started. If the active run cannot accept steering, Apus waits for the active run to finish before starting the prompt.

Queue modes

/queue controls what normal inbound messages do while a session already has an active run:

Messages from channels using durable ingress admission, including Discord and Telegram, keep separate followup turns: collect behaves like followup there. Each message owns an exclusive admission lifecycle so one source cannot commit before another rejects a combined turn. Compatible Gateway chat.send inputs use non-exclusive lifecycles and can still collect.

For runtime-specific timing and dependency behavior, see Steering queue. For the explicit /steer <message> command, see Steer.

Gateway input retains its authenticated operator and original scope ceiling while queued or delegated to a child. Collecting messages or steering an active run requires compatible operator sources and tool permissions; other input waits in FIFO order instead of borrowing the active or newest sender's permissions.

An accepted turn can continue after its request returns or its client disconnects. That does not extend revoked device authority or permissions removed by a current operator role. Subsequent actions still check the original source, including work held by an accepted child.

Configure globally or per channel via messages.queue:

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

Both maps accept channel IDs from bundled and installed plugins. For example, messages.queue.byChannel.x sets the X plugin's queue mode. Config validation warns about IDs absent from the channel registry; install the corresponding plugin or correct the key. Queue modes and nonnegative integer debounce values are still validated.

Queue options

Per-session /queue options apply to queued delivery. The debounce option also sets the Codex steering quiet window in steer mode:

The queue uses a built-in 500ms debounce. cap defaults to 20, and drop defaults to summarize.

Steer and streaming

When channel streaming is partial or block, steering can look like several short visible replies while the active run reaches runtime boundaries:

steer does not abort in-flight tools. Skipped Apus tool calls receive synthetic paired error results so the transcript remains valid. Use /queue interrupt when the newest message should abort the current run.

Answering a pending question

A plain-text answer to a pending agent question goes to that question before ordinary queue handling, including when a native CLI cannot accept steering. Apus checks the answer against the question creator's permissions and active run, not the model selected for your next turn. Changed permissions or a closed creator produce an explicit refusal rather than starting another turn.

If the answer may have committed but confirmation is lost, Apus reports that uncertainty and does not resend it as steering or a followup. Check the conversation before retrying. A later delivery or source-cleanup failure does not make the answer replayable, and uncertainty alone does not cancel the original agent run.

Precedence

For mode selection, Apus resolves:

  1. Inline or stored per-session /queue override.
  2. messages.queue.byChannel.<channel>.
  3. messages.queue.mode.
  4. Default steer.

For options, inline or stored /queue options win over config. Then channel-specific debounce (messages.queue.debounceMsByChannel), plugin debounce defaults, and built-in defaults are applied, in that order. cap and drop are global/session options, not per-channel config keys.

Per-session overrides

Queued-turn cancellation

While a prompt sits in the followup/collect queue (for example a TUI or webchat chat.send arriving while another turn is active), Gateway keeps a Gateway-owned cancel identity for that client runId until the queued content runs or is dropped. The identity follows content folded into an overflow summary.

Gateway-backed clients (including apus tui) forward mid-run prompts and let the Gateway apply the queue mode. Esc//stop uses a session-scoped abort so lost local handles cannot leave a still-queued prompt running.

apus chat and apus tui --local apply the same four modes in the embedded runtime. Local steer injects into an active embedded run when that runtime accepts steering and otherwise becomes a followup; followup and collect remain local pending work; interrupt aborts the active local run before starting the newest message. The explicit /steer <message> command is not a local-mode command.

Input durability

Ordinary user input sent through chat.send to an existing session is stored in the per-agent database before the Gateway acknowledges it. This includes the Control UI, TUI, CLI, native apps, and RPC clients. Other connected clients can display the accepted input while it waits, without waiting for a new agent turn. In collect mode, appending the combined turn and marking its source inputs consumed happen in one transaction. A browser reconnect can reconcile those source inputs even if it missed their final events.

The chat displays recorded non-Web client sources separately from the sender, for example Alice ยท via CLI. Web sources are omitted from these labels, including in collected messages that also contain input from another client. Reported app names describe the submitting client; they do not establish a human identity or grant permissions. Collected messages retain their contributing client sources, and older messages without recorded sources keep their existing attribution.

This preserves input, not execution permissions. If the Gateway stops before a queued input reaches the transcript, it appears as interrupted input after restart and requires an explicit resend. The in-memory queue is not replayed. Host sleep that preserves the process can continue the existing queue normally.

Channel messages retained by durable ingress remain retryable when a queued attempt is abandoned before agent-turn adoption. Abandonment releases that attempt's inbound and queue dedupe entries before ingress retries it. Messages already adopted or consumed keep duplicate suppression, so transport redelivery does not repeat their effects.

Lanes and scope

Background work

Skill Workshop reviews and plugin background completions, including dreaming, share a separate budget of three concurrent runs. Workshop reviews use at most one slot; each plugin can use up to three available slots. This keeps maintenance work out of foreground reply capacity while bounding its total concurrency. These limits are built in and need no configuration.

Schedulers that await background work do not occupy this budget themselves. Only the dispatched work holds a slot, through completion or cancellation cleanup, so a scheduler cannot block the child it is waiting for. Cancelled queued work is removed before it starts; Gateway restart or runtime retirement prevents stale completions from starting or returning results.

The Control UI System busyness overlay and diagnostics.lanes report this work in one background row. Its active and queued counts include every owner; owner lanes are not counted again in the dynamic session-lane totals.

Troubleshooting

Related