Files
ClaudeDo/docs/superpowers/specs/2026-06-09-unify-parent-task-model-design.md
mika kuns 8f49ebb248 docs(worker): spec + plan for unifying the parent-task model
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:58:45 +02:00

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:

  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 actionMergeAllPlanning / 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. Approving a parent merges the parent worktree (if any) + all Done children in order, reusing PlanningMergeOrchestrator. The standalone MergeAllPlanning button 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

  • 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.TryAdvanceImprovementParentAsyncTryAdvanceParentAsync; 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.