# 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.