fix: restore session-open conversation recovery (#3789)

This commit is contained in:
Carlos Florêncio
2026-09-18 10:33:34 +01:00
committed by GitHub
parent 2f15db8a14
commit c8a0b42f4e
18 changed files with 1764 additions and 108 deletions
@@ -1,9 +1,13 @@
# ADR-2026-09-16-passive-session-inspection: Keep inspection separate from execution ownership
**Status:** proposed
**Status:** superseded in part by [ADR-2026-09-18-session-open-resumes-conversation](2026-09-18-session-open-resumes-conversation.md)
**Date:** 2026-09-16
**Area:** workflow
The successor replaces parking-based suppression and the parked-session note.
Queue ownership, automatic admission, and reconciliation constraints remain.
The text below records the original decision.
## Context
Workflow parking and process loss both appear as resumable sessions without a
@@ -0,0 +1,63 @@
# ADR-2026-09-18-session-open-resumes-conversation: Opening resumes the selected conversation
**Status:** accepted
**Date:** 2026-09-18
**Area:** workflow
## Context
Workflow switches stop earlier sessions and preserve their conversations.
The previous inspection policy exposed a parked-session banner and prevented
open-time recovery. Users had to understand workflow parking before continuing
a conversation. The user explicitly rejected that interaction on 2026-09-18.
## Decision
Opening or selecting a workflow-stopped conversation uses normal automatic
session recovery. Current or historical workflow parking is not an eligibility
restriction. The interface has no parked-session banner, toolbar, or replacement
parking explanation.
Existing auto-start prevention, automatic capacity admission, terminal-session
rules, authorization, and archive rules still apply. Session opening resumes
provider context; it does not send a new prompt or replay an interrupted turn.
Conversation activation does not transfer workflow ownership. A selected older
session cannot become the primary workflow recipient, consume a destination's
queued prompt, or replace its deferred launch merely because the user opens it.
A queued destination remains scheduler-owned. A different conversation can
recover when capacity permits without altering that accepted launch.
Keep execution-stamped stop tombstones for delayed callbacks. Legacy
`workflow_parking` metadata can remain stored but does not control recovery or
presentation. Remove its policy-only readers and writers when they have no
remaining lifecycle purpose; no bulk live-data rewrite is required.
## Consequences
The user sees ordinary conversations and normal recovery behavior. Opening an
older conversation can consume automatic capacity. It never gets a manual
ceiling override. If a task already owns a deferred launch, a capacity refusal
must preserve that record rather than enqueue conflicting work.
This supersedes the parking-based suppression and parked-note portions of
[the previous inspection decision](2026-09-16-passive-session-inspection.md).
Its exact-recipient queue ownership, admission, and reconciliation rules remain.
The narrow historical-stop exception in the earlier repair plan is replaced by
this general policy. No primary-session or committed-route exception is needed
solely to disregard parking.
## Alternatives Considered
- Keep the banner and require explicit Resume: rejected by the user.
- Hide the banner but retain suppression: leaves the reported interaction defect.
- Resume only the current workflow destination: does not meet the user's request
to resume even a parked conversation when opened.
- Transfer workflow ownership on open: conflates conversation access with workflow routing.
- Ignore capacity or delete stop tombstones: breaks independent runtime protections.
## Related records
- [Requirements](../specs/tasks/requirements/queued-session-ownership.md).
- [Design](../specs/tasks/system-design/queued-session-ownership.md).
- [Revised implementation plan](../plans/session-open-recovery-eligibility/plan.md).
@@ -12,6 +12,15 @@ legacy_specs: []
# Implementation plan: Queued session ownership
## Policy supersession, 2026-09-18
The [revised conversation recovery package](../session-open-recovery-eligibility/plan.md)
supersedes parked-session suppression and parking-note presentation in this
historical package. Opening an earlier conversation now follows normal recovery.
Keep queue identity, admission, callback, and reconciliation coverage. Replace
old no-resume and parked-note assertions in the revised package's work orders.
Historical results and outstanding PostgreSQL checks below are unchanged.
## Overview
Keep Luna queued when a workflow enters Implement, keep the parked Astra
@@ -297,3 +306,11 @@ Implementation and available rendered verification on 2026-09-17:
- 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.
## Follow-up: Session-open recovery eligibility
The [recovery eligibility repair](../session-open-recovery-eligibility/plan.md)
owns the historical-stop and settled-deferral regressions found after restart.
It replaces the parking suppression policy and removes the parking note, with
separate and combined recovery cases on desktop and phone. Existing results and PostgreSQL prerequisites here
remain unchanged. The follow-up work order is pending implementation.
@@ -22,6 +22,15 @@ system_design:
# Task 01: Preserve parked sessions during inspection
## Policy supersession, 2026-09-18
The [revised conversation recovery package](../session-open-recovery-eligibility/plan.md)
supersedes parked-session suppression and parking-note presentation in this
historical package. Opening an earlier conversation now follows normal recovery.
Keep queue identity, admission, callback, and reconciliation coverage. Replace
old no-resume and parked-note assertions in the revised package's work orders.
Historical results and outstanding PostgreSQL checks below are unchanged.
## Summary
Make workflow parking durable and keep passive inspection separate from explicit
@@ -143,3 +152,11 @@ Passing checks:
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.
## Follow-up: Session-open recovery eligibility
The [recovery eligibility repair](../session-open-recovery-eligibility/plan.md)
owns the historical-stop and settled-deferral regressions found after restart.
It replaces the parking suppression policy and removes the parking note, with
separate and combined recovery cases on desktop and phone. Existing results and PostgreSQL prerequisites here
remain unchanged. The follow-up work order is pending implementation.
@@ -22,6 +22,15 @@ system_design:
# Task 03: Expose queue status across task surfaces
## Policy supersession, 2026-09-18
The [revised conversation recovery package](../session-open-recovery-eligibility/plan.md)
supersedes parked-session suppression and parking-note presentation in this
historical package. Opening an earlier conversation now follows normal recovery.
Keep queue identity, admission, callback, and reconciliation coverage. Replace
old no-resume and parked-note assertions in the revised package's work orders.
Historical results and outstanding PostgreSQL checks below are unchanged.
## Summary
Deliver the bounded queue summary and its desktop/phone presentation as one
@@ -0,0 +1,211 @@
---
created: 2026-09-18
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: []
---
# Plan: Resume conversations on open without parking UI
## Overview
Opening a workflow-stopped conversation uses normal automatic recovery, including
older non-primary sessions. Remove the parked-session banner without replacing
it with another state the user must understand.
This replaces the earlier narrow exception for a reused primary session.
The user's 2026-09-18 instruction explicitly changes the parking policy.
[The new decision](../../decisions/2026-09-18-session-open-resumes-conversation.md)
records that change. The task system owns conversation activation and workflow
recipient isolation. No material product question remains unresolved.
Implement two sequential work orders: first correct recovery and ownership
handling; then remove parking presentation and verify the full desktop/phone flow.
## Evidence and conformance
The original investigation of task `62f15ae6-bb2d-40c5-863b-91366636114d`
confirmed `needs_resume=true` with `auto_resume_allowed=false` and
`ownership_unavailable` on build `f4c9131307`. A consumed workflow-switch stop
marker blocked recovery. An empty `deferred_launch: {}` was a second blocker.
Those branches originated in #3755; #3776 and #3779 corrected separate deadlocks.
The user then supplied task `efe289d6-a6cc-4f7b-9c9d-0c3014643270` and a
screenshot of the parked-session note. The user rejected the note and requested
resume on open even while a session is parked. The screenshot is presentation
evidence; this plan does not claim a new live-state investigation of that task.
The old inspection requirement 001.1 and parked-note requirement 003.3 are
explicitly superseded by 001.9 and 003.8. Criteria 001.7/8 retain restart and
settled-queue coverage. Queue identity, automatic admission, callback correlation,
and the deadlock fixes remain compatibility constraints.
## Scope
### In scope
- Remove workflow parking and historical stop markers as recovery restrictions.
- Treat absent, null, and empty settled deferrals as no pending launch.
- Resume only the selected conversation, without changing workflow ownership.
- Remove parked-session presentation, unused projection helpers, and locale copy.
- Audit policy-only parking writers/readers and remove unused code without migrations.
- Cover free/full capacity, pending destination, restart, preference, and delayed events.
- Update public recovery documentation when implementation ships.
### Out of scope
- Resending interrupted turns or settled workflow prompts on open.
- Changing workflow primary selection, automatic capacity limits, or manual overrides.
- Removing genuine launch queue status or background-work/Office parking.
- New session states, scheduler queues, migrations, or live-data cleanup.
- Relaunching explicitly cancelled, archived, or completed sessions outside existing rules.
## Technical approach
Use [Conversation recovery and workflow stop history](../../specs/tasks/system-design/queued-session-ownership.md#conversation-recovery-and-workflow-stop-history).
Remove parking-policy checks from `autoResumeEligibility`. Keep authorization,
resumability, archive/terminal rules, and nonempty deferred ownership validation.
Keep the general stop-intent parser and callback fences intact.
A queued destination must not get a duplicate resume. A selected sibling can
resume when capacity permits, while the original deferred recipient and prompt
stay unchanged. If capacity is full, do not replace accepted work with a sibling
resume record. Test the existing admission/conflict paths rather than creating
another queue. A queue or route change after status still requires guarded
validation. A new parking marker alone is no longer a denial reason.
Audit `workflow_parking` readers and writers across models, repository,
orchestrator, tests, and web. Remove policy-only code when unused; document any
retained independent purpose. Stored legacy markers become inert. Do not broaden
this cleanup to execution-stamped stop intents or unrelated parking concepts.
Remove `ParkedSessionNote` and `hasWorkflowParkingMarker` from
`launch-queue-status.tsx`, their caller in `task-chat-panel.tsx`, and their tests.
Remove `task:parkedSessionNote` from every catalog. Keep real queue status.
## ASCII UI preview
### UI-01: Desktop, earlier conversation selected
```text
Before
[Astra] [Luna] [Plan]
[This session is parked for the workflow. Opening it does not resume it...]
Conversation
Composer
After
[Astra] [Luna] [Plan]
Conversation (existing recovery/loading indication when needed)
Composer
```
When a genuine launch is queued, its existing task-level status remains visible.
No replacement parking row, badge, toolbar, or recovery instruction appears.
Session tabs keep their current location; conversation content owns scrolling.
### UI-02: Phone, earlier conversation selected
```text
Task header
[Selected session v] -> existing Sessions picker
Conversation
Composer
[Chat | Plan | Changes | More]
```
The task drawer remains the entry point. The session picker selects the
conversation and normal recovery follows. The dedicated mobile layout, safe-area
navigation, and single chat scroll owner remain. No hover interaction is required.
UI-01 and UI-02 cover AC 001.9 and 003.8. Placement and absence of parking chrome
are requirements; text spacing is illustrative. Existing recovery errors, queue
waiting, loading, and explicit controls keep their established presentation.
The nearest mobile exemplar is `mobile-queued-session-ownership.spec.ts`.
## Test matrix
AC suffixes use `AC-TASKS-QUEUED-SESSION-OWNERSHIP-`.
| Case | Expected outcome | Criteria |
| --- | --- | --- |
| Primary or non-primary stopped conversation; valid or legacy parking; no pending work | Normal automatic recovery; no ownership transfer | 001.7, 001.9 |
| Consumed/unconsumed stop tombstone; malformed parking only | Parking history does not block recovery; callback correlation remains | 001.9 |
| Empty settled deferral, alone and with parking history | Recovery allowed; no settled prompt replay | 001.8, 001.9 |
| Same session already owns queued launch | No duplicate resume or prompt | 001.2; 002.4 |
| Different destination queued; selected sibling; free capacity | Selected conversation recovers; destination record and primary stay unchanged | 001.2, 001.5, 001.9 |
| Different destination queued; full capacity | No manual override or conflicting queue replacement | 001.2, 001.5; 002.6 |
| Auto-start prevention enabled | Open remains stopped; existing explicit Resume works | 001.5, 001.9 |
| Archive, explicit cancellation, completion, authorization failure | Existing lifecycle restrictions remain | 001.9 |
| Allowed status followed by conflicting queue or route change | Guarded admission rejects stale execution | 001.2, 001.6; 002.6 |
| Restart after switch or queue settlement | Conversation context survives; opening requests normal recovery | 001.7, 001.8, 001.9 |
| Desktop/phone with stored parking marker | No parked note; ordinary controls and real queue status remain | 003.8 |
## E2E tests
Update the existing desktop/mobile queued-session ownership specs and helper.
Replace assertions that require zero predecessor activation or a parked note.
Assert the selected conversation resumes without clicking Resume or sending a
message when preference and capacity permit. Capture `session_open` intent and
provider readiness; workspace readiness alone is insufficient.
Retain exact destination queue/prompt/primary assertions. Keep source message and
turn counts unchanged where open only restores provider context. Test free and
full capacity separately, including no manual override and no queue replacement.
Release only fixture capacity holders and assert one destination prompt delivery.
Use a workflow switch to establish real stopped-session metadata. Add restart
via the isolated `backend.restart()` fixture after leaving the target page.
Cover historical marker only, empty settled deferral only, and both together.
Verify same conversation identity and no prompt replay after recovery.
Use the existing phone task drawer and session picker at 393 px. Assert no
parking note, no document horizontal overflow, and usable existing controls.
Restore settings and runtime overrides in `finally`. No developer-instance restart
or direct production database mutation is authorized.
## Work orders
- [x] [Task 01: Restore ordinary recovery for workflow-stopped sessions](task-01-repair-session-open-eligibility.md)
- [ ] [Task 02: Remove parking presentation and verify user flows](task-02-remove-parking-presentation.md)
Order: 01 then 02. No subagents are authorized.
## Related delivery records
[Queued ownership](../queued-session-ownership/plan.md) is historical delivery
context. Its parked-suppression and parked-note scenarios are superseded by this
package. Exact queue ownership tests remain relevant. Keep historical verification
results and outstanding PostgreSQL checks unchanged; record new results here.
[Boot deadlock](../ceiling-boot-deadlock/plan.md) and
[replay/cancellation deadlock](../ceiling-replay-cancellation-deadlock/plan.md)
remain compatibility inputs. Do not change their lock order or claim fences.
## Documentation impact
Task 02 updates `docs/public/tasks-and-workflows.md` and
`docs/public/agents-and-profiles.md`: opening restores the selected conversation
under normal controls without changing workflow ownership. Remove instructions
that require a message solely because the workflow stopped the conversation.
No public behavior claim ships during this design-only turn.
## Verification results
Task 01 backend implementation is complete. Focused recovery, race, model/SQLite,
build, lint, and the full orchestrator package pass. The complete backend test
target still reports unrelated environment-sensitive failures in probe process
detection and home configuration discovery. Task 02 remains pending for the
frontend presentation removal and desktop/mobile E2E coverage.
## Risks
- Removing callback tombstones can let stale events affect a resumed execution.
- Opening a sibling can consume capacity, but must never steal its destination's queue.
- Hiding the note without changing eligibility leaves the reported defect intact.
- Tests that mistake workspace recovery for agent recovery give false confidence.
@@ -0,0 +1,131 @@
---
id: "01-repair-session-open-eligibility"
title: "Restore ordinary recovery for workflow-stopped sessions"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003
acceptance_criteria:
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.2
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.5
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.6
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.7
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.8
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.9
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.4
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.6
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.8
system_design:
- ../../specs/tasks/system-design/queued-session-ownership.md
---
# Task 01: Restore ordinary recovery for workflow-stopped sessions
## Summary
Remove workflow parking as an automatic recovery restriction. Preserve workflow
recipient isolation, automatic capacity admission, and execution-correlated callbacks.
## In scope
- Add `session_open_recovery_test.go` with `TestSessionOpenRecoveryEligibility`,
`TestSessionOpenRecoveryOwnership`, `TestSessionOpenRecoveryAfterRestart`, and
`TestSessionOpenRecoveryDelayedCallbacks`.
- Record RED for modern parking, historical stop intent, empty deferral, and their combination.
- Cover primary/non-primary sessions and the full backend matrix in the plan.
- Remove parking and stop-intent suppression from `autoResumeEligibility`.
- Accept empty settled deferrals; preserve nonempty queue validation.
- Exercise status, `passiveLaunchResponse`, `EnsureSession`, and real automatic admission.
- Verify full-capacity sibling recovery cannot replace another destination's accepted work.
- Audit parking-only writers/readers and remove unused helpers with their obsolete tests.
- Keep stop-intent tombstones, callback parsing, and independent lifecycle safeguards.
- Update old tests that assert parked-session suppression to assert the new contract.
## Out of scope
UI removal and rendered tests belong to Task 02. No live-data edits, migration,
new queue, manual ceiling override, workflow promotion, or prompt replay.
## Acceptance
1. Selected stopped conversations, including non-primary ones, recover under normal
rules regardless of workflow parking metadata. Empty settled records do not block them.
2. Actual queued destinations, guarded admission, capacity, archive, terminal, and
authorization rules remain intact. Opening a sibling never changes the queued prompt or primary.
3. Persisted restart tests and delayed-event tests pass without deleting stop tombstones.
Existing deadlock regressions remain green.
## Verification
Run from repository root. Add proposed tests before their commands.
```bash
(cd apps/backend && go test ./internal/orchestrator -run '^TestSessionOpenRecovery' -count=1 -timeout=120s)
(cd apps/backend && go test -race ./internal/orchestrator -run 'TestSessionOpenRecovery|TestAutoResumeEligibility|TestGetTaskSessionStatus|TestPassiveLaunchResponse|TestEnsureSession|Test.*WorkflowParking|Test.*ProfileSwitch|Test.*WorkflowRoute|TestCeilingReplay' -count=1 -timeout=300s)
(cd apps/backend && go test ./internal/task/models ./internal/task/repository/sqlite -run 'Test.*WorkflowParking|Test.*DeferredLaunch' -count=1 -timeout=120s)
git diff --check
```
Use real repository persistence and startup reconciliation for restart coverage.
Test queue/route replacement between status and launch with deterministic barriers.
Audit existing test names when removing policy-only code so remaining behavior
still has coverage. If dialect-sensitive repository behavior changes, add its
PostgreSQL conformance command before completion. No database migration is planned.
## Files likely touched
- `apps/backend/internal/orchestrator/task_operations.go`.
- `apps/backend/internal/orchestrator/session_launch.go` and `session_ensure.go`.
- `apps/backend/internal/orchestrator/workflow_session_target.go`.
- `apps/backend/internal/orchestrator/workflow_profile_session_lifecycle.go`.
- New `apps/backend/internal/orchestrator/session_open_recovery_test.go`.
- Existing resume, launch, ensure, workflow-target, and profile-switch tests.
- Parking-only model/repository helpers and tests, only if the consumer audit proves them unused.
## Dependencies
None. This replaces the prior work order's current-primary-only exception.
## Risks
Stop-intent metadata is not disposable parking UI state. Preserve event identity
checks. A queue conflict must not silently redirect a selected conversation.
## Parallelism
`sequential`
## Inputs
- [Plan and matrix](plan.md).
- [Design](../../specs/tasks/system-design/queued-session-ownership.md#conversation-recovery-and-workflow-stop-history).
- [Decision](../../decisions/2026-09-18-session-open-resumes-conversation.md).
- Existing automatic admission, workflow reuse, and stop-event regression fixtures.
## Results
Implemented the backend recovery contract.
- `autoResumeEligibility` no longer treats workflow parking or historical stop-intent metadata as passive recovery ownership.
- Empty settled `deferred_launch` objects are accepted as having no pending launch.
- Nonempty deferred launches still require valid destination identity and retain the queued-destination guard.
- The guarded `session_open` admission boundary rechecks current queue ownership under the existing lifecycle and ceiling-entry lock order. A capacity conflict with another accepted destination returns a successful waiting disposition and preserves that record.
- Added real `LaunchSession` coverage for status-driven recovery, free/full capacity, unchanged queued recipient and prompt, primary preservation, no manual override, no runtime launch on refusal, and queue replacement after the initial eligibility read.
- Added restart, successor-execution delayed-callback, stale-ownership, status, passive-launch, `EnsureSession`, primary/non-primary, parking, stop-intent, and settled-record regressions in the split session-open recovery test files. The delayed-callback case keeps an active successor turn and verifies route, primary, execution, and accepted queue state across service reconstruction.
- Updated existing resume and passive-launch tests to preserve the new recovery contract while retaining stop tombstones and parking lifecycle behavior.
Verification:
- Passed `go test ./internal/orchestrator -run '^TestSessionOpenRecovery' -count=1 -timeout=120s`.
- Passed the prescribed orchestrator race matrix.
- Passed the workflow parking and deferred-launch model/SQLite checks.
- Passed `make -C apps/backend build` and `make -C apps/backend lint`.
- Passed the full `internal/orchestrator` package.
- `git diff --check` passed.
- The complete backend test target reported unrelated environment-sensitive failures in `internal/agentctl/server/process/probe`, `internal/common/config`, and `internal/launcher`; all other reported packages, including the changed package, passed.
Task 02 owns the frontend parking presentation removal and desktop/mobile E2E coverage.
@@ -0,0 +1,125 @@
---
id: "02-remove-parking-presentation"
title: "Remove parking presentation and verify user flows"
status: pending
wave: 2
depends_on: ["01-repair-session-open-eligibility"]
plan: "plan.md"
requirements:
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-002
- REQ-TASKS-QUEUED-SESSION-OWNERSHIP-003
acceptance_criteria:
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.2
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.5
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.6
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.7
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.8
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.9
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.4
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-002.6
- AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.8
system_design:
- ../../specs/tasks/system-design/queued-session-ownership.md
---
# Task 02: Remove parking presentation and verify user flows
## Summary
Remove the parked-session note from shared chat surfaces. Verify selected
conversation recovery on desktop and phone without adding another parking affordance.
## In scope
- Remove `ParkedSessionNote`, `hasWorkflowParkingMarker`, and their chat-panel use.
- Remove `parkedSessionNote` from en, pseudo, pt-pt, zh-cn, zh-hk, and zh-tw catalogs.
- Replace parked-note tests with absence and ordinary-conversation-control assertions.
- Update desktop/mobile queued-session E2E scenarios according to the plan matrix.
- Verify restart, capacity, prevention preference, conversation identity, and no prompt replay.
- Update both public recovery explanations and affected companion-plan references.
## Out of scope
No replacement parking toolbar, badge, tooltip, or composer restriction. Genuine
queue status remains. No new layout, navigation hierarchy, or automatic prompt.
## Acceptance
1. No parking presentation appears on desktop or phone, including with persisted legacy markers.
2. Opening the selected stopped session resumes it when normal controls permit,
without clicking Resume. Queue ownership and workflow primary remain unchanged.
3. Both E2E projects and targeted UI/i18n checks pass. Public docs describe the new behavior.
## ASCII UI preview
Use [UI-01 and UI-02](plan.md#ascii-ui-preview).
```text
Desktop: [Session tabs] -> Conversation -> Composer
Phone: [Session picker] -> Conversation -> Composer -> Bottom navigation
Both: No parked-session row; genuine task queue status remains when needed.
```
Keep the existing desktop tabs and phone task drawer/session picker.
The chat remains the single conversation scroll owner. No new touch targets or
hover-only behavior are introduced. Cover AC 001.9 and 003.8.
## Verification
Run from repository root; install dependencies once for a fresh worktree.
```bash
(cd apps && pnpm install --frozen-lockfile)
(cd apps/web && pnpm exec vitest run components/task/launch-queue-status.test.tsx hooks/domains/session/use-session-resumption.test.ts)
(cd apps/web && pnpm run typecheck)
(cd apps/web && pnpm run i18n:check)
(cd apps/web && pnpm e2e:run --project chromium tests/workflow/queued-session-ownership.spec.ts)
(cd apps/web && pnpm e2e:run --project mobile-chrome tests/workflow/mobile-queued-session-ownership.spec.ts)
python3 scripts/list-docs.py validate
python3 scripts/lint-spec-files.test.py
python3 scripts/lint-spec-files.py --all
git diff --check
```
Run E2E projects sequentially with the managed runner. Use only disposable fixture
backends and scoped settings. Record behavioral RED before the UI removal and
GREEN after it. Backend Task 01 owns recovery RED; these tests prove integration.
Assert provider readiness independently of workspace-only readiness.
## Files likely touched
- `apps/web/components/task/launch-queue-status.tsx` and its test.
- `apps/web/components/task/task-chat-panel.tsx`.
- `apps/web/hooks/domains/session/use-session-resumption.test.ts`.
- `apps/web/src/locales/{en,pseudo,pt-pt,zh-cn,zh-hk,zh-tw}/task.json`.
- `apps/web/e2e/tests/workflow/queued-session-ownership.spec.ts`.
- `apps/web/e2e/tests/workflow/mobile-queued-session-ownership.spec.ts`.
- `apps/web/e2e/tests/workflow/queued-session-ownership-helpers.ts`.
- `docs/public/tasks-and-workflows.md` and `docs/public/agents-and-profiles.md`.
- This package's status/results and companion references.
## Dependencies
Task 01. It supplies the backend behavior required by the rendered tests.
## Risks
A hidden note is insufficient if recovery still fails. Do not remove the genuine
queue region or confuse workspace restoration with a resumed provider session.
Restore fixture capacity and user settings even when assertions fail.
## Parallelism
`sequential`
## Inputs
- [Plan, previews, and E2E matrix](plan.md).
- [Design](../../specs/tasks/system-design/queued-session-ownership.md#conversation-recovery-and-workflow-stop-history).
- Existing phone queued-session E2E fixture and session page object.
- Mobile parity and E2E skills; public docs maintenance guidance.
## Results
Pending. No frontend, E2E, or implementation check ran during planning.
@@ -11,8 +11,8 @@ owners:
## 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,
Users can open earlier conversations with normal recovery behavior without
changing the selected workflow recipient 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).
@@ -35,16 +35,17 @@ destination entry, whereas a ceiling can delay a session already selected by ent
### REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001: Inspection preserves execution ownership
**Intent:** Reading a task must not wake its retired workflow participant.
**Intent:** Opening a conversation resumes it without transferring workflow ownership.
#### 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.1:** Superseded by criterion 001.9
under [the session-open decision](../../../decisions/2026-09-18-session-open-resumes-conversation.md).
The original prohibition on resuming workflow-stopped conversations no longer applies.
- **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.
queue time, primary ownership, and workflow step. It shall not duplicate or
replace that accepted launch. A different conversation can recover under 001.9.
- **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.
@@ -54,10 +55,26 @@ destination entry, whereas a ceiling can delay a session already selected by ent
- **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
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.6:** If pending launch ownership cannot be
established, passive inspection shall remain available without starting work
or attempting a fresh-session fallback. Explicit recovery shall remain separately available.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.7:** After a workflow legitimately
reuses a conversation, historical workflow stops shall not prevent its otherwise
eligible recovery after restart. Desktop and phone task opening shall honor
the existing auto-start preference and automatic capacity limit. Pending launch ownership shall still prevent duplicate execution.
Workflow parking shall not independently block recovery.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.8:** After a deferred launch settles
and no pending launch remains, queue history shall not block otherwise eligible
open-time recovery. Recovery shall preserve the conversation and shall not
replay the settled workflow prompt.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-001.9:** Opening or selecting a
workflow-stopped conversation shall use normal automatic recovery, including
non-primary conversations. Recovery shall honor auto-start prevention, capacity,
authorization, archive, and terminal-session rules. It shall preserve conversation
context without sending a new prompt or transferring workflow ownership.
### 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.
@@ -99,9 +116,8 @@ destination entry, whereas a ceiling can delay a session already selected by ent
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.3:** Superseded by criterion 003.8.
The original requirement to identify a conversation as parked no longer applies.
- **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
@@ -128,6 +144,11 @@ destination entry, whereas a ceiling can delay a session already selected by ent
After a limit change, the banner shall show refreshed state without a page
reload and remain until dispatch or final disposition is confirmed.
- **AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.8:** Desktop and phone conversation
surfaces shall not show a parked-session banner, toolbar, badge, or recovery
explanation. Normal conversation controls shall remain available. Genuine queued
destinations shall retain queue status without a duplicate Start or Resume action.
## Compatibility and exclusions
Actual manual starts and resumes retain the existing ceiling override. This
@@ -21,7 +21,7 @@ and task-owned deferred record. It does not replace either queue or change WIP a
| Requirement | Sections |
| --- | --- |
| REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001 | Inspection intent; Durable workflow parking |
| REQ-TASKS-QUEUED-SESSION-OWNERSHIP-001 | Inspection intent; Conversation recovery and workflow stop history |
| 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 |
| REQ-TASKS-WORKFLOW-CANCELLED-TURN-COMPLETION-001 | Replay and reconciliation locking |
@@ -52,8 +52,9 @@ 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
Keep `launch_queued` and `ownership_unavailable` for pending launch restrictions.
Stop emitting `workflow_parked`; tolerate older payloads during compatibility handling.
Absence means no pending launch restriction. Keep
`is_resumable` for explicit recovery. Populate this through one orchestrator
eligibility helper shared by status, launch, and open-time ensure paths.
@@ -71,40 +72,64 @@ successful no-execution disposition, proposed `activation_disposition:
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
For a recoverable conversation, including a workflow-stopped predecessor, 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.
or add a conflicting resume record. An admitted sibling recovery can proceed.
A capacity refusal preserves the accepted record and returns the existing queued
disposition without pretending the selected sibling started.
## Durable workflow parking
## Conversation recovery and workflow stop history
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.
Follow [the session-open decision](../../../decisions/2026-09-18-session-open-resumes-conversation.md)
and AC 001.9/003.8. Opening an earlier conversation is sufficient to request
normal automatic recovery. No primary-role or committed-route exception is needed
solely because the conversation was stopped by a workflow switch.
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.
Remove `workflow_parking` and workflow-switch stop-intent checks from
`autoResumeEligibility`. Neither valid, consumed, unconsumed, nor malformed
legacy parking data grants or denies recovery. Task/session authorization,
archive, terminal state, error recovery, and actual pending launch ownership
remain separate checks. Do not infer a new prompt from session opening.
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 execution-stamped stop-intent parsing and consumed tombstones in the
callback path. They reject delayed events for the old execution. Removing
parking as an activation restriction does not remove event correlation.
Inspect every `workflow_parking` consumer and producer. Remove policy-only
helpers and writes when unused; retain any code with a demonstrated independent
lifecycle purpose. Existing metadata remains inert without a schema migration
or destructive backfill. Do not remove background-work or Office parking.
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.
The shared eligibility predicate still handles deferred launch metadata. Absent,
null, or empty objects represent no pending launch. `stripCeilingRecordKeys`
produces empty objects after settlement. Preserve nonempty-record validation;
do not weaken the replay parser to fix status inspection.
A queued destination remains owned by its accepted launch. Opening it cannot
create another resume. A different selected session can recover with available
capacity, without primary promotion, route changes, or destination prompt delivery.
If capacity is full, preserve the existing deferred record. Do not overwrite it
with the selected sibling's automatic resume. Existing retry and queue mechanisms
remain authoritative; no second task queue or hidden polling loop is introduced.
Status and launch must agree about these rules. Recheck actual queue ownership
at guarded admission after an allowed status response. New parking metadata alone
cannot cancel that permission; a conflicting accepted launch still can.
Keep the lock order and claim fences described below. No runtime call occurs
under task admission.
The browser retains `activation_source=session_open`, preference handling, and
ordinary recovery states. Remove `ParkedSessionNote`, its marker projection, and
its use in `task-chat-panel.tsx`. Remove the unused `parkedSessionNote` locale key
from all catalogs and update affected tests. Do not replace it with another
banner, chip, tooltip, disabled composer, or hidden recovery requirement.
The genuine `TaskLaunchQueueStatus` surface remains task-scoped.
Desktop keeps its session tabs above chat. Phone keeps its existing session
picker and one conversation scroll area. Both show ordinary recovery after open.
Test provider readiness independently of workspace-only readiness. Recovery must
preserve context without resending an interrupted or settled workflow prompt.
## Deferred entry ownership
@@ -148,7 +173,7 @@ 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
When a sibling resumes on open or explicitly runs, 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.
@@ -276,14 +301,15 @@ existing background-work indicators remain intact. No ordinal position is shown.
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
scroll. Do not show a parking-specific note for the selected conversation. 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.
The user can open Astra while Luna remains named in the task queue region.
Astra follows normal recovery without taking over Luna's workflow ownership.
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,
@@ -333,7 +359,7 @@ inspection. Localize labels and link text in all five catalogs.
## Failure and observability
Emit structured, bounded reason codes for suppressed inspection, queued replay,
Emit structured, bounded reason codes for suppressed duplicate launch, 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.
@@ -346,6 +372,7 @@ or claim its cause without a failing regression.
## Related records
- [Passive inspection decision](../../../decisions/2026-09-16-passive-session-inspection.md)
- [Conversation recovery decision](../../../decisions/2026-09-18-session-open-resumes-conversation.md)
- [Historical 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)
@@ -488,9 +488,12 @@ 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
records the original parking and admission implementation through the paired
[inspection and queue design](queued-session-ownership.md). Stop-intent
tombstones remain callback evidence, not current activation policy.
The [session-open decision](../../../decisions/2026-09-18-session-open-resumes-conversation.md)
supersedes parking-based recovery suppression. Opening an earlier conversation
follows normal recovery without changing its workflow recipient role.
- [Explicit session targeting](../../../plans/workflow-session-targeting/plan.md)
- [Same-profile fresh-session repair](../../../plans/workflow-same-profile-new-session/plan.md)