7.6 KiB
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:
- Two "parent is waiting on children" representations — improvement uses
Status=WaitingForChildren; planning usesPlanningPhase=Finalizedwith the parent'sStatusjumpingIdle → Done, never passing through the waiting/review states at all. - Two parent-advance methods doing the same job —
TaskRepository.TryCompleteParentAsync(planning →Done, no review) vsTaskStateService.TryAdvanceImprovementParentAsync(improvement →WaitingForReview). - A separate merge action —
MergeAllPlanning/PlanningMergeOrchestratormerges children, decoupled from the parent'sapprove. 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
WaitingForReviewafter its children finish, instead of auto-completing toDone. - Approve merges the whole unit. Approving a parent merges the parent worktree
(if any) + all
Donechildren in order, reusingPlanningMergeOrchestrator. The standaloneMergeAllPlanningbutton is retired (folded into approve). - 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
WaitingForChildrenis the single "parent waiting on children" state, used by both planning and improvement parents.WaitingForReviewis reached by every parent beforeDone.PlanningPhase:None | Active | Finalized— unchanged;Activeremains the authoring gate,Finalizedmarks "was a planning parent" and is set together withStatus=WaitingForChildren.
Code changes
-
Single parent-advance path. Rename
TaskStateService.TryAdvanceImprovementParentAsync→TryAdvanceParentAsync; it already only checksStatus==WaitingForChildren+ "all children terminal" →WaitingForReview(with the failed/cancelled annotation onResult). It becomes the only path for both systems.- Handle zero children: a finalized planning parent with no children must go
straight to
WaitingForReview(todayTryComplete/TryAdvancebothreturnonCount == 0).
- Handle zero children: a finalized planning parent with no children must go
straight to
-
Delete
TaskRepository.TryCompleteParentAsync(TaskRepository.cs:477) and its invocation inTaskStateService.OnChildTerminalAsync. Planning parents now advance viaTryAdvanceParentAsynctoWaitingForReviewinstead ofDone.- Keep
_chain.OnChildFinishedAsync(inter-child unblock — planning-only effect).
- Keep
-
FinalizePlanningAsync(TaskStateService.cs:289) sets the parentStatus = WaitingForChildrenin the same update that setsPlanningPhase = Finalized. This happens beforeSetupChainAsyncenqueues child[0], so the parent is inWaitingForChildrenbefore any child can finish. -
Approve merges the unit.
WorkerHub.ApproveReview(and the MCPReviewTaskapprove path): when the approved task has children, runPlanningMergeOrchestrator(parent worktree ifActive+ eachDonechild in order), then transition the parent toDone. On a child merge conflict, the parent stays inWaitingForReview(mirrors current single-task approve-conflict behavior). Retire theMergeAllPlanningHub method + UI button. -
Allow cancelling a
WaitingForChildrenparent. AddWaitingForChildrento theCancelAsyncguard so a parent waiting on children can be cancelled (today it cannot — minor gap). -
Docs. Fix the
WaitingForChildren-missing drift insrc/ClaudeDo.Data/CLAUDE.mdandsrc/ClaudeDo.Worker/CLAUDE.md, and update the transition diagram + the rootCLAUDE.mdstatus-flow line to the unified model.
Out of scope (unchanged)
- Authoring UX: planning session vs
suggest_improvementstay as two distinct entry points (both already callCreateChildAsync). WorktreeManager.ResolveBaseCommitAsyncbase-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
WaitingForChildrenbefore the first child can reach terminal. Guaranteed by setting it insideFinalizePlanningAsync, which runs beforeSetupChainAsync. - Zero-children planning parent — must advance to
WaitingForReview, not stick inWaitingForChildren. Explicit branch inTryAdvanceParentAsync/FinalizePlanningAsync. - Failed/cancelled children — parent still advances to
WaitingForReviewwith the existing⚠ Children: N failed, M cancelledannotation; 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+Finalizedwith live children: behavior change is forward-only (new finalizes use the new flow); no migration needed sinceStatus/PlanningPhasecolumns already exist.