146 lines
7.6 KiB
Markdown
146 lines
7.6 KiB
Markdown
# 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.** 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.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.
|