# Waiting for Review — Task State — Design

**Date:** 2026-06-01
**Status:** Approved (brainstorming)
**Scope:** `ClaudeDo.Data` (TaskEntity, EF config + migration), `ClaudeDo.Worker` (TaskStateService, TaskRunner, QueueService, WorkerHub, ExternalMcpService), `ClaudeDo.Ui` (StatusColorConverter, TaskRowViewModel, views), CLAUDE.md docs

## Problem

A successful task run currently transitions straight to `Done` and is considered complete. There is no gate for a human (or another agent) to review the result before it is accepted. We want review to be a mandatory step: after a successful run a task waits for an explicit approval, and a reviewer can send it back with feedback for another turn.

## Goals

- Add a `WaitingForReview` lifecycle state that a task enters automatically after a **successful** run.
- Reviewer can **approve** (→ `Done`), **reject-and-re-run** (→ `Queued`, resuming the same Claude session with required feedback), **reject-and-park** (→ `Idle`), or **cancel** (→ `Cancelled`).
- Reject-and-re-run reuses the existing session-resume mechanism so the agent continues with full context.
- Both the desktop UI and the external MCP surface can perform review actions.

## Non-Goals

- No change to the failure path: a **failed** run still goes straight to `Failed`, never to `WaitingForReview`.
- No change to planning-phase finalization. A planning parent that generates child tasks keeps its current behavior and does **not** route through review. Only ordinary executable runs (`Running` → success) are affected.
- No change to worktree state flow (`Active | Merged | Discarded | Kept`).
- No change to the in-run auto-retry-on-failure behavior; only the *final* successful completion routes to review.

## Design

### 1. State machine

Changed/added transitions in **bold**:

| From | To | Trigger |
|---|---|---|
| Idle | Queued | enqueue (unchanged) |
| Queued | Running | queue picker claim (unchanged) |
| Running | **WaitingForReview** | **successful run (was → Done)** |
| Running | Failed | failed run (unchanged) |
| Running | Cancelled | cancel during run (unchanged) |
| **WaitingForReview** | **Done** | **approve** |
| **WaitingForReview** | **Queued** | **reject + required feedback → resume re-run** |
| **WaitingForReview** | **Idle** | **reject → park for manual edit** |
| **WaitingForReview** | **Cancelled** | **abandon an almost-done task** |
| Done \| Failed \| Cancelled | Idle | reset (unchanged) |

### 2. Data model

`ClaudeDo.Data`:

- `TaskStatus` enum (`Models/TaskEntity.cs`): add `WaitingForReview` after `Running`.
- EF string converter (`Configuration/TaskEntityConfiguration.cs`): map `WaitingForReview` ⇄ `"waiting_for_review"` (TEXT column, no schema constraint to change).
- New nullable column **`ReviewFeedback : string?`** on `TaskEntity`. Holds the reviewer's rejection comment until the re-run consumes it, then it is cleared. Persisted so it survives a worker restart and is visible to the UI.
- One EF migration: add the `review_feedback` column. No backfill — the new status value and column are only written going forward.

### 3. Worker — status transitions (`State/TaskStateService.cs`)

`TaskStateService` remains the sole owner of status writes. New/changed methods:

- `SubmitForReviewAsync(taskId)` — `Running` → `WaitingForReview`. Sets `FinishedAt` and `Result` exactly as `CompleteAsync` does today. Called by `TaskRunner` on success **instead of** `CompleteAsync`. (`CompleteAsync` is retained for the approve path.)
- `ApproveReviewAsync(taskId)` — `WaitingForReview` → `Done`.
- `RejectToQueueAsync(taskId, feedback)` — `WaitingForReview` → `Queued`. Rejects empty/whitespace feedback with a failed `TransitionResult`. Stores `feedback` in `ReviewFeedback`. Wakes the queue.
- `RejectToIdleAsync(taskId)` — `WaitingForReview` → `Idle`. Parks for manual editing; leaves `Result` intact, clears `ReviewFeedback`.
- `CancelAsync` — extend the allowed source states to include `WaitingForReview`.

Each transition broadcasts `TaskUpdated` as today. Invalid source states return a failed `TransitionResult` (no throw), matching existing convention.

### 4. Resume-aware re-run (`Queue/QueueService.cs`)

The queue picker still atomically claims a `Queued`, unblocked task (`UPDATE … SET status='running' … RETURNING *`). The `RETURNING` row already carries `ReviewFeedback`. After a successful claim, `QueueService` branches:

1. **`ReviewFeedback` set + latest run has a `SessionId`** → `TaskRunner.ContinueAsync(task, feedback)` — `--resume {sessionId}` with `feedback` as the next-turn prompt.
2. **`ReviewFeedback` set, no prior `SessionId`** (edge case) → `TaskRunner.RunAsync` with the feedback appended to the task prompt, so the comment is not lost.
3. **No `ReviewFeedback`** → normal `TaskRunner.RunAsync` (fresh session).

`ReviewFeedback` is cleared once consumed (single UPDATE), so a later re-run does not re-apply stale feedback.

### 5. External MCP surface (`External/ExternalMcpService.cs`)

- New tool **`review_task(taskId, decision, feedback?)`**, `decision ∈ {approve, reject_rerun, reject_park, cancel}`. `feedback` is required when `decision = reject_rerun` (validation error otherwise). Maps onto the `TaskStateService` methods in §3. This lets automation / other agents act as reviewers.
- `get_task_status_values` — add `WaitingForReview` with a description covering the four exit actions.
- `list_tasks` status-filter parsing and validation message — include `WaitingForReview`.
- `get_task` lifecycle description text — update to `Idle → Queued → Running → WaitingForReview → Done | Failed | Cancelled`.
- `update_task_status` stays restricted to `Idle` and `Queued`; all review decisions go through `review_task` (keeps the "set status freely" affordance and the review affordance distinct).

### 6. Worker hub (`Hub/WorkerHub.cs` + `Hub/HubBroadcaster.cs`)

New hub methods called by the UI, each delegating to `TaskStateService`:

- `ApproveReview(taskId)`
- `RejectReviewToQueue(taskId, feedback)`
- `RejectReviewToIdle(taskId)`

Cancel already exists. No new broadcast events — `TaskUpdated` covers it.

### 7. UI (`ClaudeDo.Ui`)

- `Converters/StatusColorConverter.cs`: add a `waiting_for_review` case. Snap to an existing color token from the scale; final visual pass is left to the user (per project convention — centralize/tokenize, user does the visual pass).
- `ViewModels/Islands/TaskRowViewModel.cs`: add `IsWaitingForReview` computed property and commands **Approve**, **RejectRerun**, **RejectPark**, **Cancel** (the last reuses the existing cancel command). Commands are enabled only when `Status == WaitingForReview`.
- Reject-Rerun opens a small flyout/dialog with a required multi-line feedback text box; on confirm it calls `RejectReviewToQueue(taskId, feedback)`.
- Wire the commands to the new SignalR client methods.

### 8. Docs

Update the status flow in:

- root `CLAUDE.md` — "Task status flow" line.
- `src/ClaudeDo.Data/CLAUDE.md` — TaskEntity status list.
- `src/ClaudeDo.Worker/CLAUDE.md` — status-model transition table.

## Testing

`ClaudeDo.Worker.Tests` (real SQLite + real git, existing harness):

- `SubmitForReviewAsync`: a successful run lands in `WaitingForReview`, not `Done`.
- `ApproveReviewAsync`: `WaitingForReview` → `Done`.
- `RejectToQueueAsync`: empty feedback rejected; valid feedback stored in `ReviewFeedback` and status → `Queued`.
- `RejectToIdleAsync`: → `Idle`, `Result` preserved, `ReviewFeedback` cleared.
- `CancelAsync` from `WaitingForReview` → `Cancelled`.
- Invalid source states (e.g. approve from `Idle`) return a failed `TransitionResult`.
- Resume-aware re-run: a task with `ReviewFeedback` + a prior `SessionId`, when claimed, resumes the session with the feedback as the prompt and clears `ReviewFeedback`.
- `review_task` MCP tool: each decision maps to the correct transition; `reject_rerun` without feedback errors.

## Open questions

None outstanding. Planning-task exclusion (Non-Goals) is the one assumption to verify against the planning-finalization code path during implementation; if planning finalization shares `CompleteAsync`, route only the executable-run success site through `SubmitForReviewAsync`.
