--- status: shipped created: 2026-08-03 owner: kandev --- # Manage Pending Message Queues ## Why Long-running tasks can receive messages faster than their active session can drain them. Users need to discard stale messages and tune the queue capacity without shell access. Today the queue panel promises **Clear all**, but the backend removes only user-origin rows. A queue filled with ten inter-task agent messages reports success with zero removals, immediately reappears, and remains full. Those same rows have no individual remove action. ## What ### Queue controls - Every visible pending message offers **Remove**, regardless of whether its provenance is user, agent, workflow, or server. - **Clear all** removes every visible pending message in the current session and returns the exact number removed. - Removal does not change provenance rules for other operations. Only user-owned messages may be edited. Merge behavior remains defined by [Merge Enqueued Messages Individually](message-queue-merge.md), and admission-time compaction is defined by [Automatically Merge Consecutive Queued Messages](message-queue-auto-merge.md). The two merge settings are independent. The interrupt-and-dispatch behavior is defined by [Send Queued Messages Now](message-queue-send-now.md). - Successful removal publishes the existing `message.queue.status_changed` event. All connected views reconcile to the backend result; the initiating view may update optimistically but must refetch on a race or failure. - Displayed queue positions are compact one-based ordinals derived from the current visible order. After remove, merge, or drain leaves gaps in durable FIFO position keys, the panel still displays `#1` through `#N` without gaps. - An entry already reserved for durable lifecycle delivery is not visible and cannot be removed by these controls. Task archive/delete retains its separate privileged purge behavior. ### Capacity setting - **Settings > Task Behavior > Message Queue** exposes the maximum number of persisted messages allowed per session alongside independent manual and automatic merge switches. - The default is `10`. A positive integer sets a cap; `0` means unlimited. - A saved setting applies immediately to later admissions. Existing entries are never trimmed. If a queue already exceeds a newly lowered limit, new messages are rejected with `queue_full` until its persisted count is below the limit. - Previously accepted work may be restored or retried after a delivery failure even when the new cap is lower. Capacity limits new work; it does not turn a failed delivery into message loss. - `KANDEV_QUEUE_MAX_PER_SESSION` remains supported and has higher precedence than the saved setting. A valid environment value makes the field read-only and the page explains the lock. Values at or below zero mean unlimited. An invalid value is logged and ignored. - Signed-in members may view the effective setting. Admins may change it. With authentication disabled, the synthetic single user remains an admin. - The settings route participates in the shared settings save coordinator. It does not add a page-local Save or Cancel button. ## API Surface Existing WebSocket actions retain their payloads: ```text message.queue.remove request: { session_id: string, entry_id: string } response: { entry_id: string } message.queue.cancel request: { session_id: string } response: { session_id: string, removed: number } ``` Both actions require access to `session_id`. `message.queue.remove` returns `entry_not_found` when the entry does not exist in that session, was drained, was reserved before cancellation won, or was removed concurrently. Clear-all is idempotent and returns `removed: 0` when no visible pending entries remain. New HTTP endpoints: ```text GET /api/v1/system/message-queue/settings PATCH /api/v1/system/message-queue/settings ``` GET response and PATCH response: ```json { "settings": { "max_per_session": 10, "merge_enabled": true, "auto_merge_enabled": true }, "effective": { "max_per_session": 10, "merge_enabled": true, "auto_merge_enabled": true, "source": "default", "locked": false } } ``` `source` is `default`, `setting`, or `environment`. `settings` contains the persisted value, or the default when no override exists. PATCH accepts: ```json { "max_per_session": 25 } ``` PATCH is partial: omitted capacity, manual-merge, and automatic-merge fields remain unchanged. It rejects negative capacity with `400`, rejects a valid environment capacity lock with `409` only when the patch names `max_per_session`, and requires the admin role. GET requires an authenticated install user under the existing system-route middleware. ## Data Model No queue-table migration is required. Capacity uses the existing install-wide `settings` table under key `message_queue` with JSON value: ```json { "max_per_session": 25, "merge_enabled": true, "auto_merge_enabled": true } ``` `queued_by` stays unchanged as immutable provenance. The existing `metadata.lifecycle_reserved_in_flight` marker distinguishes a durable row already being delivered from a pending row the user may discard. ## State and Concurrency For one visible row: ```text pending --reserve wins--> in flight --acknowledge--> removed | +--user remove wins-----------------------------> removed ``` Clear-all applies the same rule to every pending row in one session. Queue repository mutations serialize per session and compare the persisted row state before deletion. A concurrent reserve or content mutation may win; cancellation must never delete an entry after it becomes in-flight and must never report a row it did not remove. The effective capacity is held by the queue service in concurrency-safe memory. Each admission reads one value and uses that same snapshot for its repository operation and error response. A successful settings PATCH persists first and then applies the value live; after a process restart the same resolver applies environment, persisted setting, and default precedence before the orchestrator starts accepting work. ## Responsive and Mobile Behavior - The existing inline queue panel remains the phone composition; the queue list remains its single internal scroll owner and the composer stays visible. - Remove and clear controls are always discoverable on touch devices. Their interactive hit areas are at least 44 by 44 CSS pixels on coarse-pointer viewports; desktop may retain compact hover-revealed controls. - The Message Queue settings page uses the existing mobile settings sheet for navigation and the shared `settings-scroll-container` as its only page scroll owner. Content is one column; the numeric input is at least 44 pixels high and remains visible above the shared save bar and safe-area inset. - Desktop and mobile expose the same queue removal and capacity behavior. ## Failure Modes - **Drain or reserve wins:** individual remove returns `entry_not_found`; the frontend refreshes status without showing a stale row. Clear-all reports only rows it actually deleted. - **Backend mutation fails:** the UI restores/refetches queue state and shows a localized error. It does not leave an optimistic empty queue on screen. - **Queue setting is environment-locked:** the input is disabled and PATCH returns `409` if called directly. - **Invalid saved data:** the backend logs the problem, uses the default, and allows an admin to replace it with a valid value. - **Invalid environment data:** the backend logs the value, ignores it, and falls through to the persisted setting or default. - **Concurrent settings writes:** last successful persisted write wins; the response carries the value applied to the running service. ## Scenarios - **GIVEN** a session queue contains ten agent-origin messages, **WHEN** its owner clicks **Clear all**, **THEN** all ten pending rows are removed, the queue panel disappears, and a new message can be admitted. - **GIVEN** one visible agent-origin message, **WHEN** the owner clicks its **Remove** action, **THEN** that entry disappears and other entries retain FIFO order and provenance, with displayed positions compacted to `#1` through `#N`. - **GIVEN** visible user, agent, workflow, and server messages in one queue, **WHEN** the owner clicks **Clear all**, **THEN** every visible pending row is removed regardless of origin. - **GIVEN** a durable lifecycle head is reserved while a later pending message remains visible, **WHEN** the owner clears the queue, **THEN** the later message is removed and the hidden in-flight row remains available for acknowledgement. - **GIVEN** a caller cannot access another user's session, **WHEN** they submit its session ID and a disclosed queue-entry ID, **THEN** the operation reveals no queue contents and performs no mutation. - **GIVEN** the saved limit is `20` and no environment override exists, **WHEN** an admin changes it to `3`, **THEN** subsequent status reports `max: 3` and new admissions are rejected once three persisted rows are present. - **GIVEN** a queue already contains ten rows, **WHEN** the admin lowers the limit to `3`, **THEN** all ten remain, removals and draining still work, and new admissions remain blocked until fewer than three rows persist. - **GIVEN** an accepted message is dequeued and delivery fails after the limit was lowered below the current count, **WHEN** the orchestrator restores it, **THEN** the original message identity and FIFO position are preserved. - **GIVEN** `KANDEV_QUEUE_MAX_PER_SESSION=50`, **WHEN** the page loads, **THEN** it shows effective value `50`, identifies the environment source, and keeps the input read-only. - **GIVEN** an admin saves `0`, **WHEN** they enqueue more than ten messages, **THEN** no capacity error is produced solely by queue length and the page explains that the queue is unlimited. - **GIVEN** a phone viewport, **WHEN** the user opens the queue panel and the Task Behavior settings menu, **THEN** remove/clear are touch-sized and Message Queue is reachable without horizontal overflow or a second page scroll owner. ## Out of Scope - Editing agent-, workflow-, or server-origin message content. - Changing the shipped merge compatibility rules. - Interrupting an active prompt to dispatch pending work; that separate action is governed by [Send Queued Messages Now](message-queue-send-now.md). - Per-workspace, per-task, or per-session capacity overrides. - Automatically pruning old messages when a lower limit is saved. - Bulk selection beyond **Clear all**. Reordering pending messages is a separate capability governed by [Reorder Queued Messages](message-queue-reorder.md).