fix: preserve queued session ownership and visibility (#3755)

This commit is contained in:
Carlos Florêncio
2026-09-17 15:59:28 +01:00
committed by GitHub
parent c277822ac1
commit c5dbb45502
115 changed files with 8510 additions and 276 deletions
@@ -0,0 +1,54 @@
# ADR-2026-09-16-passive-session-inspection: Keep inspection separate from execution ownership
**Status:** proposed
**Date:** 2026-09-16
**Area:** workflow
## Context
Workflow parking and process loss both appear as resumable sessions without a
running agent. Treating these cases alike lets opening an old conversation wake
it. The same resume request currently receives manual ceiling privileges.
A sibling ready event can then reconcile a queued task to Review.
## Decision
Passive inspection carries explicit intent across the client/server boundary.
The server verifies workflow parking and queued ownership before launch side
effects. Workflow parking is durable policy state, separate from execution-stamped
stop-event suppression and from the background-work projection.
The exact workflow entry and destination own the deferred launch. The visible
conversation does not own it. Task reconciliation considers valid deferred work
as well as running sessions. Queue UI consumes a bounded task-status projection,
not transcript text or a second client queue.
Explicit user execution remains possible and retains current manual admission.
It does not transfer workflow ownership from the queued recipient. A valid
workflow re-entry can release parking for its selected recipient.
## Consequences
The implementation adds a durable parking marker and explicit inspection
semantics. Old tasks need conservative recognition of existing park provenance.
The browser must propagate inspection intent through recovery fallbacks.
Existing manual callers remain compatible; this is not a new authorization grant.
Counts in queue details are observations with freshness, not queue positions.
Task and summary revisions must prevent a delayed update from undoing a clear.
## Alternatives considered
- Disable all automatic recovery: changes unrelated recovery workflows and the
existing user preference. Suppress only ineligible passive execution.
- Mark predecessors Completed: loses the deliberate nonterminal park/reuse behavior.
- Treat any stop-intent tombstone as permanent parking: a tombstone must survive
legitimate reuse to suppress delayed callbacks, so it cannot own current policy.
- Add a hard limit to every manual launch: changes the existing admission contract.
- Repair only the sidebar spinner: leaves unintended execution and wrong task state intact.
## Related records
- [Requirements](../specs/tasks/requirements/queued-session-ownership.md)
- [Design](../specs/tasks/system-design/queued-session-ownership.md)
- [Workflow lifecycle decision](2026-08-31-workflow-profile-session-switch-policy.md)
+288
View File
@@ -0,0 +1,288 @@
---
created: 2026-09-16
status: in_progress
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003
system_design:
- ../../specs/tasks/system-design/queued-session-ownership.md
legacy_specs: []
---
# Implementation plan: Queued session ownership
## Overview
Keep Luna queued when a workflow enters Implement, keep the parked Astra
conversation untouched during inspection, and make waiting visible on desktop
and phone. Deliver three sequential outcomes: inspection safety, queue lifecycle
correctness, then a complete queue projection and UI.
The task system owns this package because the deferred workflow entry owns its
recipient and state. Agent capacity and workflow WIP policy remain separate.
Read the [requirements](../../specs/tasks/requirements/queued-session-ownership.md),
[design](../../specs/tasks/system-design/queued-session-ownership.md), and
[decision](../../decisions/2026-09-16-passive-session-inspection.md).
## Evidence and conformance
Read-only investigation of task `ef15115f-12b9-4f8e-ba9f-bc0cd7e01bd8`, using
stored conversations, session lists, and backend build `1293d65a8e` (dirty):
| Lisbon time, 2026-09-16 | Observed event |
| --- | --- |
| 21:15:44 | Manual move to Implement selects new primary Luna session `c7451c7d-8468-4b45-8c67-ef6427390c01`; task becomes SCHEDULING. |
| 21:15:48 | Astra `8bc0ec46-6aef-43a5-b8f2-56c64cd680d0` is parked; Luna automatic launch is deferred at 5/5. |
| 21:19:02 | Task open/focus and status checks precede Astra resume by about 135 ms. Resume is classified manual and admitted at 8/5. |
| 21:19:06 | Astra boot-ready changes it to WAITING_FOR_INPUT, then changes the task from SCHEDULING to REVIEW. Luna remains CREATED. |
| Through 21:23:00 | The scheduler continues retrying Luna every 20 seconds; its accepted queue entry survives. |
The backend evidence does not prove a scheduler prompt to Astra. It shows a
resume through the shared launch path. Timing and frontend source support
open-time resumption; the exact user click and empty-output warning event remain
unproven. Do not encode either as an established cause.
The original diagnostic archive is task-local, not a required implementation
input: `.kandev/diagnostics/c79c09e35c0b6b608246d9280e0d220f.zip`. Its manifest
reports truncation of older logs; the incident window was present.
Confirmed defects are `buildResumeRequest` sharing manual admission with passive
recovery, and `writeTaskReviewState` ignoring a CREATED deferred destination.
Existing lifecycle AC-TASKS-WORKFLOW-PROFILE-SESSIONS-001.4 defines parking but
does not explicitly exclude inspection. The new requirement pair defines that
gap and queue visibility. Manual overrides remain allowed by
AC-AGENTS-SESSION-CEILING-001.2.
## Scope
### In scope
- Durable workflow parking, explicit passive intent, guarded status/ensure/resume.
- Exact queued recipient and entry identity; state reconciliation and replay races.
- Revisioned queue details in sidebar, task details, and phone navigation.
- Regression tests, restart evidence, localization, and user documentation.
### Out of scope
- Hard limits for explicit manual launches, new limits/settings, queue ordering or ETA.
- A global queue page, Office scheduling, provider policy, or new session states.
- Changes to source/destination profile selection or workflow WIP admission.
- Rewriting historical transcript warnings or assuming the unexplained warning's cause.
## Technical approach
Task 01 adds parking metadata and passive-source handling end to end, including
status/launch/ensure eligibility and the browser recovery hook. Task 02 binds
workflow deferrals to their entry and guards state and clear operations. Task 03
extends `task/statussummary` and delivers all queue surfaces together.
No new SQL column or scheduler is planned. Conditional operations on existing
metadata and task state must be tested through both supported database contracts.
Never clear callback tombstones to implement unpark. Never use background-work
parking as workflow parking. Existing old records need conservative migration
or passive suppression, not guessed ownership.
## ASCII UI preview
### UI-01: Desktop task row and detail, queued destination
Entry: move to Implement, then reopen the task or select Astra.
```text
Before (observed) After
Review Scheduling
Investigate issue [clock] Investigate issue Queued
+-------------------------------------------------------------------+
| Investigate issue Implement |
| Queued: Luna. Waiting for session capacity |
| 5 of 5 in use. Checked just now. Queued since 21:15 |
| Starts automatically when capacity is available. |
| [Astra] [Luna: Queued] [Plan] |
| Parked for workflow. Send a message to continue this conversation. |
| Astra conversation ... |
+-------------------------------------------------------------------+
```
The task queue region is outside conversation scroll and remains when Astra is
selected. The parked note is conversation-specific. Selecting Luna removes
the parked note but keeps queue status; it does not show an unsolicited Start
or Resume prompt. Copy and spacing are illustrative; placement and identity are required.
### UI-02: Phone task navigation and detail
```text
Task navigator (existing bottom drawer)
+------------------------------------+
| Tasks Close|
| [clock] Investigate issue Queued | <- scrolling task list
+------------------------------------+
Task detail (direct navigation)
+------------------------------------+
| Investigate issue Implement |
| Queued: Luna |
| Waiting for session capacity |
| 5 of 5. Checked just now. |
| Queued since 21:15 |
| Starts automatically. |
| [Astra v] | <- existing session picker
| Parked for workflow. |
| Send a message to continue here. |
| Conversation ... | <- single chat scroll owner
| Composer |
| Chat | Plan | Changes | More | <- existing safe-area nav
+------------------------------------+
```
Reuse `SessionTaskSwitcherSheet`, `TaskSwitcherDrawer`, `MobileSessionsPicker`,
and the dedicated mobile layout. No stacked desktop panes or extra disclosure
drawer. Required queue text is visible without hover; existing navigation owns
touch/focus behavior. New interactive targets, if needed, are at least 44 px on
phones/coarse pointers; ordinary desktop sizing remains unchanged.
### UI-03: Freshness and settlement, both viewports
```text
Loading: Queue status loading... (do not offer a duplicate launch)
Disconnected: Queued: Luna. Reconnecting. Capacity last checked 21:16.
Read failure: Queued: Luna. Capacity unavailable. Automatic retry pending.
Replay error: Queued: Luna. Launch retry pending. [existing error details]
Ambiguous: Queued launch needs recovery. [existing recovery surface]
Dispatched: Queue region removed; ordinary Luna Starting/Running status.
Dropped: Queue region removed; existing disposition/error remains visible.
No deferral: No queue region. Ordinary task behavior.
```
UI-01 through UI-03 cover requirement 003. Counts are timestamped observations,
not an ordinal queue position. Pending questions and real failures remain visible.
Desktop and phone share the view model, while phone uses stacked text. Status
changes use accessible text; frequent population updates are not live announcements.
## Tests
The following are planned tests, not tests run during package creation. AC suffixes
refer to `AC-TASKS-QUEUED-SESSION-OWNERSHIP-`.
| Work order | Test file and planned cases | Criteria |
| --- | --- | --- |
| 01 | `orchestrator/queued_session_inspection_test.go`: `TestQueuedSessionInspection` (parked/queued, free/full, explicit, unknown); `TestWorkflowParking` (no runtime, consumed tombstone, newer stamp, re-entry) | 001.1-6 |
| 01 | `task/repository/sqlite/workflow_parking_test.go`: `TestWorkflowParking` (conditional writes and restart); backend service/turn and frontend recovery tests | 001.1, 001.4, 001.6; 003.7 |
| 02 | `orchestrator/queued_session_ownership_test.go`: `TestQueuedSessionOwnership` (sibling boot/idle/failure, explicit sibling run); `TestQueuedEntryReplay` (exact-once, restart, deleted/terminal/moved, newer-record CAS races) | 002.1-6 |
| 03 | `task/statussummary/projector_launch_queue_test.go`, `rebuild_launch_queue_test.go`; task service/DTO tests; `task-status-summary` handler and queue view-model tests | 003.1-5 |
| 03 | Desktop/mobile rendered specs below | 001.1-3; 002.1-4; 003.1-7 |
Each work order must record behavioral RED before correction and all exact GREEN
commands. Existing test scaffolds are not evidence of this defect until exercised.
## E2E tests
Create `apps/web/e2e/tests/workflow/queued-session-ownership.spec.ts` (`chromium`)
and `mobile-queued-session-ownership.spec.ts` (`mobile-chrome`) with shared
`queued-session-ownership-helpers.ts`. Extend the desktop file from Task 01
through Task 03 rather than duplicating the same lifecycle fixture.
Use the isolated worker backend's `useEnv` for a ceiling of 1, with restoration
in `finally`. Start and settle a source session, occupy capacity using a separate
controllable mock task, then move through the topbar into a different profile's
auto-start step. Assert Luna exists and is queued before inspection. Capture
transport before opening the task, selecting Astra, reloading, and reconnecting.
Prove no Astra boot/prompt/queued-message additions and unchanged primary ownership.
Do not use helpers that click recovery or reload as part of an idle wait.
Release only the fixture's capacity holder. Observe Luna's one prompt delivery,
queue removal, and untouched Astra. Add explicit Astra follow-up as a separate
case; it must leave Luna's queue intact. Use real service/repository tests for
deterministic restart and simultaneous CAS races. E2E restart supplements them
by checking visible restoration without manually reconstructing queue state.
Phone tests enter through the task drawer, choose each session, inspect queue
details, and observe automatic dispatch. Assert no document horizontal overflow,
visible queue text at 393 px, long-name wrapping, and existing picker target
geometry. A genuine empty prompt retains its warning in unit tests; queue waiting
and passive navigation produce none in the rendered scenario.
## Work orders
- [in_progress] [Task 01: Preserve parked sessions during inspection](task-01-preserve-parked-sessions.md)
- [in_progress] [Task 02: Preserve queued entry lifecycle](task-02-preserve-queued-entry.md)
- [in_progress] [Task 03: Expose queue status across task surfaces](task-03-expose-queue-status.md)
Implementation is complete for all three sequential slices. The work orders
remain in progress because the required PostgreSQL checks need
`KANDEV_TEST_POSTGRES_DSN`, which is not configured in this workspace. No
subagents are authorized by this plan.
## Related delivery records
[Workflow lifecycle](../workflow-profile-session-reuse/plan.md),
[explicit targeting](../workflow-session-targeting/plan.md),
[same-profile fresh sessions](../workflow-same-profile-new-session/plan.md), and
[session ceiling](../session-concurrency-ceiling/plan.md) are compatibility inputs.
Their results are historical and do not prove this intersection. This package
owns the new cases; it does not reopen completed work or change lifecycle defaults.
## Documentation impact
During Task 03, update `docs/public/tasks-and-workflows.md` (explanation) and
`docs/public/agents-and-profiles.md` only where they describe inspection/recovery.
Explain the distinction between WIP waiting and session-capacity waiting.
No public page is changed during this design turn. Preserve existing overview
and configuration defaults. Check root README/screenshot references for affected wording.
## Verification results
Design-package validation on 2026-09-16:
- `python3 scripts/list-docs.py validate`: passed (282 decisions, 971 specifications).
- `python3 scripts/lint-spec-files.test.py`: passed (36 tests).
- `python3 scripts/lint-spec-files.py --all`: passed.
- Catalog queries for `queued-session-ownership` and `passive-session-inspection`:
discovered the new requirement, design, and decision.
- Work-order reference check: all three files reference existing requirement/AC
IDs and system-design paths, with pending status and verification sections.
- `git diff --check`: passed.
- `git status --short -- docs/plans/queued-session-ownership`: confirmed the new
untracked package; files are ready for review and have not been committed.
Implementation and available rendered verification on 2026-09-17:
- Task 01 inspection and recovery coverage passed with
`go test -race ./internal/orchestrator -run
'TestQueuedSessionInspection|TestWorkflowParking|Test.*Resume|Test.*ProfileSwitch'
-count=1`, the repository parking test, the focused frontend recovery tests,
typecheck, the backend build, and the desktop queued-session scenario.
- Task 02 queue lifecycle coverage passed with the race-focused orchestrator
suite, SQLite deferred-entry and service suites, the created-session Send Now
test, the desktop queued-session and workflow-targeting scenarios, and the
stale-successor compare-and-set test.
- Task 03 projection and UI coverage passed with statussummary/DTO/service
tests, the race-focused launch-queue suite, 93 focused frontend tests,
frontend typecheck and lint, the desktop and mobile scenarios, and the
localized queue view-model/component tests.
- `make -C apps/backend build` passed. `pnpm run i18n:check`,
`pnpm run i18n:ratchet`, public-doc validation, specification validation,
and `git diff --check` passed.
- `cd apps/web && pnpm test` passed (2,140 files, 18,465 tests, 4 skipped).
- `make -C apps/backend test` exercised all backend packages and passed the
task-related packages, but the repository-wide command exits nonzero on
unrelated environment-sensitive existing tests in process probing, home
configuration discovery, launcher configuration/restart, and the Office
priority migration fixture.
- The scoped E2E sleep lint preview and sleep ratchet passed. The full
`lint:e2e-sleeps` audit still reports the checkout's unrelated baseline
violations, so it is not used as a clean repository gate.
- The required PostgreSQL checks were not run because
`KANDEV_TEST_POSTGRES_DSN` is unset. The PostgreSQL parking contract test is
present and skips without that environment value. Until an isolated DSN is
supplied, Tasks 01 through 03 remain in progress.
## Risks
- Callback tombstones outlive actual parking; using them alone blocks valid reuse.
- A stale REVIEW writer or replay clear can overwrite a newer accepted entry.
- Passive-source omission in a fallback can still give opening manual privileges.
- Projection refreshes can accidentally count as user activity and reorder tasks.
- Legacy ambiguous ownership must surface recovery without dispatch or data loss.
- The unexplained warning must not cause blanket suppression of genuine empty turns.
@@ -0,0 +1,145 @@
---
id: "01-preserve-parked-sessions"
title: "Preserve parked sessions during inspection"
status: in_progress
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003
acceptance_criteria:
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.1
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.2
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.3
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.4
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.5
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.6
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.7
system_design:
- ../../specs/tasks/system-design/queued-session-ownership.md
---
# Task 01: Preserve parked sessions during inspection
## Summary
Make workflow parking durable and keep passive inspection separate from explicit
execution. Deliver the backend guard and browser caller changes together so
opening the queued task cannot wake Astra or create another launch.
## In scope
- Implement the design's stamped `workflow_parking` metadata, no-runtime case,
conservative legacy resolution, and conditional clear on legitimate activation.
- Add inspection source and status eligibility; guard launch and ensure before
side effects. Propagate no-execution outcomes through browser fallbacks.
- Preserve actual manual overrides, direct conversation follow-ups, workflow
re-entry, preference semantics for ordinary sessions, and existing permissions.
- Add backend/repository RED tests, hook tests, and the first desktop lifecycle
scenario in `queued-session-ownership.spec.ts` using the shared fixture.
- Verify passive suppression creates no turn or empty-output warning. Keep the
genuine empty-turn test; diagnose any failure rather than suppressing all notices.
## Out of scope
Queue UI markup belongs to Task 03. Task state/replay race repair belongs to
Task 02. Do not change background-work parking, lifecycle defaults, or Office.
## Acceptance
1. The full/free capacity and preference on/off matrix leaves parked Astra
untouched after open, tab selection, preview, reload, and reconnect, while
preserving Luna's accepted payload and queue time.
2. Explicit Astra execution targets Astra alone; valid workflow reuse succeeds.
Old tombstones and no-runtime parking work across restart and stale-clear races.
3. Status and request handlers both enforce the decision; ownership errors and
queued responses never trigger fresh/resume fallback or manual-override notes.
## Verification
Run from the repository root. Bootstrap dependencies once if absent.
```bash
(cd apps && pnpm install --frozen-lockfile)
(cd apps/backend && go test -race ./internal/orchestrator -run 'TestQueuedSessionInspection|TestWorkflowParking|Test.*Resume|Test.*ProfileSwitch' -count=1)
(cd apps/backend && go test ./internal/task/repository/sqlite -run 'TestWorkflowParking' -count=1)
(cd apps/backend && test -n "$KANDEV_TEST_POSTGRES_DSN" && go test ./internal/task/repository/sqlite -run 'TestPostgresWorkflowParking' -count=1)
(cd apps/backend && go test ./internal/task/service -run 'Test.*Turn|Test.*SessionMessage' -count=1)
(cd apps/web && pnpm exec vitest run hooks/domains/session/use-session-resumption.test.ts hooks/domains/session/use-ensure-task-session.test.ts hooks/use-ensure-task-session.test.ts lib/services/session-launch-helpers.test.ts lib/ws/handlers/empty-turn-notice.test.ts)
(cd apps/web && pnpm run typecheck)
make -C apps/backend build
(cd apps/web && pnpm e2e:run --project chromium tests/workflow/queued-session-ownership.spec.ts)
git diff --check
```
Add planned missing tests before their commands. PostgreSQL tests use
`testutil.PostgresDSNFromEnv` and isolated schemas. Provision a disposable test
database through the repository's existing test setup and set
`KANDEV_TEST_POSTGRES_DSN` without printing it. The explicit environment guard
prevents a skipped PostgreSQL test from counting as a pass. If unavailable,
record the blocked database check and do not mark this work order done.
Frontend behavior changes here do not change layout, so hook tests plus the
desktop flow suffice for this slice; Task 03 owns the full mobile flow.
## Files likely touched
- `apps/backend/internal/task/models/models.go` and a focused parking model file.
- `apps/backend/internal/task/repository/interface.go`, `sqlite/session.go`,
`sqlite/session_workflow.go`, and new `workflow_parking_test.go`.
- `apps/backend/internal/orchestrator/workflow_profile_session_lifecycle.go`.
- `apps/backend/internal/orchestrator/session_launch.go`, `task_operations.go`,
`ceiling_seam4.go`, `ceiling_replay.go`, and new `queued_session_inspection_test.go`.
- `apps/backend/internal/orchestrator/dto/dto.go` (`TaskSessionStatusResponse`),
`session_ensure.go`, and launch/ensure HTTP/WS mappings.
- `apps/web/lib/services/session-launch-service.ts`, `session-launch-helpers.ts`.
- `apps/web/hooks/domains/session/use-session-resumption.ts`,
`use-session-resumption-operations.ts`, and both
`hooks/use-ensure-task-session.ts` and `hooks/domains/session/use-ensure-task-session.ts`.
- Their test files and the workflow E2E file/helper named above.
## Dependencies
None. Read the existing profile-switch, resume, launch, and empty-turn tests first.
## Risks
Stop-intent presence is not current parking. A queued or suppressed launch
response must not be interpreted as a successful running session. A status read
must not clear markers or promote the selected tab.
## Parallelism
`sequential`
## Inputs
[Design: inspection intent and durable workflow parking](../../specs/tasks/system-design/queued-session-ownership.md#inspection-intent).
Use `event_handlers_workflow_profile_session_policy_test.go` and existing
`use-session-resumption.test.ts` fixtures. Read the [plan's E2E contract](plan.md#e2e-tests).
## Results
Implementation is present. The durable stamped parking marker, passive launch
source, status eligibility guard, and browser recovery propagation are covered
by the existing orchestrator/model tests and the new repository parking test.
The desktop workflow scenario confirms that inspecting the queued task and its
parked source does not start, prompt, or queue activity for the source session.
Passing checks:
- `go test -race ./internal/orchestrator -run
'TestQueuedSessionInspection|TestWorkflowParking|Test.*Resume|Test.*ProfileSwitch'
-count=1`
- `go test ./internal/task/repository/sqlite -run 'TestWorkflowParking' -count=1`
- The focused frontend recovery, ensure, launch-helper, and empty-turn tests
passed (7 files, 97 tests).
- `pnpm run typecheck`
- `make -C apps/backend build`
- `pnpm e2e:run --project chromium
tests/workflow/queued-session-ownership.spec.ts`
- `git diff --check`
The PostgreSQL counterpart `TestPostgresWorkflowParking` is implemented, but
the required command is blocked because `KANDEV_TEST_POSTGRES_DSN` is unset.
The work order therefore remains in progress until that database check runs.
@@ -0,0 +1,136 @@
---
id: "02-preserve-queued-entry"
title: "Preserve queued entry lifecycle"
status: in_progress
wave: 2
depends_on:
- "01-preserve-parked-sessions"
plan: "plan.md"
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002
acceptance_criteria:
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.1
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.2
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.3
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.4
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.5
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.6
system_design:
- ../../specs/tasks/system-design/queued-session-ownership.md
---
# Task 02: Preserve queued entry lifecycle
## Summary
Make task state and replay respect the exact queued workflow entry. A sibling
ready event must not hide Luna's queue, and a stale replay must not affect a
newer entry.
## In scope
- Bind workflow deferrals to the committed route/step/session identity.
- Guard review-state reconciliation with working-session and valid-deferral
predicates, including restoration from legacy REVIEW plus accepted deferral.
- Serialize enqueue/state writes and observed-record dispatch/clear against
successor records. Preserve prompt, turn, and session identity across retries.
- Dispose obsolete/deleted/terminal destinations without retargeting; preserve
records on transient failures. Keep archive/cancel and Office precedence.
- Extend the desktop E2E flow to release capacity and verify Luna's one delivery.
## Out of scope
Queue rendering and summary schema belong to Task 03. No new scheduler, generic
retry framework, WIP policy, global ordering, or profile-selection algorithm.
## Acceptance
1. `TestQueuedSessionOwnership` fails before the fix when a sibling boot-ready
event turns SCHEDULING into REVIEW. Mixed CREATED+idle/failed/cancelled cases
preserve Scheduling; explicit sibling running and settlement retain queue ownership.
2. `TestQueuedEntryReplay` proves one destination delivery, restart recovery,
old-record compatibility, invalidation on step change/deletion/cancellation,
and transient-error retention without retargeting or duplicate prompts.
3. Barrier-controlled races prove an old REVIEW write or replay clear cannot
overwrite a new record. Stale callbacks cannot activate parked Astra.
## Verification
```bash
(cd apps/backend && go test -race ./internal/orchestrator -run 'TestQueuedSessionOwnership|TestQueuedEntryReplay|Test.*Ceiling|Test.*ReviewState|Test.*BootReady' -count=1)
(cd apps/backend && go test ./internal/task/repository/sqlite -run 'Test.*DeferredLaunch|Test.*QueuedEntry' -count=1)
(cd apps/backend && test -n "$KANDEV_TEST_POSTGRES_DSN" && go test ./internal/task/repository/sqlite -run 'TestPostgresQueuedEntry' -count=1)
(cd apps/backend && go test ./internal/task/service -run 'Test.*DeferredLaunch|Test.*Workflow' -count=1)
make -C apps/backend build
(cd apps/web && pnpm e2e:run --project chromium tests/workflow/queued-session-ownership.spec.ts tests/workflow/workflow-session-targeting.spec.ts)
git diff --check
```
Use a controllable runtime and real repository fixtures for RED and race cases.
Do not substitute timing sleeps for an ordered conflicting operation. Record
actual database coverage. `TestPostgresQueuedEntry` is a planned isolated-schema
test using `KANDEV_TEST_POSTGRES_DSN` from Task 01. A missing database blocks this
check; a skipped test is not verification. Keep the work order pending until its
required database checks pass.
## Files likely touched
- `apps/backend/internal/orchestrator/event_handlers_streaming.go`,
`event_handlers_agent.go`, `event_handlers_workflow.go`.
- `apps/backend/internal/orchestrator/ceiling_defer.go`, `ceiling_replay.go`,
`ceiling_seam2.go`, `ceiling_seam3.go`, and workflow route integration.
- `apps/backend/internal/task/models/ceiling_launch.go` and route model helpers.
- `apps/backend/internal/task/repository/interface.go`,
`sqlite/deferred_launch_cas.go`, `sqlite/task.go`, and corresponding tests.
- New `apps/backend/internal/orchestrator/queued_session_ownership_test.go`.
- Workflow E2E file/helper introduced in Task 01.
## Dependencies
Task 01 establishes parked policy and passive launch outcomes. Its inspection
tests remain mandatory compatibility evidence.
## Risks
Task-state serialization alone does not protect against an independently written
deferred record. Guard identity at the persistence boundary. Validity checks
must distinguish source read errors from proven obsolete work.
## Parallelism
`sequential`
## Inputs
[Design: deferred entry ownership and task reconciliation](../../specs/tasks/system-design/queued-session-ownership.md#deferred-entry-ownership).
Read existing `ceiling_replay_test.go`, `ceiling_defer_test.go`,
`event_handlers_workflow_deferred_launch_race_test.go`, and deferred CAS tests.
## Results
Implementation is present. Deferred entries retain the exact workflow route,
step, destination session, prompt, and queue time. Review-state reconciliation,
replay clearing, and Send Now admission use the destination identity and
compare-and-set rules. A created destination now takes the explicit manual
Send Now path, and a failed dispatch remains restorable by the queue worker.
Passing checks:
- `go test -race ./internal/orchestrator -run
'TestQueuedSession|TestQueuedEntry|Test.*Ceiling.*Surface|Test.*LaunchQueue'
-count=1`
- `go test ./internal/task/repository/sqlite -run
'Test.*DeferredLaunch|Test.*QueuedEntry' -count=1`
- `go test ./internal/task/service -run
'Test.*DeferredLaunch|Test.*Workflow' -count=1`
- `go test ./internal/orchestrator -run
'TestPromptSendNowClaimStartsCreatedSessionManually|TestPromptSendNowClaimSkipsOnTurnStartWhenAlreadyProcessed'
-count=1`
- `make -C apps/backend build`
- `pnpm e2e:run --project chromium tests/workflow/queued-session-ownership.spec.ts
tests/workflow/workflow-session-targeting.spec.ts` (6 tests passed)
- `git diff --check`
The required PostgreSQL queued-entry check remains outstanding because
`KANDEV_TEST_POSTGRES_DSN` is unset. The work order remains in progress until
the isolated PostgreSQL verification runs.
@@ -0,0 +1,201 @@
---
id: "03-expose-queue-status"
title: "Expose queue status across task surfaces"
status: in_progress
wave: 3
depends_on:
- "02-preserve-queued-entry"
plan: "plan.md"
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003
acceptance_criteria:
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.1
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.2
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.3
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.4
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.5
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.6
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.7
system_design:
- ../../specs/tasks/system-design/queued-session-ownership.md
---
# Task 03: Expose queue status across task surfaces
## Summary
Deliver the bounded queue summary and its desktop/phone presentation as one
vertical slice. Users can inspect either conversation while seeing which
session is queued, why it waits, and that retry is automatic.
## In scope
- `launch_queue` summary model, validation, source loading, rebuild, live
publication, revision/invalidation, and explicit removal.
- Capacity sampling and freshness without semantic task-activity churn.
- Shared queue view model, sidebar label, task-level status region, selected
session parking note, and suppression of duplicate queued-session launch prompts.
- Full desktop/mobile lifecycle, reload, disconnect, unknown-capacity,
long-name, prompt-warning, question/error coexistence, and dispatch coverage.
- English, Portuguese, Simplified Chinese, and generated Traditional Chinese
copy. Public workflow explanation and applicable source/API documentation.
## Out of scope
No global queue page, ETA, ordinal queue position, bypass control, new drawer,
or modification of historical status messages. Do not count launches as queued prompts.
## Acceptance
1. Task list, boot, detail, and WS paths share the revisioned queue projection;
stale updates cannot resurrect cleared work. Restart restores queued identity,
missing capacity remains unknown, and refresh does not change activity ordering.
2. UI-01 through UI-03 are realized with localized readable status, retained
errors/questions, and independent task queue versus selected-session state.
3. Desktop and phone tests prove passive inspection leaves Astra untouched and
capacity release starts only Luna once, with queue status clearing automatically.
## ASCII UI preview
Excerpt of [the full plan previews](plan.md#ascii-ui-preview), requirement 003:
```text
UI-01 Desktop
Sidebar: [clock] Investigate issue Queued
Detail: Queued: Luna. Waiting for session capacity
5 of 5. Checked just now. Queued since 21:15
Starts automatically when capacity is available.
[Astra] [Luna: Queued] [Plan]
Parked for workflow. Send a message to continue here.
UI-02 Phone
Task drawer row: [clock] Investigate issue Queued
Task detail:
Queued: Luna
Waiting for session capacity
5 of 5. Checked just now. Queued since 21:15
Starts automatically.
[Astra v]
Parked for workflow. Send a message to continue here.
Conversation ...
UI-03 Shared outcomes
Disconnected -> Queued, reconnecting, last checked time
No count -> Capacity unavailable
Replay error -> Retry pending plus existing error details
Dispatched -> Queue region removed; normal Starting/Running
```
Queue region is outside chat scroll, regardless of selected conversation. Use
the existing mobile drawer/picker and dedicated layout, with one chat scroll
owner and safe-area clearance. Names wrap in detail and truncate in navigation.
No required hover disclosure. New action targets meet 44 px only on touch/mobile.
These structural requirements are fixed; spacing and wording are illustrative.
## Verification
```bash
(cd apps/backend && go test ./internal/task/statussummary ./internal/task/dto -count=1)
(cd apps/backend && go test ./internal/task/service -run 'Test.*StatusSummary|Test.*LaunchQueue' -count=1)
(cd apps/backend && go test -race ./internal/orchestrator -run 'TestQueuedSession|TestQueuedEntry|Test.*Ceiling.*Surface|Test.*LaunchQueue' -count=1)
(cd apps/web && pnpm exec vitest run components/task/launch-queue-status.test.tsx lib/tasks/launch-queue-view-model.test.ts lib/ws/handlers/tasks-status-summary.test.ts hooks/domains/session/use-session-resumption.test.ts)
(cd apps/web && pnpm run i18n:zh-hant && pnpm run i18n:check && pnpm run i18n:ratchet)
(cd apps/web && pnpm run typecheck)
make -C apps/backend build
(cd apps/web && pnpm e2e:run --project chromium tests/workflow/queued-session-ownership.spec.ts tests/workflow/workflow-session-targeting.spec.ts)
(cd apps/web && pnpm e2e:run --project mobile-chrome tests/workflow/mobile-queued-session-ownership.spec.ts)
node --test scripts/validate-public-docs.test.mjs
node scripts/validate-public-docs.mjs
python3 scripts/list-docs.py validate
python3 scripts/lint-spec-files.py --all
git diff --check
```
Create the named new component/view-model tests during RED. Run any additional
modified test suite explicitly and record its command. The managed E2E runner
builds fresh artifacts and owns cleanup; do not overlap desktop/mobile runs or
use all-worker overrides. Compare rendered phone geometry and labels with UI-02.
## Files likely touched
- `apps/backend/internal/task/statussummary/{model,rebuild,projector,projector_events}.go`
and new queue projection tests.
- `apps/backend/internal/task/service/service_status_summary_rebuild.go`, task
DTO enrichment, boot/list wiring, and the existing summary composition provider.
- `apps/backend/internal/orchestrator/ceiling_defer.go`, `ceiling_replay.go`,
`ceiling_sweep.go`, `ceiling_surface.go`, and tests.
- `apps/web/lib/types/task-status-summary.ts`, `http.ts`, `backend.ts`.
- `apps/web/lib/ws/handlers/task-status-summary.ts`, task hydration/merge paths.
- `apps/web/lib/tasks/launch-queue-view-model.ts` (new) and tests.
- `apps/web/components/task/launch-queue-status.tsx` (new), `task-item.tsx`,
`task-switcher.tsx`, `task-chat-panel.tsx`, desktop task layout placement.
- `apps/web/components/task/mobile/session-mobile-layout.tsx`,
`session-task-switcher-sheet.tsx`, and session-picker presentation if needed.
- `apps/web/src/locales/{en,pt-pt,zh-cn,zh-hk,zh-tw}` catalogs.
- Workflow E2E specs/helper introduced by this package.
- `docs/public/tasks-and-workflows.md`; inspect `agents-and-profiles.md` and
`configuration.md` for related wording without changing startup defaults.
## Dependencies
Tasks 01 and 02 provide passive eligibility and authoritative queue lifecycle.
## Risks
Summary invalidation must carry clears, and old hydration must not resurrect
queue state. Capacity sampling must not re-sort tasks or expose global session
identities. A mobile status region must not cover the session picker or composer.
## Parallelism
`sequential`
## Inputs
[Design: queue projection and surfaces](../../specs/tasks/system-design/queued-session-ownership.md#queue-projection).
Use existing queued-prompt summary tests as revision/rebuild examples without
merging the two semantics. Read mobile parity, e2e, and docs-maintainer guidance.
## Results
Implementation is present. The revisioned `launch_queue` projection is loaded
from durable task metadata, refreshed with capacity observations, and exposed
through task list, sidebar, detail, desktop chat, and mobile navigation. The
shared view model keeps queue status independent from the selected parked
conversation, and the phone layout has one chat scroll owner.
Passing checks:
- `go test ./internal/task/statussummary ./internal/task/dto -count=1`
- `go test ./internal/task/service -run
'Test.*StatusSummary|Test.*LaunchQueue' -count=1`
- `go test -race ./internal/orchestrator -run
'TestQueuedSession|TestQueuedEntry|Test.*Ceiling.*Surface|Test.*LaunchQueue'
-count=1`
- Focused frontend tests for the queue component, view model, status-summary
handler, and session recovery passed (93 tests).
- `pnpm run typecheck`, `pnpm run lint`, `pnpm run i18n:zh-hant`,
`pnpm run i18n:check`, and `pnpm run i18n:ratchet`
- `pnpm test` passed (2,140 files, 18,465 tests, 4 skipped).
- `make -C apps/backend build`
- `pnpm e2e:run --project chromium tests/workflow/queued-session-ownership.spec.ts
tests/workflow/workflow-session-targeting.spec.ts`
- `pnpm e2e:run --project mobile-chrome
tests/workflow/mobile-queued-session-ownership.spec.ts`
- `node --test scripts/validate-public-docs.test.mjs`,
`node scripts/validate-public-docs.mjs`, `python3 scripts/list-docs.py
validate`, `python3 scripts/lint-spec-files.py --all`, and
`git diff --check`
The rendered desktop and phone scenarios match the preview contract: queued
destination and capacity are visible while the parked source is selected,
queue status is present in task navigation and detail, names wrap on phone,
and releasing the fixture capacity dispatches the destination once. The full
E2E sleep lint audit retains unrelated baseline violations; the scoped preview
and sleep ratchet pass.
The required PostgreSQL projection/rebuild coverage remains outstanding because
`KANDEV_TEST_POSTGRES_DSN` is unset. The work order remains in progress until
the isolated PostgreSQL verification runs. The repository-wide backend test
target also reports unrelated existing environment-sensitive failures; the
task-related backend packages pass.
@@ -42,6 +42,10 @@ remain separate during repair.
## Risks
Follow-up: [queued session ownership](../queued-session-ownership/plan.md) adds
passive-origin enforcement and task queue visibility. Existing manual override
semantics and the completed work order remain unchanged.
- A repository read failure before a process callback is treated as not owned,
so a reservation can wait for the sweep instead of being released by an
ambiguous callback.
@@ -119,6 +119,12 @@ and save behavior.
- [ ] [Task 03: Simplify the selector and add agent logos](task-03-expose-workflow-policy.md)
- [ ] [Task 04: Prove all lifecycle combinations](task-04-prove-and-document-reuse-flows.md)
## Follow-up delivery
[Queued session ownership](../queued-session-ownership/plan.md) owns the later
inspection/ceiling intersection and its regression matrix. This historical
package does not establish that opening a parked predecessor is safe.
## Risks
- A transition path can use the destination end setting by mistake.
@@ -111,6 +111,10 @@ the shared backend policy rather than phone interaction.
## Related delivery records
[Queued session ownership](../queued-session-ownership/plan.md) adds the
parked-predecessor and deferred-destination intersection. This completed repair
retains its original scope and results.
[Workflow session targeting](../workflow-session-targeting/plan.md) delivered
explicit initial and source-step recipients. Its completed results remain
historical evidence, not proof of this profile-only case. This package owns
@@ -13,6 +13,10 @@ legacy_specs: []
## Overview
Follow-up: [queued session ownership](../queued-session-ownership/plan.md) covers
inspection and admission after recipient selection. Its new cases do not reopen
this package's delivered routing work or reuse its test results as queue evidence.
Allow Review to return to the task's initial agent or an earlier step's
conversation. Keep **When this step starts** and **When this step ends** as
independent controls. Keep lifecycle navigation visible above the recipient list.
+5
View File
@@ -247,6 +247,11 @@ exclusive health probe before it becomes eligible again. This shared error
classification is used by task/Kanban and Office routing, while the per-
candidate policies are configured on dynamic profiles.
When a task launch waits for session capacity, Kandev keeps the selected
destination and retries it automatically. Inspecting another session does not
resume a parked predecessor. Use an explicit **Resume** action or send a
message for manual recovery. These actions can override the automatic ceiling.
Provider errors that occur before a result can use the configured action, such
as retrying the current candidate or trying the next candidate. A started turn
with an ambiguous result does not switch providers automatically. If no
+12
View File
@@ -601,6 +601,18 @@ destination queue. If the configured
feeder is also full, creation returns a conflict. Ephemeral tasks are not
counted.
A task can also show **Queued** after workflow entry selects a session but the
agent session ceiling blocks automatic launch. This is different from WIP
queueing: WIP waits before destination entry, while session-capacity queueing
keeps the selected session and retries it automatically. Task details and the
task navigator show the destination, the latest capacity observation, queue
time, and retry state. They do not show a queue position or estimated start
time. Capacity counts older than 40 seconds, or counts unavailable because the
client is disconnected, are labelled stale while the destination remains
visible. Opening a task or a parked predecessor does not start it. Use the
explicit **Start** or **Resume** action, or send a message, to override the
automatic ceiling for that conversation.
Integration watchers use the same admission rule. For example, a GitHub review
watch targeting a `Review` step with a limit of two admits at most two newly
observed pull requests at a time. Pull requests that lose the capacity race
@@ -63,6 +63,11 @@ origins never become manual during replay. The sweep clears a record only after
successful dispatch. A repeated refusal leaves the original timestamp and
payload in place.
The task-owned [queued session ownership design](../../tasks/system-design/queued-session-ownership.md)
plans explicit passive-inspection classification at the caller boundary. Opening
a conversation is not a manual override. Actual explicit execution retains this
design's manual admission rule; workflow parking eligibility remains task-owned.
## Failure and recovery
Reservation ownership is session-keyed. A stale process-start callback first
@@ -12,6 +12,14 @@ owners:
This document is the migrated task-system source for the capability. The source detail below remains authoritative while the system is migrated into separate requirement and design records.
### Queued session ownership restriction
[Queued session ownership](queued-session-ownership.md) defines the restriction on
open-time recovery: workflow-parked sessions and an accepted queued destination
remain protected with either preference value. The default-off behavior below
continues for otherwise eligible sessions. Explicit user execution and legitimate
workflow re-entry retain their own activation paths.
## Requirements
### REQ-TASKS-PREVENT-AGENT-AUTOSTART-ON-OPEN-001: Prevent Agent Auto-Start On Open
@@ -221,4 +229,4 @@ per-task variant in this feature.
- Whether the option should also gate the office advanced mode's
`ensureExecution` resume once that WS field is actually wired. Currently a
no-op; intentionally out of scope until the office flow auto-starts.
no-op; intentionally out of scope until the office flow auto-starts.
@@ -0,0 +1,135 @@
---
status: draft
system: tasks
created: 2026-09-16
owners:
- kandev
---
# Queued session ownership requirements
## Overview
A workflow can select its next session before instance capacity permits launch.
Users must be able to inspect that task without waking a parked predecessor or
losing the visible queued state. The task system owns the selected recipient,
durable launch intent, and its presentation across task surfaces.
This capability extends [workflow session lifecycle](workflow-profile-session-lifecycle.md).
The [agent ceiling](../../agents/requirements/session-concurrency-ceiling.md)
continues to own capacity admission. A ceiling queue is distinct from a
[workflow WIP queue](wip-limit-pull-system.md): WIP admission occurs before
destination entry, whereas a ceiling can delay a session already selected by entry.
## Terminology
- **Passive inspection:** Opening, focusing, reloading, reconnecting, previewing,
or selecting a conversation without requesting execution.
- **Parked predecessor:** A conversation stopped by a workflow session switch.
It remains available for reading, explicit follow-up, or later workflow reuse.
- **Queued destination:** The exact session selected for a deferred workflow launch.
- **Explicit execution:** A user selects Start/Resume or sends a message to an
identified conversation. A valid workflow re-entry is a separate authorized trigger.
## Requirements
### REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001: Inspection preserves execution ownership
**Intent:** Reading a task must not wake its retired workflow participant.
#### Acceptance criteria
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.1:** Passive inspection shall not
start, resume, prompt, or enqueue work for a parked predecessor. This applies
with free or full capacity and with either auto-start-on-open preference value.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.2:** When a destination is queued,
passive inspection of any conversation shall preserve its recipient, prompt,
queue time, primary ownership, and workflow step. It shall not create another launch.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.3:** Explicit execution for a parked
predecessor shall target that conversation only. It shall not replace the
queued destination, consume its prompt, or acquire its workflow ownership.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.4:** A later workflow entry that
legitimately selects a parked conversation shall be able to reuse it under
the existing lifecycle policy. Historical parking shall not permanently block reuse.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.5:** Passive recovery that remains
eligible under existing preferences shall respect the automatic session
ceiling. It shall never produce a manual-override audit message.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.6:** If current ownership cannot be
established, passive inspection shall remain available without starting work
or attempting a fresh-session fallback. Explicit recovery shall remain separately available.
### REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002: Deferred work survives sibling lifecycle events
**Intent:** A queued launch must remain owned and retryable until its own outcome is known.
#### Acceptance criteria
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.1:** While an eligible queued
destination exists and no session is executing work, the task shall remain
Scheduling. A sibling becoming ready, idle, stopped, failed, or completed
shall not move that task to Review or clear its queue.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.2:** If a sibling explicitly runs
while the destination is queued, task activity shall reflect the running work
and retain the queue indicator. When that sibling settles, the task shall
return to Scheduling while the destination remains queued.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.3:** When capacity becomes available,
automatic retry shall deliver the accepted launch to its exact eligible
destination once. It shall not wake or send the step prompt to the predecessor.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.4:** Reload and backend restart shall
preserve queued ownership and parked inspection behavior. Repeated refusal
shall not reset queue time, replace the accepted prompt, or duplicate sessions.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.5:** If the destination is removed,
cancelled, or superseded by a later workflow entry, its obsolete launch shall
not run or retarget another session. The task shall expose the disposition.
A transient read or replay error shall preserve accepted work for retry.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.6:** A stale replay or lifecycle event
shall not clear, launch, or reconcile state for a newer queued entry. Explicit
task cancellation and archive shall retain their existing precedence.
### REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003: Visible launch queue
**Intent:** Users can distinguish waiting for capacity from a stopped or failed agent.
#### Acceptance criteria
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.1:** Desktop sidebar rows and the
phone task navigator shall expose a labelled Queued indicator for deferred
session launches, without requiring the queued conversation to be selected.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.2:** Task details shall show the
queued destination, waiting reason, queue time, capacity observation, and
automatic retry explanation outside the historical transcript. This status
shall remain visible when the user selects a parked predecessor.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.3:** A parked conversation shall be
identified as parked for the workflow. A queued destination shall not offer
a misleading recovery prompt or imply that the user must restart it.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.4:** Queue status shall clear after
confirmed dispatch or final disposition. Older snapshots shall not restore a
cleared queue or erase newer queued work. Pending questions and real errors
shall remain visible alongside queue status.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.5:** Capacity observations shall show
when they were checked. When unavailable or disconnected, the interface
shall retain known queued ownership and identify stale or unavailable counts.
It shall not claim a queue position or an estimated start time.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.6:** Phone users shall inspect the
same status and navigate conversations through the existing task drawer and
session picker. Required information shall not depend on hover. New touch
actions shall have at least 44 px hit targets, with no page horizontal overflow.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.7:** Passive inspection and ordinary
queue waiting shall not generate an empty-output completion warning. A real
prompt that completes without output shall retain its existing warning behavior.
## Compatibility and exclusions
Actual manual starts and resumes retain the existing ceiling override. This
package does not turn the ceiling into a hard limit for explicit execution.
Ordinary open-time recovery remains preference-controlled, subject to the
ownership and automatic-admission conditions above.
No new session-state enum, global queue dashboard, queue reordering, ETA,
capacity setting, workflow lifecycle setting, or provider-routing policy is added.
Office scheduling, WIP admission order, terminal conversation recovery, and
unrelated prompt queues retain their owning contracts.
## Design
- [Queued session ownership](../system-design/queued-session-ownership.md)
@@ -34,6 +34,10 @@ fresh conversation for independent work.
- **Profile re-entry:** A profile switch to an agent profile that already has a
session on the task.
Inspection of parked sessions and deferred destination launches is specified in
[queued session ownership](queued-session-ownership.md). Selecting a conversation
for reading is not a workflow re-entry or an explicit request to execute it.
## Requirements
### REQ-TASKS-WORKFLOW-PROFILE-SESSIONS-001: Configurable step profile-session lifecycle
@@ -0,0 +1,240 @@
---
status: draft
system: tasks
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003
---
# Queued session ownership system design
## Purpose and boundaries
This design extends [workflow session lifecycle](workflow-profile-session-lifecycle.md)
at the boundary between an accepted recipient, launch admission, and inspection.
It uses the existing [agent ceiling](../../agents/system-design/session-concurrency-ceiling.md)
and task-owned deferred record. It does not replace either queue or change WIP admission.
## Requirement mapping
| Requirement | Sections |
| --- | --- |
| REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001 | Inspection intent; Durable workflow parking |
| REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002 | Deferred entry ownership; Task reconciliation |
| REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003 | Queue projection; Desktop and mobile surfaces; Failure and observability |
## Existing components
- `workflow_profile_session_lifecycle.go` parks a source and records an
execution-stamped stop intent. That record has a durable consumed tombstone.
- `session_launch.go:launchResume` classifies origin from `AutoStart`.
`buildResumeRequest` currently omits that field for both explicit and open-time recovery.
- `GetTaskSessionStatus` reports runtime resumability. The browser's
`use-session-resumption.ts` drives recovery after a status read.
- `handleAgentBootReady` calls `setSessionWaitingForInput`, which calls
`writeTaskReviewState`. The current sibling guard recognizes working sessions,
but not an eligible CREATED destination whose launch is deferred.
- `ceiling_replay.go` replays by stored launch kind every 20 seconds and on
release. `ceiling_surface.go` writes historical status messages.
- `task/statussummary` persists a revisioned, bounded summary used by task
list and detail consumers. Extend it rather than inventing a frontend queue.
## Inspection intent
Add optional `activation_source: "user_action" | "session_open"` to the unified
launch request. Omission preserves existing explicit-call behavior. Reject
unknown values. `session_open` cannot carry a prompt or grant recovery permission.
The backend forces automatic admission for this source even if `auto_start`
was omitted or false. Internal workflow and peer-message paths retain their
existing explicit origins; do not mark all automatic work as passive inspection.
Add `auto_resume_allowed` and `auto_resume_blocked_reason` to session status.
Reasons are closed values: `workflow_parked`, `launch_queued`, or
`ownership_unavailable`; absence means no new ownership restriction. Keep
`is_resumable` for explicit recovery. Populate this through one orchestrator
eligibility helper shared by status, launch, and open-time ensure paths.
The browser consults these fields before `markSessionStarting`. Every passive
resume sends `session_open`; explicit Resume and Send remain user actions.
Inspect `useEnsureTaskSession` and `session.ensure` as well as tab recovery.
An ensure for a task with an accepted queued destination returns that destination
without launching, allocating another session, or switching to the visible tab.
Recheck under the existing session/entry lifecycle guards before reservation,
turn creation, runtime launch, prompt creation, or fresh fallback. Return a
successful no-execution disposition, proposed `activation_disposition:
"suppressed" | "queued"`, with the reason and current session. Teach
`session-launch-service.ts` and recovery operations to accept this as waiting,
not launch success or an error that triggers workspace/fresh fallback.
Transport errors stay errors. Preserve request-generation guards on late responses.
For an ordinary recoverable session without parking or pending work, retain the
preference behavior and use automatic ceiling admission. If it is deferred,
retain inspection source in its replay payload and revalidate eligibility on retry.
If a task already has a different accepted launch, inspection cannot replace it
or add a conflicting resume record. It returns the existing queued disposition.
## Durable workflow parking
Add a typed `workflow_parking` session metadata value with `stamp`,
`parked_at`, and source entry identity where available. Record it even when no
runtime execution exists. This is current policy, not evidence of background work.
Do not reuse `parked_on_background_work` or clear the stop-intent tombstone.
Write parking with the parked session transition under the existing lifecycle
guard and a conditional repository operation. A failed transition must not leave
an unowned parking marker. Clear only the matching stamp when explicit execution
is accepted or a workflow entry commits that session as recipient. No mark is
cleared on a status read, focus, denied admission, or transient launch failure.
Coordinate clear with the launch claim so failed attempts leave the conversation
protected from subsequent passive recovery. A later park supersedes an older clear.
Existing parked tasks may lack this marker. During status/launch eligibility,
recognize an execution-stamped workflow stop intent (including consumed state)
only with non-primary source identity and no newer authorized activation/route.
Inspect durable session execution timestamps and the committed workflow route;
do not equate any WAITING_FOR_INPUT session with parking. If legacy evidence is
ambiguous, deny passive execution with `ownership_unavailable`. Reading remains
possible, and explicit recovery or an authoritative workflow selection resolves
the ambiguity. Never fabricate queue work for an uncertain predecessor.
Keep metadata parsing in task models and conditional writes in the task
repository, with SQLite/PostgreSQL conformance coverage. No new state enum or
database column is required. Tests must include parking without a runtime,
consumed tombstones, later reuse, explicit follow-up, restart, and stale clears.
## Deferred entry ownership
Extend workflow-origin deferred payloads with a nested entry binding containing
workflow ID, destination step ID, committed route operation ID, and destination
session ID. Obtain these from the existing `workflow_session_route` contract,
not asynchronous history. Preserve the first payload and enqueue time across refusals.
Generic start/resume records outside workflow entry keep their own eligibility rules.
Before replay, validate task membership, current entry identity, recipient,
session state, and archive/cancellation status. A superseded, deleted, or terminal
destination receives a final disposition without retargeting. Read failures
retain the record. Old records without the binding are replayable only when the
current committed route and exact session unambiguously agree; enrich them by CAS.
Ambiguous records remain visible as requiring recovery, without dispatch.
Carry the observed record identity through dispatch and clear. A successful
dispatch may clear only that record; it must not re-read and strip a successor's
ceiling keys. Route changes, archive/cancel, and replay claims serialize using
existing task-entry and lifecycle ownership. Never hold a DB transaction while
calling the runtime. Add conditional repository operations where the existing
read-then-write sequence cannot prove atomic ownership.
Recheck the claimed route before prompt admission. Preserve the stored turn and
prompt ownership on retry; a second sweep must not create a second step message.
An already-dispatched exact execution is acknowledged through existing correlated
launch identity. No new scheduler, fan-out queue, or launch lease system is added.
## Task reconciliation
Within `writeTaskReviewState`, use the same task-runtime serialization as running
state reconciliation. Reconcile eligible non-Office, non-archived tasks as follows:
1. Any authorized working session preserves normal IN_PROGRESS behavior.
2. Otherwise, any valid deferred destination preserves or restores SCHEDULING.
3. Only with neither condition may ordinary completion reconcile to REVIEW.
The second predicate is existential: a CREATED queued destination protects the
task even when a sibling is idle, failed, cancelled, or completed. An arbitrary
CREATED row with no accepted launch is not sufficient. Read errors fail closed.
Guard the write against the observed queue/route identity so a concurrent enqueue
cannot lose to an older REVIEW writer. Preserve explicit terminal task actions.
When a user explicitly runs the parked sibling, retain queue status alongside
IN_PROGRESS; settling that sibling restores SCHEDULING. Do not promote it to
primary or run destination entry actions. Keep existing completion-follow-up,
Office, cancellation, and runtime-publication ordering protections.
The sweep also repairs legacy REVIEW+valid-deferral tasks to SCHEDULING when no
session is working. This repair requires authoritative task and entry evidence;
the browser never writes task state based on a badge.
## Queue projection
Add optional `launch_queue` to `TaskStatusSummary` and its frontend type. It is
a complete replacement value under the existing summary revision:
```text
launch_queue: null | {
session_id?, agent_profile_id?, workflow_step_id?, queued_at,
reason: "session_capacity" | "ownership_unavailable" | "replay_error",
retrying: boolean,
capacity: null | { in_use, limit, observed_at }
}
```
For session-backed workflow launches, session and profile are required. A
sessionless start can use the generic task queue label without fabricating a
session ID; it does not participate in parked-predecessor recipient matching.
Project sanitized identity and status from the durable ceiling record through a
narrow provider at the task-service/orchestrator composition boundary. The task
summary must not import the orchestrator or expose raw replay payloads, prompts,
environment variables, or credentials. Resolve profile names through the existing
authorized profile catalog; fall back to a generic session label if unavailable.
Extend summary rebuild, equality, validation, live projection, boot/list/detail
enrichment, and `task.status_summary.updated`. Include explicit queue removal in
the replacement summary. Missing fields on legacy snapshots are not a newer clear.
Reuse existing revision/invalidation handling in `task-status-summary.ts` and
task hydration; test old list snapshots after a queued update and after a clear.
Admission/refusal/dispatch/drop changes invalidate and rebuild affected summaries.
The existing sweep refreshes observations at most once per pass; use one shared
population sample per pass rather than a count query per row. Capacity is sampled
from the ceiling controller, not copied forever from the original refusal.
Changes to observations do not advance semantic task activity or reorder recent
tasks. On restart rebuild queue ownership first; unavailable capacity is null.
Observations older than two sweep intervals (40 seconds), or a disconnected
client, are labelled stale. UI cannot infer dispatch from a count below the limit.
Queue presence does not increase `queued_prompt_count`: a deferred launch and a
user prompt queue are different contracts. Pending question/error precedence and
existing background-work indicators remain intact. No ordinal position is shown.
## Desktop and mobile surfaces
Share a queue view model and task-scoped status component. Desktop `TaskSwitcher`
rows show Queued text with an existing clock/status icon. Task details place the
status above conversation content, independently of selected session and transcript
scroll. Show a separate parked note for the selected predecessor. The destination's
CREATED start/recovery affordance yields to queued status while accepted work exists.
Use the existing `SessionTaskSwitcherSheet` phone drawer and
`session-mobile-layout.tsx` dedicated composition. Place a compact task queue
region above `MobileSessionsPicker`, outside the chat scroll. The navigator row
shows the same Queued label. Long names wrap in details and truncate in rows.
The user can inspect Astra while Luna remains named in the task queue region.
This is persistent status, so do not add a second drawer or global dashboard.
Keep the existing fixed header/navigation, one chat scroll owner, dynamic viewport,
and safe-area handling. Existing explicit execution controls remain available;
no new bypass button is needed. Status text is keyboard/screen-reader readable,
uses restrained polite announcements for state changes, and never announces every
capacity sample. All new copy uses five-language i18n and the Traditional Chinese
generation command. Previews and viewport assertions live in the work orders.
## Failure and observability
Emit structured, bounded reason codes for suppressed inspection, queued replay,
stale-entry disposition, and protected state reconciliation. Include task,
session, entry, and execution IDs in logs only, with no prompt content.
Do not create lifecycle-only turns merely to announce a suppressed inspection.
Keep historical ceiling notes as history. The live status is independent and
does not rewrite old audit messages. An ordinary deferral does not mean an empty
prompt completed. Verify empty-turn handling at the real event boundary; the
reported warning's exact origin was not proven, so do not remove valid warnings
or claim its cause without a failing regression.
## Related records
- [Passive inspection decision](../../../decisions/2026-09-16-passive-session-inspection.md)
- [Runtime state publication](runtime-state-publication-order.md)
- [Implementation package](../../../plans/queued-session-ownership/plan.md)
@@ -487,6 +487,10 @@ logs include session ID, execution ID, and intent stamp.
## Implementation plans
- [Queued session ownership](../../../plans/queued-session-ownership/plan.md)
extends parking and admission through the paired
[inspection and queue design](queued-session-ownership.md). Stop-intent
tombstones remain callback evidence, not current activation policy.
- [Explicit session targeting](../../../plans/workflow-session-targeting/plan.md)
- [Same-profile fresh-session repair](../../../plans/workflow-same-profile-new-session/plan.md)