Skip to content

feat(plan): decomposition-for-parallelism doctrine + deterministic plan-quality re-dispatch #290

Description

@randomm

Context

Nessie #604: an 8.6-second plan collapsed 6 enumerated findings into ONE workstream with ONE path; the developer sprawled to 11 files, looped 17 failed builds, burned 10.5M tokens, and died. The #582-586 oversized-diff incidents show decomposition is also the convergence fix. The pre-driver PM flow defaulted to parallel (up to 10 slots); inlinePlanPrompt (work-driver.ts:4413-4441) carries no decomposition doctrine, and runPlan accepts any parseWorkstreams result (:805) unchecked. Issue #280's addendum proposed counting enumerated findings but only warning — this issue absorbs and supersedes that addendum (one corrective re-dispatch instead of a warning).

Two-level accounting: K≤2 groups × M workstreams; develop peak = K×M children, lens peak = K×6 = 12 — all bounded by the spawn semaphore (PI_ENSEMBLE_SPAWN_CAP=8 FIFO); async-jobs MAX_JOBS=50 and dispatch MAX_PARALLEL=10 unchanged.

This work must ship as its own separate PR, independent of any other open issue.

What to build

A. inlinePlanPrompt overhaul (:4413-4441) — replace the doctrine block with: (1) ENUMERATE the issue's discrete findings/deliverables first (numbered/checkbox items are the default enumeration); (2) map EACH finding to its own workstream unless two genuinely require edits to the same files — independence criterion = disjoint file sets, nothing else; (3) every workstream MUST declare non-empty paths: and out-of-scope: fences; (4) bias TOWARD more workstreams (cite #604); unmapped findings go under an explicit Deferred: line; (5) floor: one workstream per genuinely separable file-set — a single-file 2-line fix is ONE workstream; target per-workstream diff ≤ ~150 lines.

B. Deterministic plan-quality check in runPlan after parseWorkstreams (:805): pure countEnumeratedFindings(body) counting top-level ^\s*(\d+[.)]|- \[[ x]\]) items against the issue body cached by explore (writeDispatchArtifact, :598). If findings ≥ 3 && workstreams === 1 → re-dispatch the plan ONCE with corrective steer ("the issue enumerates N findings; map each to its own workstream or list it under Deferred"). Independently, any workstream with paths.length === 0 → same single corrective re-dispatch ("declare paths per workstream" — paths drive the scope-fanout gate #285 and worktree independence). One combined retry max; second result is final; empty paths after retry are kept but recorded.

Schema (additive): pipelineState.planQuality?: { findingsCount: number; redispatched: boolean; reason?: "under-decomposed" | "empty-paths" }.
Knob: PI_ENSEMBLE_PLAN_QUALITY=0 disables the check (prompt doctrine stays).

Acceptance criteria

  • End-to-end runWorkDriver test: 6-numbered-finding body + dispatchFn returning 1 workstream then 4 → exactly 2 plan dispatches; planQuality.redispatched === true, reason under-decomposed.
  • 2-finding body → no re-dispatch. Second-result-is-final: dispatchFn returning 1 workstream twice → accepted, no third dispatch.
  • Empty-paths fixture → one re-dispatch, reason empty-paths; combined violation → still exactly one retry.
  • countEnumeratedFindings unit tests (numbered, checkbox, nested-indent exclusion).
  • PI_ENSEMBLE_PLAN_QUALITY=0 → zero re-dispatches.
  • tsc + biome + smokes green; docs updated (doctrine summary + knob).

Out of scope

Parallel groups/mutex; worktree mechanics; #280's main body (repeat-finding seam escalation, dead step-back); scope-fanout gate implementation (#285); grouping rules.

Test plan

Offline: fixtures above via dispatchFn injection. Live on nessie: /work on a multi-finding issue; verify plan yields ≥2 workstreams with non-empty paths, or a recorded corrective re-dispatch; compare per-workstream diff sizes against the ≤150-line target.


Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions