From e9abe208bf5d1ffbbb5e8d6b0df187d97d89b1d9 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 8 Jul 2026 08:20:13 +0000 Subject: docs: add design spec for recursive story decomposition Retro from the tasks-board build (see memory claudomator-sequential-orchestration-gap): claudomator has no native way to drive a stated intent through review-gated implementation to arbitrary depth -- that role was played by a human/chatbot controller hand-dispatching and re-dispatching all session. Design converged through several rejected drafts (a separate "chain mode" pipeline, a distinct Planner role) before landing on the actual shape: one role, one recursive decompose-or-evaluate mechanism, applied at every depth, with arbitrated multi-evaluator review running at every node instead of once at a story's outer level. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD --- ...6-07-08-recursive-story-decomposition-design.md | 194 +++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-08-recursive-story-decomposition-design.md (limited to 'docs') diff --git a/docs/superpowers/specs/2026-07-08-recursive-story-decomposition-design.md b/docs/superpowers/specs/2026-07-08-recursive-story-decomposition-design.md new file mode 100644 index 0000000..5810ad2 --- /dev/null +++ b/docs/superpowers/specs/2026-07-08-recursive-story-decomposition-design.md @@ -0,0 +1,194 @@ +# Recursive Story Decomposition — Design Spec + +**Date:** 2026-07-08 +**Status:** Approved + +--- + +## Goal + +Give claudomator a native way to drive a stated user intent through review-gated +implementation, to arbitrary depth, without a human or chatbot controller +hand-orchestrating dispatch → review → fix → advance the way this session's +tasks-board build required. Replace that hand-driven loop with one recursive, +uniform mechanism inside claudomator itself. + +--- + +## Context + +Building claudomator's own "unified Tasks board" web feature (2026-07-06/08) +used an adapted `subagent-driven-development`: a human-facing plan document +with 8 sequential tasks, each dispatched to claudomator as an implementer, +each reviewed by a separately-dispatched local reviewer, fixes re-dispatched +on failure, advanced by hand. It worked, but the retro on it surfaced a real +architecture gap (see memory `claudomator-sequential-orchestration-gap` for +the narrative): `internal/scheduler.StoryOrchestrator` already automates a +Builder → 4-parallel-Evaluators → Arbitration → `REVIEW_READY` pipeline, but +that pipeline runs exactly once, at a story's outer level, for one atomic +deliverable. It does not decompose, and it does not apply itself recursively +to sub-pieces of a larger story. + +Two early design drafts for closing this gap were rejected during +brainstorming, for reasons worth recording so they aren't re-proposed later: + +- **A separate "chain mode" with a distinct sequential pipeline** (single + evaluator per task, `Story.Mode` field) was rejected as unnecessary + special-casing — two pipelines selected by a flag, instead of asking why + the one that already exists (arbitrated multi-evaluator review) shouldn't + just apply everywhere. +- **A distinct "Planner" role that runs once, up front, and hands off to a + different "Implementer" role** was rejected for the same reason — it + special-cases "decide how to break this down" as something only one kind + of agent does at one point in time, rather than a judgment every task + makes about its own assignment, recursively. + +The design below is the corrected shape: one role, one recursive mechanism, +applied at every depth. + +--- + +## Design + +### Story is the root of a tree, and only the root + +A Story represents user intent — what a human (or a chatbot acting for one) +asked for. It carries `Spec` (prose intent) and `AcceptanceCriteria`, and +owns the single human-facing accept-gate +(`POST /api/stories/{id}/accept`, unchanged from today). Nothing beneath the +root is a Story. A human's attention lives at this level; an agent driving +the work via MCP may need to inspect or adjust individual tasks at any +depth, but that's a different altitude of view onto the same tree, not a +different data model. `Epic` (grouping multiple Stories) is untouched by +this design — orthogonal, already exists. + +### Every Task, at every depth, gets identical treatment + +There is one role (`builder` — already named in existing seed data, unchanged +purpose: deliver work meeting its assignment's completion criteria). On +dispatch, a `builder`-role task is handed its assignment (a story's +`Spec`/`AcceptanceCriteria` if it's the root, or a parent task's own stated +subtask instructions/`AcceptanceCriteria` otherwise) and makes one judgment: +**is this minimal and targeted?** + +- **Yes:** implement directly, commit, done — this node's "work" is its own + diff. +- **No:** decompose. Spawn subtasks via `spawn_subtask`, each carrying its + own `Instructions` and `AcceptanceCriteria` (the field already exists on + `Task`, mostly unused today). This node becomes a roll-up; its "work" is + the aggregate of its children once each has independently completed. + +This recurses to arbitrary depth. A subtask that itself judges its own +assignment too large repeats the same decision. Depth is driven by how big +the actual work is — a well-scoped leaf doesn't decompose and is evaluated +once, same as any leaf; there's no depth ceiling imposed by the mechanism +itself (see Non-Goals for the cost implication). + +This judgment has to actually be instructed, not just structurally +possible: the `builder` role's `RoleConfig.SystemPrompt` needs to be +written (or updated, if a draft already exists from prior seeding) to tell +the agent explicitly to make this decompose-or-implement call against its +own assignment, and to use `spawn_subtask`'s new `DependsOn` field when the +pieces it's creating have a genuine order dependency rather than being +independently parallel. Without this, the mechanism is inert plumbing — the +plan for this design must include writing/updating that prompt as a real +deliverable, not an assumed side effect of adding the data-model fields. + +### `SubtaskSpec` gains `DependsOn` + +Today, `agentchannel.SubtaskSpec` (`Name`, `Instructions`, `Model`, +`MaxBudgetUSD`, `Role`) has no way to express ordering between siblings — +every spawned subtask is structurally parallel, and `storeChannel.SpawnSubtask` +never sets `DependsOn` on the child it creates. Add `DependsOn []string` to +`SubtaskSpec`, wired through to `child.DependsOn` the same +backward-compatible-optional-field way `Role` was added ("every +pre-existing caller unchanged"). `spawn_subtask` already returns each new +task's ID, so a decomposing agent can spawn step 1, capture its ID, spawn +step 2 with `depends_on: [step1_id]`, and so on — or spawn three independent +branches followed by one integration task depending on all three. One +primitive expresses pure-sequential, pure-parallel, or mixed DAGs; nothing +needs to pick a mode. This reuses 100% of the existing `DependsOn` +dispatch-gating and cascade-fail-on-dependency-failure machinery already +built for top-level tasks — no new scheduling logic. + +### Arbitrated review runs at every node, not once at the story's outer level + +The existing Builder → 4-Evaluators → Arbitration pipeline generalizes: once +*any* node's own work is done (a leaf's direct commit, or a roll-up's +children having all independently completed), that node goes through the +same multi-angle evaluation against its own `AcceptanceCriteria` (the +story's, for the root; the parent-assigned criteria, for anything beneath +it). This is what catches integration problems that only exist at a roll-up +level and are invisible in any single child's own diff — exactly the shape +of the CSS cascade collision found in the tasks-board build's final +whole-branch review (a bug between Task 3's and Task 4's changes, invisible +in either task's own scoped review). Making arbitrated review the universal +mechanism means every roll-up gets that same integration check, not just +the outermost one, and not only when a human remembers to run one by hand. + +### The fix-and-re-evaluate loop has to become real, not stay a documented simplification + +Today, `StoryOrchestrator.finalizeArbitration` never actually parses the +Arbitration task's verdict — it always routes to `REVIEW_READY` regardless, +trusting a human to read the summary and manually set `NEEDS_FIX` if they +disagree (see `CLAUDE.md`'s own "Documented simplification" note on this). +That gap can't stay a simplification once arbitrated review is the +universal per-node mechanism instead of a rare final gate a human is +already watching for. This design requires: parse the verdict; on reject, +spawn a fix attempt (same node, same completion criteria) and re-run +evaluation; on approve, auto-proceed (matching today's existing +Builder/Evaluator/Arbitration auto-accept behavior). Repeated non-convergence +escalates through the `builder` role's `EscalationLadder` (existing +mechanism, same one `Scheduler` already uses for FAILED-task retry/escalate +— no new retry-cap concept to invent). + +### MCP surface: story-level tools, additive to what exists + +New: `create_story` (name, spec, acceptance_criteria, repository_url → +story_id), `get_story`, `list_stories`, `accept_story` (wraps the existing +`POST /api/stories/{id}/accept`). These didn't exist on chatbot MCP before — +today it's task-only. `submit_task` and the rest of the existing task-level +toolset are unchanged and remain the right choice for a genuinely +single-step ask where creating a story and running it through decomposition +judgment is unwarranted overhead. + +--- + +## Non-Goals / Deferred + +- **No depth or cost bound in this design.** Recursion is unbounded and + arbitrated review (4 parallel evaluators) runs at every node, which is a + real cost multiplier for a large, deep story — not addressed here. The + intended mitigation is architectural, not a cap: a leaf task that judges + itself minimal/targeted should default to a cheap/local model tier + (`LocalRunner`/tinyllama already exists for this; the `EscalationLadder` + already supports tiering from cheap to expensive), reserving expensive + models for roll-up judgment, arbitration, and escalation. This connects + directly to the same session's `--model` wiring fix + (`internal/executor/container.go`, commit `2d9ae7e`) — model selection + has to actually work before tiering leaf tasks to cheap models is + possible at all. Left as a distinct, future piece of work. +- **No resumable-orchestrator design here.** Retro item 4 + (resume after session/model failures) is deliberately deferred until this + design's discipline — durable, DB/event-backed state per node, no + ledger-in-a-file — is actually built and proven. Resumability is a + property this design should make easier, not a feature to add on top of + it separately. +- **Epic is untouched.** Grouping multiple Stories under an Epic is an + existing, orthogonal concept this design doesn't change. + +--- + +## Testing + +- `internal/scheduler`: tests for the generalized per-node arbitrated-review + branch (fake store, assert correct spawn/evaluate/fix/advance sequencing + at both leaf and roll-up nodes) — extending the existing + `StoryOrchestrator` test style. +- `internal/executor`: tests for `SubtaskSpec.DependsOn` wiring through to + the created child task (mirrors existing `Role`-field tests in the same + package). +- `internal/api`: tests for the new `create_story`/`get_story`/ + `list_stories`/`accept_story` chatbot MCP tools (mirrors existing + `chatbotmcp_test.go` conventions for the task-level tools). +- No browser/UI testing needed — backend and chatbot MCP only. -- cgit v1.2.3