# Unify the parent-task model (planning · improvement · normal) **Date:** 2026-06-09 **Status:** Approved-pending-implementation ## Problem ClaudeDo has three ways a task produces and waits on work, grown as separate mechanisms that represent the *same shape* — "a task runs, may emit children, and once it + its children are terminal it surfaces for review": | | children authored | scheduling | parent flow today | merge of children | |---|---|---|---|---| | **Normal** | none | — | `Running → WaitingForReview → Done` | own worktree on approve | | **Improvement** | autonomously *during* run (`suggest_improvement`) | parallel (no blockers) | `Running → WaitingForChildren → WaitingForReview → Done` | separate `MergeAllPlanning` | | **Planning** | interactively *before* run (planning session) | sequential chain (`BlockedByTaskId`) | `Idle →(Active→Finalized)→ Done` (skips review) | separate `MergeAllPlanning` | The incidental divergence we want to remove: 1. **Two "parent is waiting on children" representations** — improvement uses `Status=WaitingForChildren`; planning uses `PlanningPhase=Finalized` with the parent's `Status` jumping `Idle → Done`, never passing through the waiting/review states at all. 2. **Two parent-advance methods** doing the same job — `TaskRepository.TryCompleteParentAsync` (planning → `Done`, no review) vs `TaskStateService.TryAdvanceImprovementParentAsync` (improvement → `WaitingForReview`). 3. **A separate merge action** — `MergeAllPlanning` / `PlanningMergeOrchestrator` merges children, decoupled from the parent's `approve`. Approving a parent and merging its unit are two clicks. What is **genuinely unique and kept**: `PlanningPhase.Active` — the interactive, human-in-the-loop authoring gate where children are drafted and cannot run until finalize. Improvement has no equivalent. The two *authoring* entry points (`PlanningMcpService.CreateChildTask` vs `TaskRunMcpService.SuggestImprovement`) also stay distinct — they already share `CreateChildAsync`; unifying the authoring UX is explicitly out of scope. ## Decisions (locked) - **All parents get review.** A planning parent now surfaces in `WaitingForReview` after its children finish, instead of auto-completing to `Done`. - **Approve merges the whole unit — full UX consolidation.** Approve is the single entry for reviewing *and* merging any task. For a parent with children it drives the existing `PlanningMergeOrchestrator` (unit merge + parent→`Done` + conflict continue/abort, all already implemented); the standalone "Merge All" button is removed and the orchestrator's conflict dialog + combined-diff preview are reused in-place. Childless tasks keep `ApproveAndMergeAsync`. - **Scope = state model + code paths.** Internal refactor; authoring UX and child base-commit resolution are unchanged. ## Target model **One parent-with-children lifecycle, used by every parent regardless of how its children were authored:** ``` ┌─ (no children) ──────────────┐ Idle → Queued → Running ──┤ ├→ WaitingForReview → Done └─ (has/spawns children) ─┐ │ (approve = │ │ merge unit) WaitingForChildren ─┘ │ │ │ (all children terminal) ───────┘ ``` Planning parent (never runs as an agent — it runs an interactive session): ``` Idle (PlanningPhase None) →[StartPlanning] Idle (PlanningPhase Active) ← authoring gate (KEPT) →[FinalizePlanning] WaitingForChildren (Finalized) ← children chain runs →[all children terminal] WaitingForReview →[approve] merge unit → Done ``` Children (planning **and** improvement) keep going straight to `Done` with no individual review; they accumulate on their branches and merge as a unit when the parent is approved. ### State machine after the change - `WaitingForChildren` is the **single** "parent waiting on children" state, used by both planning and improvement parents. - `WaitingForReview` is reached by every parent before `Done`. - `PlanningPhase`: `None | Active | Finalized` — unchanged; `Active` remains the authoring gate, `Finalized` marks "was a planning parent" and is set together with `Status=WaitingForChildren`. ## Code changes 1. **Single parent-advance path.** Rename `TaskStateService.TryAdvanceImprovementParentAsync` → `TryAdvanceParentAsync`; it already only checks `Status==WaitingForChildren` + "all children terminal" → `WaitingForReview` (with the failed/cancelled annotation on `Result`). It becomes the only path for both systems. - Handle **zero children**: a finalized planning parent with no children must go straight to `WaitingForReview` (today `TryComplete`/`TryAdvance` both `return` on `Count == 0`). 2. **Delete `TaskRepository.TryCompleteParentAsync`** (`TaskRepository.cs:477`) and its invocation in `TaskStateService.OnChildTerminalAsync`. Planning parents now advance via `TryAdvanceParentAsync` to `WaitingForReview` instead of `Done`. - Keep `_chain.OnChildFinishedAsync` (inter-child unblock — planning-only effect). 3. **`FinalizePlanningAsync`** (`TaskStateService.cs:289`) sets the parent `Status = WaitingForChildren` in the same update that sets `PlanningPhase = Finalized`. This happens before `SetupChainAsync` enqueues child[0], so the parent is in `WaitingForChildren` before any child can finish. 4. **Approve merges the unit.** `WorkerHub.ApproveReview` (and the MCP `ReviewTask` approve path): when the approved task has children, run `PlanningMergeOrchestrator` (parent worktree if `Active` + each `Done` child in order), then transition the parent to `Done`. On a child merge conflict, the parent stays in `WaitingForReview` (mirrors current single-task approve-conflict behavior). Retire the `MergeAllPlanning` Hub method + UI button. 5. **Allow cancelling a `WaitingForChildren` parent.** Add `WaitingForChildren` to the `CancelAsync` guard so a parent waiting on children can be cancelled (today it cannot — minor gap). 6. **Docs.** Fix the `WaitingForChildren`-missing drift in `src/ClaudeDo.Data/CLAUDE.md` and `src/ClaudeDo.Worker/CLAUDE.md`, and update the transition diagram + the root `CLAUDE.md` status-flow line to the unified model. ## Out of scope (unchanged) - Authoring UX: planning session vs `suggest_improvement` stay as two distinct entry points (both already call `CreateChildAsync`). - `WorktreeManager.ResolveBaseCommitAsync` base-commit divergence (planning children branch from list HEAD; improvement children from parent head) — left as-is. - Sequential-vs-parallel scheduling — already shared infrastructure (`BlockedByTaskId`); planning chains, improvement doesn't. No change. ## Risks / edge cases - **Ordering on finalize** — parent must be `WaitingForChildren` before the first child can reach terminal. Guaranteed by setting it inside `FinalizePlanningAsync`, which runs before `SetupChainAsync`. - **Zero-children planning parent** — must advance to `WaitingForReview`, not stick in `WaitingForChildren`. Explicit branch in `TryAdvanceParentAsync` / `FinalizePlanningAsync`. - **Failed/cancelled children** — parent still advances to `WaitingForReview` with the existing `⚠ Children: N failed, M cancelled` annotation; no wedge. - **Approve-merge conflict** — keep parent in `WaitingForReview`; surface the conflicting child like the current merge-conflict path. - **Existing rows** — planning parents currently sitting at `Idle`+`Finalized` with live children: behavior change is forward-only (new finalizes use the new flow); no migration needed since `Status`/`PlanningPhase` columns already exist.