diff --git a/docs/decisions/0051-pr-agent-notifications-extend-task-pr-automation.md b/docs/decisions/0051-pr-agent-notifications-extend-task-pr-automation.md
index d69d5a8c90..7e7999c1ff 100644
--- a/docs/decisions/0051-pr-agent-notifications-extend-task-pr-automation.md
+++ b/docs/decisions/0051-pr-agent-notifications-extend-task-pr-automation.md
@@ -209,3 +209,14 @@ target whose `queued_by` equals the caller's identity.
Workflow- and server-owned rows remain entirely non-mergeable, and the reference-
overflow guard (a merge is rejected atomically when the combined entity
references exceed the per-message cap) applies to every sender kind.
+
+## Supersession: session-owner queue cancellation
+
+**Date:** 2026-08-03
+
+[ADR-2026-08-03-separate-message-queue-provenance-cancellation-and-capacity](2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md)
+supersedes this ADR only where it prohibited authorized clients from deleting
+agent-, workflow-, or server-owned **pending** queue rows. Provenance still
+governs editing and merging. Durable lifecycle rows already reserved in flight
+remain hidden and non-cancellable, and all acknowledgement, retry, purge, and
+generation guarantees in this ADR remain unchanged.
diff --git a/docs/decisions/2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md b/docs/decisions/2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md
new file mode 100644
index 0000000000..fbb3060c97
--- /dev/null
+++ b/docs/decisions/2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md
@@ -0,0 +1,103 @@
+# ADR-2026-08-03-separate-message-queue-provenance-cancellation-and-capacity: Separate Message Queue Provenance, Cancellation, and Capacity
+
+**Status:** accepted
+**Date:** 2026-08-03
+**Area:** backend, frontend, protocol, security
+
+## Context
+
+Queued messages carry a `queued_by` identity that records their provenance:
+user, agent, workflow, or server. ADR 0051 also used that provenance as a
+client-mutation boundary, allowing clients to remove only user-owned rows.
+That coupling made an authorized task owner unable to discard visible work
+sent by another task. The queue panel still offered **Clear all**, but the
+backend silently retained every agent-, workflow-, and server-owned row. A
+queue containing ten inter-task messages therefore stayed full after the
+user's explicit clear action.
+
+The per-session capacity is also an operational policy. It currently comes
+only from `KANDEV_QUEUE_MAX_PER_SESSION`, so changing it requires access to the
+launch environment and a backend restart. Users need an install-wide setting
+without weakening environment-controlled deployments or risking already
+accepted delivery work when the limit is lowered.
+
+## Decision
+
+Treat provenance, content mutation, cancellation, and capacity as separate
+concerns.
+
+- `queued_by` remains immutable provenance. It continues to govern content
+ editing and the existing merge rules; it does not grant a pending message an
+ exemption from an authorized user's explicit discard action.
+- A caller authorized for the target session may remove any **visible pending**
+ queue entry, regardless of whether it came from a user, agent, workflow, or
+ server. **Clear all** removes every visible pending message in that session.
+- A durable lifecycle row marked `lifecycle_reserved_in_flight` is already in
+ the delivery protocol, is omitted from queue status, and is not cancellable
+ through either user action. Cancellation and reservation serialize per
+ session and use persisted compare guards so whichever operation wins does so
+ completely. Backend archive/delete purge remains the privileged operation
+ that may remove reserved rows and advance lifecycle generation.
+- Every user-facing WebSocket queue operation authorizes the named session in
+ its handler. Session scoping remains mandatory on every row mutation, so a
+ disclosed or guessed entry UUID cannot mutate another session.
+- Queue capacity is an install-wide setting exposed under **Settings > General
+ > Message Queue**. The effective value uses this precedence:
+
+ ```text
+ valid KANDEV_QUEUE_MAX_PER_SESSION > persisted install setting > default 10
+ ```
+
+ A positive integer is the maximum number of persisted messages per session;
+ `0` means unlimited. For compatibility, a non-positive environment value is
+ normalized to `0`. An invalid environment value is logged and ignored so a
+ valid persisted value or the default can apply.
+- A valid environment value locks the UI control. Otherwise an admin may save
+ a non-negative integer to the install-wide `settings` store. The saved value
+ applies live through a concurrency-safe queue-service setter; no restart is
+ required.
+- Changing the limit never deletes existing rows. Lowering it below a current
+ queue size blocks new admissions until the count falls below the limit.
+ Restoration and retry of work accepted before a dequeue bypass the admission
+ cap, so a failed delivery cannot lose work merely because the setting was
+ lowered.
+
+This decision supersedes ADR 0051 only where that ADR prohibited an authorized
+client from deleting agent-, workflow-, or server-owned **pending** rows. ADR
+0051's provenance, durable reservation, acknowledgement, archive-generation,
+and delivery guarantees remain in force.
+
+## Consequences
+
+- **Clear all** matches its visible promise and can recover a queue filled by
+ inter-task or automation messages.
+- Individual removal works consistently for every pending row while editing
+ and merging retain stricter provenance rules.
+- A reservation that wins the race may still be delivered, but hidden
+ in-flight work cannot be silently deleted between executor handoff and
+ acknowledgement.
+- Operators can tune queue pressure without rebuilding launch configuration;
+ managed deployments keep environment authority.
+- The queue service's cap becomes runtime mutable and all admission paths must
+ read one atomic snapshot per operation.
+- Queue-setting reads are available to signed-in users; mutations are
+ admin-only. Authentication-disabled installs retain their synthetic-admin
+ behavior.
+
+## Alternatives Considered
+
+1. **Keep backend-origin rows undeletable and relabel the button.** Rejected
+ because it leaves users unable to recover a queue filled by stale inter-task
+ messages.
+2. **Clear agent rows but preserve workflow/server rows.** Rejected because
+ **Clear all** would still have origin-dependent hidden exceptions and the
+ user owns the target session, not the sender's provenance.
+3. **Delete reserved in-flight lifecycle rows too.** Rejected because those
+ rows participate in at-least-once handoff and acknowledgement; deletion can
+ corrupt delivery state after work has already reached the executor.
+4. **Require restart after a capacity change.** Rejected because the cap is an
+ admission-time integer and can be updated safely without reconstructing the
+ orchestrator.
+5. **Make capacity per workspace or per session.** Rejected for this change.
+ The existing policy is install-wide, and finer scopes add inheritance and
+ override semantics without evidence they are needed.
diff --git a/docs/decisions/INDEX.md b/docs/decisions/INDEX.md
index ecf63f6ca0..b4beeeff0f 100644
--- a/docs/decisions/INDEX.md
+++ b/docs/decisions/INDEX.md
@@ -112,3 +112,4 @@ Read individual ADRs for full context. Create new ones via `/record decision` or
| 2026-08-02-class-aware-git-subprocess-admission | [Class-Aware Git Subprocess Admission](2026-08-02-class-aware-git-subprocess-admission.md) | accepted | backend, agentctl, observability | 2026-08-02 |
| 2026-08-02-isolate-replaceable-session-stream-traffic | [Isolate Replaceable Session Stream Traffic](2026-08-02-isolate-replaceable-session-stream-traffic.md) | accepted | backend, frontend, protocol | 2026-08-02 |
| 2026-08-03-backend-owned-cancellation-progress | [Keep Cancellation Progress Backend Owned](2026-08-03-backend-owned-cancellation-progress.md) | accepted | backend, frontend, protocol | 2026-08-03 |
+| 2026-08-03-separate-message-queue-provenance-cancellation-and-capacity | [Separate Message Queue Provenance, Cancellation, and Capacity](2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md) | accepted | backend, frontend, protocol, security | 2026-08-03 |
diff --git a/docs/plans/message-queue-management/plan.md b/docs/plans/message-queue-management/plan.md
new file mode 100644
index 0000000000..0009d28879
--- /dev/null
+++ b/docs/plans/message-queue-management/plan.md
@@ -0,0 +1,188 @@
+---
+spec: docs/specs/ui/message-queue-management.md
+decision: docs/decisions/2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md
+created: 2026-08-03
+status: implemented
+---
+
+# Fix Plan: Message Queue Management
+
+## Overview
+
+Make the queue panel's **Clear all** promise literal, add removal for every
+visible pending message, and expose the per-session queue limit under
+**Settings > General > Message Queue**. Preserve durable in-flight delivery,
+session isolation, message provenance, and already accepted retries.
+
+## Confirmed Root Cause
+
+The reported Bitbucket Integration task has ten persisted queue rows. Every row
+has `queued_by='agent'`. The frontend sends `message.queue.cancel`, whose
+handler calls `messagequeue.Service.CancelAll`; the SQLite and memory
+repositories then delete only rows whose `queued_by` is not `agent`,
+`workflow`, or `server`. The response therefore says `removed: 0`. The hook's
+optimistic empty state is replaced by the next authoritative status event, so
+the full queue immediately reappears.
+
+Individual removal has the same repository guard. The React row action also
+uses one `canEdit` flag for both **Edit** and **Remove**, hiding removal on
+agent/workflow rows. This behavior implemented ADR 0051's prior ownership
+boundary, so the repair requires an explicit contract change rather than only
+a frontend patch.
+
+The live installation was inspected read-only. No queued row, task, session,
+or setting was mutated during diagnosis.
+
+## Behavioral Contract
+
+- An authorized session owner may remove any pending entry returned by queue
+ status and may clear all such entries, regardless of provenance.
+- `queued_by` continues to control editing and merge compatibility.
+- The visible queue uses compact one-based ordinals after remove, merge, or
+ drain; durable FIFO position keys remain unchanged.
+- A durable lifecycle row already marked in flight remains hidden and
+ non-cancellable. Cancellation and reservation must be race-safe.
+- Every user-facing queue WebSocket handler authorizes its session. Queue add
+ and append additionally validate that `task_id` belongs to `session_id`.
+- Capacity resolves as valid environment value, then persisted install
+ setting, then default `10`. `0` means unlimited.
+- Saving capacity applies live, does not prune existing rows, and limits only
+ new admissions. Restore/retry of already accepted work bypasses the cap.
+
+## Backend Design
+
+### Pending-row cancellation
+
+- Change the `Repository.DeleteByID` and `DeleteAllBySession` contracts from
+ user-origin deletion to visible-pending deletion.
+- In both SQLite/Postgres-compatible and memory repositories, serialize with
+ the existing per-session mutation lock and skip
+ `lifecycle_reserved_in_flight` rows.
+- Use persisted compare guards and affected-row counts so an out-of-process
+ reserve or update that wins cannot be deleted or counted afterward.
+- Preserve the mandatory `(session_id, entry_id)` scope and the privileged
+ `PurgeTask`/`AcknowledgeByID` paths.
+- Update service logging and error text to remove the obsolete "not owned by
+ caller" implication.
+
+### Queue action authorization
+
+- Add a task-service authorization method for a `(task_id, session_id)` pair:
+ authorize both resources, load the session, and fail closed when the pair is
+ inconsistent.
+- Inject a queue access authorizer into `QueueHandlers` from backend
+ composition.
+- Guard get, add, append, update, remove, merge, clear, and drain before any
+ queue read or mutation. Add/append use pair authorization; session-only
+ actions use session authorization.
+- Return the existing non-enumerating error shape and test that unauthorized
+ calls neither reveal status nor publish a mutation event.
+
+### Runtime capacity setting
+
+- Add `internal/system/queuesettings` with typed settings, generic-settings
+ adapter, environment resolver, live target interface, service, and HTTP
+ handlers.
+- Persist `{"max_per_session": N}` under install-wide key `message_queue`.
+- Resolve the setting before the orchestrator queue starts. Extend system
+ wiring with the live `messagequeue.Service` so GET/PATCH use the same target
+ and register under `/api/v1/system/message-queue/settings`.
+- Register GET on the signed-in system group and PATCH on its admin subgroup.
+ PATCH validates `N >= 0`, returns `409` while a valid environment override is
+ active, persists first, then live-applies the response value.
+- Replace the queue service's plain integer with a concurrency-safe runtime
+ value and add `SetMaxPerSession`. Each admission snapshots it once for the
+ repository call, log, and `queue_full` response.
+- Normalize non-positive environment values to effective `0`; ignore and warn
+ on malformed values. Preserve `KANDEV_QUEUE_MAX_PER_SESSION` compatibility.
+- Make ordinary restore and durable lifecycle retry bypass the current
+ admission cap. Initial user/agent/workflow/server insertions still obey it.
+
+## Frontend Design
+
+### Queue panel
+
+- Split `canEdit` from `canRemove` in `QueuedGhostMessage` and `RowActions`.
+ Every visible row gets remove; editing remains user-only and merge rules stay
+ unchanged.
+- Keep optimistic behavior only when the hook can reconcile it. On removal or
+ clear failure/race, refetch authoritative status and show localized feedback.
+- Replace touched queue-header literals with `chat` namespace keys.
+- Keep compact hover controls on pointer devices. On coarse-pointer viewports,
+ make remove, clear, close, expand, merge, and run-next actions at least 44px
+ high/wide and always visible.
+
+### General settings page
+
+- Add API types and fetch/update helpers for the configured/effective response.
+- Add a client settings card with a numeric `min=0` input, explicit unlimited
+ help, source badge/help, loading/error states, environment lock state, and
+ member read-only state.
+- Register the draft with `useSettingsSaveContributor`; do not add local
+ Save/Cancel controls.
+- Add `/settings/general/message-queue` to the Vite route registry, app-page
+ composition, General sidebar, breadcrumb label map, and sidebar-navigation
+ tests. Redirect the former `/settings/system/message-queue` URL to preserve
+ existing bookmarks.
+- Add all new copy to translation catalogs and regenerate/check pseudo locale.
+ Avoid module-scope `t()` by storing label keys and translating during render.
+
+## Responsive and Mobile Contract
+
+The nearest shipped queue exemplar is
+`apps/web/e2e/tests/chat/mobile-message-queue-management.spec.ts`: retain its inline
+panel and single queue scroll region so the composer stays visible. The new
+controls must not depend on hover and must meet the 44px coarse-pointer target.
+
+The settings page uses the existing left-side mobile Settings sheet and shared
+`settings-scroll-container`. Render one column with no nested page scroller;
+keep the numeric field at least 44px high and above the existing save bar plus
+safe-area padding. Desktop and mobile expose identical values and permissions.
+
+## TDD and Verification Strategy
+
+1. Change existing backend tests to expect agent/workflow/server pending rows to
+ be removable and add in-flight/race/security regressions. Run them RED
+ against unchanged production code.
+2. Implement cancellation and handler authorization; run memory, SQLite, and
+ env-gated Postgres package tests GREEN.
+3. Add capacity resolver/store/service/handler tests, including live apply,
+ environment precedence, lower-than-current behavior, and retry restoration.
+ Run RED, then implement and rerun GREEN.
+4. Add frontend component/hook/settings tests first, including split
+ edit/remove permissions and environment/member locks. Implement minimal UI
+ changes, then run typecheck, lint, and i18n gates.
+5. Add desktop and mobile Playwright scenarios for mixed/agent provenance and
+ settings navigation/save. Preserve and restore the install-wide cap in test
+ teardown so parallel specs cannot inherit modified state.
+6. Update public queue/configuration/operations docs and run their validators.
+
+## Implementation Waves
+
+- [x] [Task 01: Make pending queue cancellation authoritative](task-01-authoritative-queue-cancellation.md) — wave 1
+- [x] [Task 02: Add live install-wide queue capacity settings](task-02-live-queue-capacity-settings.md) — wave 2, after Task 01
+- [x] [Task 03: Expose removal for every visible queue row](task-03-visible-queue-removal-ui.md) — wave 3, after Task 01
+- [x] [Task 04: Add the Message Queue General settings page](task-04-message-queue-settings-ui.md) — wave 3, after Task 02
+- [x] [Task 05: Prove end-to-end behavior and update public docs](task-05-e2e-and-public-docs.md) — wave 4, after Tasks 03 and 04
+
+Tasks 03 and 04 are file-disjoint enough to run in parallel after their backend
+dependencies, but this package does not authorize subagents. Implementation
+remains in the user-controlled primary session unless the user later delegates.
+
+## Environment Prerequisite
+
+If `apps/node_modules` is absent, run `pnpm install --frozen-lockfile` from
+`apps/` before frontend RED tests. Do not change the lockfile.
+
+## Risks and Boundaries
+
+- A clear/remove request may lose to reservation; that message can still be
+ delivered. The UI must reconcile rather than claim it cancelled in-flight
+ work.
+- Dynamic lowering must not make restore or lifecycle retry lossy.
+- Settings tests mutate install-wide state and must restore their baseline even
+ after failure.
+- Queue contents from the diagnosed live Bitbucket task remain untouched until
+ this package is implemented and deployed.
+- Editing backend-origin content, changing merge rules, queue reordering,
+ per-workspace limits, and automatic pruning remain out of scope.
diff --git a/docs/plans/message-queue-management/task-01-authoritative-queue-cancellation.md b/docs/plans/message-queue-management/task-01-authoritative-queue-cancellation.md
new file mode 100644
index 0000000000..cf60a5e5f6
--- /dev/null
+++ b/docs/plans/message-queue-management/task-01-authoritative-queue-cancellation.md
@@ -0,0 +1,93 @@
+---
+id: "01-authoritative-queue-cancellation"
+title: "Make pending queue cancellation authoritative"
+status: completed
+wave: 1
+depends_on: []
+plan: "plan.md"
+spec: "../../specs/ui/message-queue-management.md"
+---
+
+# Task 01: Make Pending Queue Cancellation Authoritative
+
+## Acceptance
+
+- Individual remove succeeds for visible user, agent, workflow, and server
+ entries while preserving session scope and FIFO order.
+- Clear-all removes every visible pending entry and returns the exact affected
+ count.
+- A hidden durable in-flight row survives individual and bulk cancellation.
+- Reserve/remove and reserve/clear races have one complete winner; no row is
+ both delivered and reported removed.
+- Every queue WebSocket action authorizes its session before access. Add and
+ append also reject an authorized session paired with a different task.
+- Unauthorized calls expose no queue state, mutate nothing, and publish no
+ status event.
+
+## TDD Sequence
+
+1. Reverse the existing agent/workflow removal expectations in service and
+ handler tests; add server-origin, mixed clear-all, and in-flight cases.
+2. Add deterministic SQLite/memory reservation race tests and an env-gated
+ Postgres parity case. Run focused packages and record RED.
+3. Add handler authorization tests for session-only and task/session-pair
+ actions, including non-enumerating failure behavior. Run RED.
+4. Change repository contracts and implementations with per-session locks,
+ persisted compare guards, and affected-row accounting.
+5. Add the task/session pair authorizer, inject it into queue handlers, and
+ guard all entry points before queue access.
+6. Rerun focused packages GREEN and inspect logs/error strings for obsolete
+ origin-ownership wording.
+
+## Verification
+
+```bash
+cd apps/backend
+go test ./internal/orchestrator/messagequeue ./internal/orchestrator/handlers ./internal/task/service ./internal/backendapp
+KANDEV_TEST_POSTGRES_DSN="$KANDEV_TEST_POSTGRES_DSN" go test ./internal/orchestrator/messagequeue
+```
+
+The Postgres command skips when `KANDEV_TEST_POSTGRES_DSN` is unset.
+
+## Files Likely Touched
+
+- `apps/backend/internal/orchestrator/messagequeue/repository.go`
+- `apps/backend/internal/orchestrator/messagequeue/repository_sqlite.go`
+- `apps/backend/internal/orchestrator/messagequeue/repository_memory.go`
+- `apps/backend/internal/orchestrator/messagequeue/service.go`
+- `apps/backend/internal/orchestrator/messagequeue/types.go`
+- `apps/backend/internal/orchestrator/messagequeue/repository_sqlite_test.go`
+- `apps/backend/internal/orchestrator/messagequeue/repository_postgres_*_test.go`
+- `apps/backend/internal/orchestrator/messagequeue/service_test.go`
+- `apps/backend/internal/orchestrator/handlers/queue_handlers.go`
+- `apps/backend/internal/orchestrator/handlers/queue_handlers_test.go`
+- `apps/backend/internal/task/service/service_access.go`
+- `apps/backend/internal/task/service/service_access_test.go`
+- `apps/backend/internal/backendapp/gateway.go`
+
+## Parallelism
+
+Sequential. Repository race semantics, service expectations, and handler
+authorization share one RED-GREEN cycle.
+
+## Output Contract
+
+Report RED/GREEN commands and counts, SQLite/Postgres status, authorization
+coverage, changed files, and any residual race or compatibility risk. Update
+this task and `plan.md` status in the same implementation conversation.
+
+## Results
+
+- RED: `go test ./internal/orchestrator/messagequeue -run 'Test(RemoveEntry|CancelAll)' -count=1`
+ failed because agent/workflow/server rows remained.
+- RED: `go test ./internal/task/service -run TestAuthorizeTaskSessionAccessRejectsMismatchedPair -count=1`
+ failed because a same-owner mismatched task/session pair passed.
+- RED: `go test ./internal/orchestrator/handlers -run 'TestQueueHandlersDenyUnauthorized' -count=1`
+ failed because every handler reached queue dependencies.
+- GREEN: `go test -race ./internal/orchestrator/messagequeue -count=1`.
+- GREEN: full `internal/orchestrator/handlers` and `internal/backendapp`; focused
+ task-service access tests.
+- PostgreSQL parity test is present and skipped locally because
+ `KANDEV_TEST_POSTGRES_DSN` is unset.
+- The full task-service package was attempted; unrelated existing filesystem
+ tests fail with `parent directory cannot be accessed` in this executor.
diff --git a/docs/plans/message-queue-management/task-02-live-queue-capacity-settings.md b/docs/plans/message-queue-management/task-02-live-queue-capacity-settings.md
new file mode 100644
index 0000000000..7011c6876d
--- /dev/null
+++ b/docs/plans/message-queue-management/task-02-live-queue-capacity-settings.md
@@ -0,0 +1,99 @@
+---
+id: "02-live-queue-capacity-settings"
+title: "Add live install-wide queue capacity settings"
+status: completed
+wave: 2
+depends_on: ["01-authoritative-queue-cancellation"]
+plan: "plan.md"
+spec: "../../specs/ui/message-queue-management.md"
+---
+
+# Task 02: Add Live Install-Wide Queue Capacity Settings
+
+## Acceptance
+
+- Effective capacity resolves as valid environment, persisted setting, then
+ default `10`; malformed environment data is ignored with a warning.
+- Non-positive environment values normalize to unlimited `0`; persisted
+ negative values are rejected.
+- GET returns configured and effective values, source, and lock state. PATCH is
+ admin-only, rejects an environment lock, persists, and applies live.
+- Queue status and every new-admission path observe one concurrency-safe live
+ cap snapshot.
+- Lowering capacity never removes rows. New admission blocks until below cap,
+ while restore and durable retry of accepted work remain possible.
+- Restart resolution applies the same value before the orchestrator accepts
+ work.
+
+## TDD Sequence
+
+1. Add pure resolver and store tests for default, setting, environment,
+ malformed data, unlimited normalization, and validation. Run RED.
+2. Add service/handler tests for GET, admin PATCH, member rejection,
+ environment lock, persistence failure, and live-target application. Run RED.
+3. Add queue-service tests that change the cap concurrently, lower it below a
+ current queue, and restore/retry accepted work. Run RED.
+4. Implement `internal/system/queuesettings`, the atomic queue-service setter,
+ startup resolver, and system route wiring.
+5. Rerun focused tests GREEN, then run backend package tests covering startup
+ composition and system routes.
+
+## Verification
+
+```bash
+cd apps/backend
+go test ./internal/system/queuesettings ./internal/system ./internal/orchestrator/messagequeue ./internal/backendapp
+go test -race ./internal/system/queuesettings ./internal/orchestrator/messagequeue
+```
+
+## Files Likely Touched
+
+- `apps/backend/internal/system/queuesettings/types.go`
+- `apps/backend/internal/system/queuesettings/store.go`
+- `apps/backend/internal/system/queuesettings/service.go`
+- `apps/backend/internal/system/queuesettings/handler.go`
+- matching `*_test.go` files
+- `apps/backend/internal/system/system.go`
+- `apps/backend/internal/system/system_routes_test.go`
+- `apps/backend/internal/orchestrator/messagequeue/service.go`
+- `apps/backend/internal/orchestrator/messagequeue/service_test.go`
+- `apps/backend/internal/backendapp/orchestrator.go`
+- `apps/backend/internal/backendapp/main.go`
+- `apps/backend/internal/backendapp/helpers_test.go`
+
+## Dependencies
+
+Task 01 lands first so this task can update queue-service tests without
+conflicting with cancellation contract changes.
+
+## Parallelism
+
+Sequential after Task 01. Startup resolution, HTTP service, and live queue
+target form one consistency boundary.
+
+## Output Contract
+
+Report resolver precedence, auth/lock behavior, race-test result, live cap and
+restore evidence, changed files, and any startup fallback. Update this task and
+`plan.md` status in the same implementation conversation.
+
+## Results
+
+- RED: queue restore failed with `queue full` after the cap was lowered.
+- RED: resolver ignored valid, unlimited, malformed, and locking environment
+ values; the HTTP lock case returned `200` instead of `409`.
+- RED: startup resolution ignored persisted settings and returned negative
+ environment values unchanged.
+- RED: System Message Queue routes returned `404` before registration.
+- GREEN: `go test ./internal/system/queuesettings ./internal/system ./internal/orchestrator/messagequeue ./internal/backendapp -count=1`.
+- GREEN: `go test -race ./internal/system/queuesettings ./internal/orchestrator/messagequeue -count=1`.
+- Verified live lowering without pruning, all new-admission paths, unlimited
+ mode, restore/retry bypass, environment precedence/lock, persistence-before-
+ apply, and member/admin routing.
+- Final spec audit added a RED/GREEN regression for malformed JSON and negative
+ persisted values: GET now falls back to the default with a warning, and an
+ admin PATCH can replace the invalid record without restarting.
+- Final concurrency audit added a deterministic RED/GREEN regression for two
+ overlapping admin saves: persistence and live application now share one
+ serialized service boundary, so an older request cannot overwrite the live
+ value after a newer request has persisted.
diff --git a/docs/plans/message-queue-management/task-03-visible-queue-removal-ui.md b/docs/plans/message-queue-management/task-03-visible-queue-removal-ui.md
new file mode 100644
index 0000000000..8c1962df9a
--- /dev/null
+++ b/docs/plans/message-queue-management/task-03-visible-queue-removal-ui.md
@@ -0,0 +1,97 @@
+---
+id: "03-visible-queue-removal-ui"
+title: "Expose removal for every visible queue row"
+status: completed
+wave: 3
+depends_on: ["01-authoritative-queue-cancellation"]
+plan: "plan.md"
+spec: "../../specs/ui/message-queue-management.md"
+---
+
+# Task 03: Expose Removal for Every Visible Queue Row
+
+## Acceptance
+
+- User, agent, workflow, and server rows all render an individual Remove
+ action; only user rows render Edit.
+- Existing merge eligibility and sender-provenance rendering remain unchanged.
+- Clear-all and individual removal reconcile to authoritative queue status on
+ success, race, and error; localized feedback replaces stale optimistic state.
+- Visible positions compact to `#1` through `#N` after remove, merge, or drain,
+ even when durable FIFO position keys retain gaps.
+- On coarse-pointer/mobile viewports, queue actions are always visible and
+ expose at least 44px hit areas. Desktop keeps compact hover behavior.
+- The queue keeps one internal scroll region and never pushes the composer off
+ the phone viewport.
+
+## TDD Sequence
+
+1. Add component tests proving `canRemove` is independent from `canEdit` for
+ each provenance kind. Add hook tests for clear/remove success and refetch on
+ failure. Run RED.
+2. Split the row-action props and render Remove outside the edit-only branch.
+3. Update clear/remove reconciliation and localize all newly touched queue
+ labels, titles, and errors.
+4. Add coarse-pointer responsive classes and focused assertions for visible
+ 44px controls without changing the queue scroll owner.
+5. Run focused unit tests, typecheck, lint, and i18n checks GREEN.
+
+## Verification
+
+```bash
+cd apps
+pnpm --filter @kandev/web exec vitest run \
+ components/task/chat/queued-ghost-list.test.tsx \
+ components/task/chat/queued-ghost-message.test.tsx \
+ hooks/domains/session/use-queue.test.ts
+pnpm --filter @kandev/web run typecheck
+pnpm --filter @kandev/web run lint
+pnpm --filter @kandev/web run i18n:check
+pnpm --filter @kandev/web run i18n:ratchet
+```
+
+## Files Likely Touched
+
+- `apps/web/components/task/chat/queued-ghost-list.tsx`
+- `apps/web/components/task/chat/queued-ghost-list.test.tsx`
+- `apps/web/components/task/chat/queued-ghost-message.tsx`
+- `apps/web/components/task/chat/queued-ghost-message.test.tsx`
+- `apps/web/hooks/domains/session/use-queue.ts`
+- `apps/web/hooks/domains/session/use-queue.test.ts`
+- `apps/web/src/locales/en/chat.json`
+- generated pseudo-locale catalog
+
+## Dependencies
+
+Task 01 supplies the backend behavior this UI exposes.
+
+## Parallelism
+
+Parallel-safe with Task 04 after backend dependencies. They touch separate
+components, API domains, and translation namespaces. No subagent is authorized
+by this task file.
+
+## Output Contract
+
+Report RED/GREEN unit evidence, provenance matrix, mobile hit-area result,
+typecheck/lint/i18n status, changed files, and residual UX risks. Update this
+task and `plan.md` status in the same implementation conversation.
+
+## Results
+
+- RED: the focused component/hook suite failed 9 tests because removal was
+ coupled to editability, coarse-pointer hit-area classes were absent, and
+ clear/remove skipped authoritative reconciliation paths.
+- GREEN: 73 focused tests pass across `queued-ghost-list`,
+ `queued-ghost-message`, and `use-queue`.
+- User, agent, workflow, and server rows expose Remove; only `queued_by=user`
+ exposes Edit. Existing merge eligibility is unchanged.
+- Clear and Remove refetch authoritative status after success, drain races,
+ and failures; non-race failures restore state and surface localized errors.
+- Desktop row controls remain 24px hover actions. Coarse-pointer controls are
+ always visible with 44px hit areas; the existing queue list remains the one
+ panel scroll owner.
+- GREEN: web typecheck, lint, `i18n:check`, and `i18n:ratchet`.
+- Follow-up RED/GREEN: a sparse-position render failed with `#1, #3`; the
+ queue panel now derives display ordinals from rendered order and shows
+ `#1, #2` without mutating durable FIFO keys.
diff --git a/docs/plans/message-queue-management/task-04-message-queue-settings-ui.md b/docs/plans/message-queue-management/task-04-message-queue-settings-ui.md
new file mode 100644
index 0000000000..49e53dc181
--- /dev/null
+++ b/docs/plans/message-queue-management/task-04-message-queue-settings-ui.md
@@ -0,0 +1,105 @@
+---
+id: "04-message-queue-settings-ui"
+title: "Add the Message Queue General settings page"
+status: completed
+wave: 3
+depends_on: ["02-live-queue-capacity-settings"]
+plan: "plan.md"
+spec: "../../specs/ui/message-queue-management.md"
+---
+
+# Task 04: Add the Message Queue General Settings Page
+
+## Acceptance
+
+- `/settings/general/message-queue` is reachable through desktop and mobile
+ General navigation and has a translated breadcrumb/title. The former System
+ URL redirects to the canonical General route.
+- The page shows configured/effective values, source, unlimited explanation,
+ loading/error state, environment lock, and member read-only state.
+- Admin edits accept integers `>= 0`, register with the shared settings save
+ coordinator, persist through PATCH, and reconcile the returned effective
+ value. No local Save/Cancel button exists.
+- The page is single-column, uses the shared page scroll owner, has no
+ horizontal overflow, and keeps the numeric input at least 44px high.
+- API types, helpers, route registry, sidebar tests, and i18n catalogs cover the
+ new surface.
+
+## TDD Sequence
+
+1. Add API helper tests and component tests for default, unlimited, dirty save,
+ validation, environment lock, member read-only, failure, and discard. Run
+ RED.
+2. Add route/sidebar/breadcrumb tests for the new leaf and mobile Settings tree.
+ Run RED.
+3. Implement typed API helpers, settings component/page, route registration,
+ translated nav label, and save-contributor wiring.
+4. Apply mobile layout and input sizing from the shared System page language.
+5. Run focused tests, typecheck, lint, and i18n gates GREEN.
+
+## Verification
+
+```bash
+cd apps
+pnpm --filter @kandev/web exec vitest run \
+ components/settings/system/message-queue-settings.test.tsx \
+ components/app-sidebar/sections/settings/settings-tree-render.test.tsx \
+ components/settings/settings-layout-client.test.tsx \
+ lib/api/domains/settings-api.test.ts
+pnpm --filter @kandev/web run typecheck
+pnpm --filter @kandev/web run lint
+pnpm --filter @kandev/web run i18n:check
+pnpm --filter @kandev/web run i18n:ratchet
+```
+
+## Files Likely Touched
+
+- `apps/web/components/settings/system/message-queue-settings.tsx`
+- `apps/web/components/settings/system/message-queue-settings.test.tsx`
+- `apps/web/app/settings/general/message-queue/page.tsx`
+- `apps/web/app/settings/system/message-queue/page.tsx` (legacy redirect)
+- `apps/web/src/settings-routes.tsx`
+- `apps/web/components/app-sidebar/sections/settings/system-group.tsx`
+- `apps/web/components/app-sidebar/sections/settings/settings-tree-render.test.tsx`
+- `apps/web/components/settings/settings-layout-client.tsx`
+- `apps/web/components/settings/settings-layout-client.test.tsx`
+- `apps/web/lib/api/domains/settings-api.ts`
+- `apps/web/lib/api/domains/settings-api.test.ts`
+- `apps/web/lib/types/system.ts`
+- `apps/web/src/locales/en/system.json`
+- generated pseudo-locale catalog
+
+## Dependencies
+
+Task 02 supplies the typed HTTP contract and live behavior.
+
+## Parallelism
+
+Parallel-safe with Task 03 after backend dependencies. No subagent is
+authorized by this task file.
+
+## Output Contract
+
+Report RED/GREEN unit evidence, save-coordinator behavior, desktop/mobile route
+coverage, typecheck/lint/i18n status, changed files, and residual UX risks.
+Update this task and `plan.md` status in the same implementation conversation.
+
+## Results
+
+- RED: the five focused files reported one unresolved component suite and five
+ failures for missing API helpers, route, translated breadcrumb, and System
+ navigation leaf.
+- GREEN: 54 focused tests pass across the settings component, API, route,
+ sidebar tree, and layout.
+- The shared save contributor stages valid admin changes, PATCHes only on the
+ global Save changes action, reconciles the response, and restores the
+ authoritative configured value on discard. Invalid drafts block shared save.
+- Environment overrides show their effective value and lock explanation;
+ members receive the same read surface with a disabled field. Load and save
+ failures stay visible and retryable/actionable.
+- The same translated General leaf feeds desktop and mobile navigation. The
+ page uses the shared safe-area-aware scroll container, adds no nested page
+ scroller, and gives the numeric input a 44px height.
+- Follow-up RED/GREEN: navigation and route tests proved the leaf was still
+ under System; it now lives under General and the old URL redirects.
+- GREEN: web typecheck, lint, `i18n:check`, and `i18n:ratchet`.
diff --git a/docs/plans/message-queue-management/task-05-e2e-and-public-docs.md b/docs/plans/message-queue-management/task-05-e2e-and-public-docs.md
new file mode 100644
index 0000000000..7655db23d7
--- /dev/null
+++ b/docs/plans/message-queue-management/task-05-e2e-and-public-docs.md
@@ -0,0 +1,127 @@
+---
+id: "05-e2e-and-public-docs"
+title: "Prove end-to-end behavior and update public docs"
+status: completed
+wave: 4
+depends_on:
+ - "03-visible-queue-removal-ui"
+ - "04-message-queue-settings-ui"
+plan: "plan.md"
+spec: "../../specs/ui/message-queue-management.md"
+---
+
+# Task 05: Prove End-to-End Behavior and Update Public Docs
+
+## Acceptance
+
+- Desktop Playwright creates agent-origin queued messages through the mock MCP
+ path, removes one, clears the remainder, and proves capacity is available.
+- Mobile Playwright performs individual remove and clear-all through touch-sized
+ controls while the composer remains visible, the queue scrolls internally,
+ and displayed positions compact after a middle-row removal.
+- Desktop and mobile General Settings navigation reach Message Queue; an admin
+ change saves and reloads, while environment/member states remain read-only.
+- Any test changing the install-wide cap captures and restores its baseline in
+ teardown, including failure paths.
+- Public coordination, configuration, sessions/review, and operations docs
+ explain visible-origin removal, UI configuration, environment precedence,
+ `0` unlimited, live application, and non-pruning.
+
+## TDD Sequence
+
+1. Extend the cross-task message fixture to hold a target busy while multiple
+ sender tasks queue agent-origin rows. Add desktop remove/clear assertions and
+ run RED.
+2. Add a mobile management scenario based on the shipped queue-scroll helper;
+ assert coarse-pointer hit boxes, one scroll owner, and visible composer. Run
+ RED.
+3. Add settings navigation/save/reload scenarios with baseline restoration and
+ run RED.
+4. Complete any missing wiring discovered by E2E, rerun all focused scenarios,
+ and record GREEN.
+5. Update public docs and run both documentation validators.
+
+## Verification
+
+```bash
+cd apps/web
+pnpm e2e:run tests/chat/message-queue.spec.ts tests/chat/agent-message-attribution.spec.ts
+pnpm e2e:run --project mobile-chrome tests/chat/mobile-message-queue-management.spec.ts
+pnpm e2e:run tests/system/message-queue-settings.spec.ts
+pnpm e2e:run --project mobile-chrome tests/system/mobile-message-queue-settings.spec.ts
+cd ../../
+node --test scripts/validate-public-docs.test.mjs
+node scripts/validate-public-docs.mjs
+git diff --check
+```
+
+## Files Likely Touched
+
+- `apps/web/e2e/tests/chat/message-queue.spec.ts`
+- `apps/web/e2e/tests/chat/agent-message-attribution.spec.ts`
+- `apps/web/e2e/tests/chat/mobile-message-queue-management.spec.ts`
+- `apps/web/e2e/tests/system/message-queue-settings.spec.ts`
+- `apps/web/e2e/tests/system/mobile-message-queue-settings.spec.ts`
+- shared E2E API/page helpers only if needed
+- `docs/public/coordination.md`
+- `docs/public/configuration.md`
+- `docs/public/sessions-and-review.md`
+- `docs/public/operations.md`
+
+## Dependencies
+
+Tasks 03 and 04 must be complete so browser tests exercise production UI
+rather than mocked components.
+
+## Parallelism
+
+Sequential final integration task. It mutates shared install-wide E2E state and
+must own its setup/restore lifecycle.
+
+## Output Contract
+
+Report desktop/mobile scenario results, baseline restoration evidence, public
+docs validation, changed files, and any residual flake or release risk. Update
+this task, `plan.md`, and spec status when the feature is genuinely complete.
+
+## Results
+
+- GREEN: 19 desktop queue tests. The new mock-MCP scenario filled a busy
+ target with ten agent-origin rows from two sender tasks, removed one, cleared
+ the other nine, then admitted a fresh agent message.
+- GREEN: one mobile queue-management test. The queue retained its single
+ internal scroll owner and visible composer; row removal, clear-all, and close
+ controls measured at least 44 by 44 CSS pixels.
+- GREEN: three desktop settings tests and one mobile settings test. Desktop
+ navigation, admin save/reload, environment lock, and member read-only states
+ passed; mobile navigation and the shared single-scroll layout passed.
+- The settings suite captures the install-wide baseline with GET before every
+ test and restores it with PATCH in `afterEach`, so restoration also runs on a
+ failed assertion.
+- GREEN: 58 public-doc validator tests, 41 published pages validated, and
+ `git diff --check`.
+- GREEN: web lint, typecheck, `i18n:check`, and `i18n:ratchet` after the E2E and
+ documentation changes.
+- No additional production wiring gap appeared in the final browser wave. A
+ subsequent spec audit found and fixed invalid persisted-setting replacement
+ and overlapping-save ordering; the focused race-enabled backend suite is
+ green. The browser tests use the shipped mock agent; repository races remain
+ covered by Tasks 01 and 02.
+- Follow-up RED: mobile middle-row removal displayed `#6` in the fifth visible
+ slot, while desktop/mobile settings tests could not find Message Queue under
+ General.
+- Follow-up GREEN: three desktop General settings tests and two mobile
+ queue/settings tests pass with retries disabled. Middle-row removal compacts
+ the final visible label to `#9`; desktop and mobile navigation use General.
+- Follow-up GREEN: 113 focused frontend tests, typecheck, lint, both i18n gates,
+ 58 public-doc tests, the 41-page docs validator, and `git diff --check` pass.
+- The retained isolated evaluation target has four agent-origin rows with
+ durable positions `4–7`; the live feature UI displays compact positions
+ `#1–#4`. General navigation, the legacy redirect, and browser console are
+ clean.
+- PR fixup GREEN: synced current `main`; preserved raw SQLite metadata guards;
+ made ordinary accepted-message retries bypass a lowered cap; corrected the
+ General route, redirect coverage, drain compaction, and environment
+ precedence docs; and scoped strict Playwright locators. Focused Go tests, 40
+ frontend unit tests, typecheck, both i18n gates, 58 public-doc tests, the
+ exact desktop queue scenario, and both mobile queue/settings scenarios pass.
diff --git a/docs/public/configuration.md b/docs/public/configuration.md
index 063c6a9ec7..4f2cc20f91 100644
--- a/docs/public/configuration.md
+++ b/docs/public/configuration.md
@@ -333,13 +333,13 @@ Profile environment variables are eventually injected into agent subprocesses or
## Advanced operator tuning
-These startup-only variables are supported by specific runtime components but are not YAML fields. Leave them at defaults unless diagnosing a measured problem.
+These variables are supported by specific runtime components but are not YAML fields. Most are startup-only; the message queue limit also has a live database-backed setting described below. Leave them at defaults unless diagnosing a measured problem.
| Variable | Default | Parsing and effect |
|---|---:|---|
| `KANDEV_GH_MAX_CONCURRENT` | `8` | Positive integer process-wide cap for `gh` subprocesses; invalid/non-positive uses default. |
| `KANDEV_GIT_MAX_CONCURRENT` | `12` | Positive integer process-wide cap for `git` subprocesses; invalid/non-positive uses default. |
-| `KANDEV_QUEUE_MAX_PER_SESSION` | `10` | Queued user messages per session. Invalid uses default; zero/negative disables the cap. |
+| `KANDEV_QUEUE_MAX_PER_SESSION` | `10` | Pending messages per session. A valid value overrides and locks the saved UI setting; zero/negative means unlimited, while invalid falls through to the saved setting or default. |
| `KANDEV_ACP_IDLE_TIMEOUT` | `1h` | Go duration after which idle agentctl instances are reaped; `0` disables. Invalid uses default. |
| `KANDEV_ACP_IDLE_REAPER_INTERVAL` | `1m` | Go duration between idle scans. Intended mainly for testing; use the default in production. |
| `KANDEV_ACP_NOTIF_QUEUE` | `131072` | Per-connection ACP inbound notification capacity; positive values clamp to `1024`-`131072`, invalid uses default. |
@@ -348,7 +348,7 @@ These startup-only variables are supported by specific runtime components but ar
| `KANDEV_MCP_LOG_FILE` | unset | File path for per-agentctl MCP debug logs. Logs tool names, arguments, session IDs, results for tool errors, and timings; invalid paths warn and disable this sink. |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | unset | Enables OTLP/HTTP tracing for backend and agentctl spans; unset uses a no-op tracer. See the transport warning below. |
-Changing concurrency/queue values trades memory and process pressure against throughput. Values are captured during process/component initialization and need a restart.
+Changing concurrency values trades process pressure against throughput and requires a restart. The queue environment value is also read at startup. Without that environment override, an admin can use **Settings > General > Message Queue** to save an install-wide limit that applies live. `0` means unlimited. Lowering the live limit does not prune existing rows; new admissions remain blocked until the pending count drops below the limit, while retries of already accepted work remain eligible.
The current OTLP exporter strips an `http://` or `https://` prefix from the configured endpoint and always uses `WithInsecure()`. Treat this as implementation-bound cleartext transport: send it only to a trusted private collector over a protected network, not directly across an untrusted network. The service name is `kandev-agentctl`, and spans can include task/session/execution IDs plus raw agent-event JSON truncated to 8192 characters. That payload can contain prompts, files, and tool data. Use collector-side access controls and retention accordingly.
diff --git a/docs/public/coordination.md b/docs/public/coordination.md
index ad5fa689b6..2b8a17a7b6 100644
--- a/docs/public/coordination.md
+++ b/docs/public/coordination.md
@@ -120,7 +120,9 @@ For predictable top-level creation, pass `repository_url`, `repository_id`, or `
| Created but not started | Starts the session with the message. |
| Failed or cancelled | Returns an error. |
-The default delivery mode is queued. Each session accepts 10 queued messages by default; operators can change it with `KANDEV_QUEUE_MAX_PER_SESSION`, and a value of `0` or less disables the limit. Only one queued message drains per agent turn. When the cap is reached, the sender receives a structured `queue_full` error and should retry after a target turn completes.
+The default delivery mode is queued. Each session accepts 10 queued messages by default. An admin can change the install-wide limit under **Settings > General > Message Queue**; `0` means unlimited. The saved value applies immediately to new admissions without removing messages already waiting. `KANDEV_QUEUE_MAX_PER_SESSION` has higher precedence, locks the UI field, and requires a restart when changed; zero or a negative value means unlimited. Only one queued message drains per agent turn. When the cap is reached, the sender receives a structured `queue_full` error and should retry after space becomes available.
+
+In the task workbench, expand the queue chip to manage pending messages. Every visible pending row has **Remove**, whether it came from a user, another agent, workflow automation, or a server action. **Clear all** removes all visible pending rows in that session and releases their capacity. After removal, merge, or drain, displayed positions compact to `#1` through `#N` while FIFO order stays unchanged. Provenance still matters for editing and merging: only user-origin content can be edited. A row already reserved for delivery is hidden from the panel and cannot be cancelled there.
Choose the control by intent:
diff --git a/docs/public/coverage.json b/docs/public/coverage.json
index 207437fb27..ab6884a796 100644
--- a/docs/public/coverage.json
+++ b/docs/public/coverage.json
@@ -270,21 +270,26 @@
"docs": ["operations", "configuration", "feature-status"],
"sources": [
"apps/backend/internal/system/settings/store.go",
+ "apps/backend/internal/system/queuesettings/service.go",
"apps/backend/internal/system/storage/handler.go",
"apps/backend/internal/system/logbundle/service.go",
"apps/backend/internal/mcp/handlers/diagnostic_bundle.go",
"apps/web/components/settings/system/feature-toggles-settings.tsx",
+ "apps/web/components/settings/system/message-queue-settings.tsx",
"apps/web/components/settings/system/storage/storage-maintenance-settings.tsx"
],
"tests": [
"apps/backend/internal/system/settings/store_test.go",
+ "apps/backend/internal/system/queuesettings/queuesettings_test.go",
"apps/backend/internal/system/storage/handler_test.go",
"apps/backend/internal/system/logbundle/service_test.go",
"apps/backend/internal/mcp/handlers/diagnostic_bundle_test.go",
"apps/web/components/settings/system/feature-toggles-settings.test.tsx",
+ "apps/web/components/settings/system/message-queue-settings.test.tsx",
"apps/web/components/settings/system/storage/storage-maintenance-settings.test.tsx"
],
"settingsRoutes": [
+ "/settings/general/message-queue",
"/settings/system/about",
"/settings/system/backups",
"/settings/system/database",
@@ -355,7 +360,8 @@
{ "route": "/settings/general/resource-metrics", "reason": "Legacy redirect to Appearance." },
{ "route": "/settings/general/shell", "reason": "Legacy redirect to Terminal." },
{ "route": "/settings/plugins", "reason": "Experimental feature intentionally omitted until its public contract stabilizes." },
- { "route": "/settings/system", "reason": "Redirect to System status." }
+ { "route": "/settings/system", "reason": "Redirect to System status." },
+ { "route": "/settings/system/message-queue", "reason": "Legacy redirect to General Message Queue." }
],
"mcpTools": []
}
diff --git a/docs/public/operations.md b/docs/public/operations.md
index e9ec219702..263039bd02 100644
--- a/docs/public/operations.md
+++ b/docs/public/operations.md
@@ -60,6 +60,14 @@ kandev service logs -f
Add `--system` to both commands for a system service.
+## Message queue capacity
+
+Open **Settings > General > Message Queue** to set the install-wide number of pending messages allowed in each session. The default is `10`; `0` means unlimited. Admin saves apply immediately to later admissions. Lowering the limit does not prune rows already waiting, so a queue at or above the new limit rejects new work until messages run or are removed. Delivery retries for work accepted before the change are not discarded by the lower cap.
+
+`KANDEV_QUEUE_MAX_PER_SESSION` has higher precedence than the saved setting. A valid environment value makes the UI field read-only; zero or a negative value means unlimited. Invalid text is logged and ignored in favor of the saved setting or default. Environment changes require a backend restart, while UI changes do not.
+
+To recover capacity in one session, expand its queue chip in the task workbench. **Remove** deletes one visible pending row and **Clear all** deletes all visible pending rows, including user-, agent-, workflow-, and server-origin work. After removal, merge, or drain, displayed positions immediately compact to `#1` through `#N`; durable FIFO ordering is unchanged. A row already reserved for delivery is hidden and is not cancelled by either action.
+
## State and storage
`KANDEV_HOME_DIR` relocates the Kandev home. Its default is `~/.kandev`; the derived data directory is always `/data`.
diff --git a/docs/public/sessions-and-review.md b/docs/public/sessions-and-review.md
index 6eb7fc1ff8..c9b2113313 100644
--- a/docs/public/sessions-and-review.md
+++ b/docs/public/sessions-and-review.md
@@ -63,6 +63,8 @@ Stopping is not deletion. Resume succeeds only while the executor still has the
Stopping a turn does not itself run the next queued message. Expand the queue and select **Run next** when you want processing to continue.
+The expanded queue also lets you discard stale work. **Remove** is available for every visible pending row, including messages from users, peer agents, workflows, and server actions; **Clear all** removes all visible pending rows in that session. Only user-origin rows remain editable. A message already reserved for delivery is hidden from the queue and cannot be cancelled with these controls.
+
A CLI-passthrough profile displays the agent's native terminal interface in a PTY. It still belongs to the task, but it does not provide Kandev's structured chat messages and tool-call presentation.
@@ -83,7 +85,7 @@ Delivery follows the target state:
- a created session starts with the message as its first prompt;
- a failed or cancelled session rejects the message.
-The default pending-message limit is 10 per session. Operators can change it with `KANDEV_QUEUE_MAX_PER_SESSION`; a value of `0` or less removes the cap, so the queue can grow without that bound. Interrupt delivery is restricted to a direct parent task messaging its child. Other senders must queue.
+The default pending-message limit is 10 per session. An admin can change it live under **Settings > General > Message Queue**; `0` removes the cap. A valid `KANDEV_QUEUE_MAX_PER_SESSION` value takes precedence and makes the UI field read-only; changing the environment still requires a restart. Malformed environment values are logged and ignored, so the saved setting or default applies instead. Lowering the saved limit does not delete entries already waiting. Interrupt delivery is restricted to a direct parent task messaging its child. Other senders must queue.
For urgent replacement work, the parent should use `message_task_kandev` with `delivery_mode: "interrupt"`; this cancels the current approach and immediately tries to dispatch the new prompt, with a safe queued fallback. Use `stop_task_kandev` only for halt-only intent. A successful stop marks every accepted live child session `CANCELLED` and schedules graceful teardown asynchronously. Kandev then attempts to move an eligible unarchived, non-Office task from `IN_PROGRESS` or `SCHEDULING` to `REVIEW`; other task states remain unchanged. A child with no live execution returns idempotent `not_running`, and its worktrees, environment, commits, task record, descendants, and queued messages are preserved. See [Coordination](coordination.md) for the complete authority and lifecycle contract.
@@ -274,7 +276,7 @@ Before moving a task to done:
- **New Agent has no profiles:** create a profile compatible with the task executor. A profile for another executor is intentionally hidden.
- **Summary or generated text fails:** configure the corresponding utility agent and a reachable model in **Settings > Utility Agents**.
- **Resume fails:** start fresh when the executor no longer has resumable session state, then supply a summary or copy the relevant context.
-- **A peer message never arrives:** check the target session state and ID. Running sessions queue messages; failed or cancelled sessions reject them; a full queue must drain before another message can be accepted.
+- **A peer message never arrives:** check the target session state and ID. Running sessions queue messages; failed or cancelled sessions reject them. For a full queue, expand its chip and run, remove, or clear pending work before retrying; an admin can also review the install-wide limit under **Settings > General > Message Queue**.
- **Changes is empty:** select the correct repository and comparison, then confirm the agent wrote inside the materialized task path.
- **Review marks became stale:** the underlying diff changed. Re-review the new hash before marking the file complete.
- **Walkthrough does not appear:** confirm an active task-MCP session exists and that the saved `changes-walkthrough` prompt was not removed or made invalid.
diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md
index 07a175991b..7a4ded93ee 100644
--- a/docs/specs/INDEX.md
+++ b/docs/specs/INDEX.md
@@ -147,6 +147,7 @@ Subscription quota tracking and per-agent cheap-model profile routing.
| [entity-reference-composer](ui/entity-reference-composer.md) | draft |
| [agent-launch-prompt-composer](ui/agent-launch-prompt-composer.md) | shipped |
| [mermaid-rendering](ui/mermaid-rendering.md) | shipped |
+| [message-queue-management](ui/message-queue-management.md) | shipped |
| [message-queue-merge](ui/message-queue-merge.md) | shipped |
| [settings-manual-save](ui/settings-manual-save.md) | shipped |
| [executor-settings-card-spacing](ui/executor-settings-card-spacing.md) | shipped |
diff --git a/docs/specs/system-page/spec.md b/docs/specs/system-page/spec.md
index 913fa7a7c6..162223a9b9 100644
--- a/docs/specs/system-page/spec.md
+++ b/docs/specs/system-page/spec.md
@@ -109,6 +109,8 @@ GET /api/v1/system/updates - { current, latest, latest_ur
POST /api/v1/system/updates/check - force selected-source re-poll; rate-limited 30s
PATCH /api/v1/system/updates/channel - persist Stable/Nightly for a verified managed npm/npx user service; body { channel }
POST /api/v1/system/updates/apply - queue service-only self-update; body { confirm: "UPDATE", target_version }
+GET /api/v1/system/message-queue/settings - configured/effective queue limit and source
+PATCH /api/v1/system/message-queue/settings - save and live-apply admin queue limit
```
The System API has no global casing convention. Updates and Info use the snake_case JSON fields
@@ -198,6 +200,10 @@ The page reads the JSON statically; no backend endpoint is needed.
- **GIVEN** the user clicks **Check now** twice within 30 seconds, **WHEN** the second click fires, **THEN** the endpoint returns `429 Too Many Requests` and the UI shows "Already checked, try again in s".
- **GIVEN** retained backend logs and a connected browser with local console history, **WHEN** the user downloads diagnostics from `/settings/system/logs`, **THEN** one ZIP contains separate backend/frontend directories and a manifest describing both captures.
- **GIVEN** the user opens `/settings/system/licenses` while offline, **WHEN** the page renders, **THEN** every dependency's license text is available locally (no network calls).
+- **GIVEN** no queue-limit environment variable is set, **WHEN** an admin saves
+ a new limit on `/settings/general/message-queue`, **THEN** later queue
+ admissions use it immediately without deleting existing messages or
+ requiring a restart.
## Data model
@@ -226,6 +232,7 @@ guard covers database vacuum/optimize/reset, manual update checks, update-channe
update apply. Backup create/restore/delete and disk-usage
refresh currently remain on the base authenticated group. Reset and restore additionally validate
their confirmation bodies server-side as defence in depth.
+The Message Queue settings GET is readable by members, while its PATCH is admin-only.
## Failure modes
diff --git a/docs/specs/ui/message-queue-management.md b/docs/specs/ui/message-queue-management.md
new file mode 100644
index 0000000000..adbb1ab489
--- /dev/null
+++ b/docs/specs/ui/message-queue-management.md
@@ -0,0 +1,222 @@
+---
+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).
+- 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 > General > Message Queue** exposes the maximum number of persisted
+ messages allowed per session.
+- 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 },
+ "effective": {
+ "max_per_session": 10,
+ "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 rejects negative values with `400`, rejects a valid environment lock
+with `409`, 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 }
+```
+
+`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
+ General 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.
+- Cancelling a prompt after it has entered in-flight delivery or reached the
+ executor.
+- Per-workspace, per-task, or per-session capacity overrides.
+- Automatically pruning old messages when a lower limit is saved.
+- Queue reordering or bulk selection beyond **Clear all**.
diff --git a/docs/specs/ui/message-queue-merge.md b/docs/specs/ui/message-queue-merge.md
index 2a406cfd06..670d93f035 100644
--- a/docs/specs/ui/message-queue-merge.md
+++ b/docs/specs/ui/message-queue-merge.md
@@ -6,6 +6,10 @@ owner: kandev
# Merge Enqueued Messages Individually
+Queue-wide removal and capacity behavior is defined separately by
+[Manage Pending Message Queues](message-queue-management.md). This spec governs
+only the merge operation.
+
## Why
When a session is busy, users queue follow-up prompts that drain one-per-turn. A
@@ -118,9 +122,10 @@ No schema change. The merge reuses the existing `queued_messages` table:
one-way prompts from a single inter-task agent preserves the reserved row's
provenance (the merged entry keeps the target's identity and the source's
`sender_task_id`-matched lineage) while reducing deliveries. The gate is
- strict — identical non-empty `sender_task_id`, never caller-tunable — so a
- client still cannot move, delete, or reorder agent-owned rows outside that
- exact fold.
+ strict — identical non-empty `sender_task_id`, never caller-tunable — so the
+ merge operation cannot move, reorder, or combine unrelated agent-owned rows.
+ Authorized pending-row deletion is a separate operation governed by
+ [Manage Pending Message Queues](message-queue-management.md).
- Workflow and system messages are never a merge source or target.
- The WS handler rejects client-supplied reserved `user_id` values, exactly like
the update handler.