Decompose Spec
Turn one large spec into the fewest self-contained tickets that can each be implemented in a single
fresh context — then seed the durable build ledger that orchestrate-build drives to completion.
This is the planning half of a multi-session build. It does not implement anything, create branches, or run code. Its entire job is to answer "what are the units of work, in what order, and where do we cut?" — well, because the decomposition is the ceiling on the whole build: a bad split produces tickets with hidden cross-dependencies that no downstream rigor can recover from (the multi-session analogue of "the build is only as good as its spec"). Treat this as the highest-leverage, highest-rigor step, not a formality.
When to use: a spec too large for one focused implementation run, that you intend to ship as a chain of reviewable PRs across multiple sessions. For a spec that fits one sitting, skip this and run
implement-specdirectly. The per-ticket worker in both cases isimplement-spec.
The objective (what "optimal" means here)
Minimize the number of tickets N, subject to — for every ticket:
- It fits one fresh-context
implement-specrun with rigor headroom. The ticket's working set (its spec slice + the code it must read + the tests + the self-review/gap/verify passesimplement-specruns inside it) stays well under half the worker's context window. On thesubagentdispatch target this is a hard failure boundary — a subagent cannot compact, so a ticket that overflows fails. Onheadless/manualit is softer (the worker compacts, trading gap-analysis fidelity for survival). Size to the ceiling of the declareddispatch_target. - It is one coherent, independently landable concern — a single logical thing that stands alone as one CI-green PR, traceable to spec section(s). Soft proxies (not laws): ~one focused change, on the order of a few files and a few hundred lines. A large mechanical rename can be far bigger and still trivial; LOC is a proxy for cognitive size, not the target.
- It respects the dependency order — a ticket never forks from work that hasn't landed yet.
- Its boundaries fall on the weakest-coupling seams — cut where two units share the least (few cross-references, no shared implicit decisions), so each cut creates the fewest integration seams the capstone must later reconcile. High cohesion within a ticket; low coupling across.
The two bounds you are balancing:
- Under-factoring (tickets too big) → context rot: model accuracy degrades well below the window limit and accumulated irrelevant context hurts independently of raw length. This is the failure the fresh-context design exists to prevent; rule 1 is its guard.
- Over-factoring (tickets too small) → two costs. (a) Fragmented decisions: splitting one concern across tickets disperses implicit decisions (naming, a shared helper, a pattern) that then collide — the dominant failure mode of naive multi-agent coding. (b) Multiplied fixed cost: every ticket re-pays context bootstrapping (re-reading conventions, anchors, shared models) and every cut adds an integration seam and a PR to review.
So the balance is precise: make each ticket as large as rule 1 safely allows, then stop splitting the moment a
cut would sever a shared implicit decision. granularity only nudges where inside that safe band you land.
Phase 0: Read the spec and the ground truth
- Read the spec in full (and any companion docs it references). Extract four structured lists — you will
partition against these and hand them to the ledger:
- Requirements — every "shall / add / wire / implement" item, by section.
- Acceptance criteria — the spec's explicit ACs plus any implied "must hold" invariants.
- Cross-cutting invariants — the "every ticket must preserve" rules (back-compat/additive, wire-name stability, security invariants, "do not touch X"). These are the ACs most easily missed and belong at the top of the plan so every ticket re-checks them.
- Out-of-scope — what no ticket does (the scope-creep guard the capstone and each gap-analysis rely on).
- Read the repo's ground truth so tickets cut along real boundaries, not imagined ones: the AGENTS.md / CLAUDE.md hierarchy (root → subproject) for conventions and "ask-first" boundaries, and the spec's integration anchors (file:line) — open each so the partition respects the code's actual module structure. Anchors drift; record "re-confirm at build time" against each.
For a large spec, this reading is itself broad: you MAY fan out read-only subagents over subsystems (reading is safe to parallelize; only writes must stay single-threaded). Each returns a compact map of its area's seams and dependencies.
In mode=extend (a re-planning round on an existing build), also read the current build state before
cutting: docs/build/LEDGER.md (what has landed, chainTip, round), docs/tickets/DEFERRALS.md (owed work
that a new round may close), docs/research-ledger.md §Q (operator decisions), and docs/build/BACKLOG.csv
(carried debt). The new round's tickets fork from the current chainTip; you append to the manifest's chain
table and ## Plan extensions, and never rename an existing ticket file (BM-COMPAT-05).
Phase 1: Build the dependency + coupling map
Before cutting, understand the structure you are cutting:
- Dependency DAG — which requirements must land before which (data model before its consumers, an interface before its callers, a config key before the code that reads it). This fixes ordering and reveals the stacked-PR chain.
- Coupling seams — where the work naturally separates (module/package/layer boundaries, distinct files, distinct data flows) and, conversely, where units are tightly bound (they touch the same function, share a new helper, or encode the same design decision). The tight bindings are where you must NOT cut.
- Repo boundaries — units that live in a different repository are out-of-chain: they cannot stack on this repo's branch and must be tracked separately (they depend on the chain informationally, e.g. "needs the image name P2 publishes," not by git history).
Phase 2: Partition into the ticket plan
Apply the objective. Produce, as a table, one row per ticket:
| Ticket | Scope (spec §§) | Files/anchors | forks-from / PR-base | Acceptance (deterministic / agentic) | Tests |
Rules for filling it:
- Order by the DAG. Number
T1..Tnso everyforks-frompoints at a ticket that lands earlier (no forward references). Chained tickets fork from the previous ticket's branch (thechainTipat run time); the PR--baseis that same branch so each PR diff is exactly one ticket. - Mark out-of-chain rows (different repo / deliberately independent): they do not advance
chainTipand record their dependency as informational. - Include non-code rows where the build has them, marked as such. Two kinds, each written as a marker
file in the same global sequence (BM-TICKET-05, from
build-memory'stemplates/HUMAN.md/templates/GATE.md): human prerequisites (NN[a-z]_HUMAN-H<k>__<slug>.md— account registrations, outreach, procurement, work only the operator can do; listed so the chain never silently blocks on them) and milestone gates (NN[a-z]_GATE-G<k>__<slug>.md— a hard synchronization barrier or a pre-registered go/no-go, its thresholds quoted verbatim from the spec and recorded before the gated work begins). Neither is animplement-specinput: they do not advancechainTip. A gate is a pause, not a block — it is dispositioned by the operator (recorded in the ledger's GATE DECISIONS + areadouts/GATE-G<k>.md) and never guessed past; a pending gate is aRETURN PASSrow, not ablockedOn. - Emit skeleton tickets after a gate where the body cannot be written yet (BM-TICKET-03):
Kind: skeleton, a> Skeleton onlybanner naming the gate, and only the header +## Scope (one line)+## Spec §§+## REQ coverage. A skeleton carries no run line — the validator refuses to run one andimplement-specstops if asked to. - Classify acceptance per ticket. Is "done" deterministic (a unit/integration test, a byte-identity or behavioral check) or empirical/agentic (a repeat-scored metric on a benchmark set)? Agentic tickets need a benchmark fixture defined at SETUP — note it.
- Respect the ceiling from
dispatch_targetfor every row (Phase-4 review re-checks this explicitly). - Keep shared decisions inside one ticket. If two candidate tickets would both introduce or depend on the same new symbol/helper/convention, either merge them or make the first ticket own the decision and later tickets reference it — and note that dependency in the row.
Phase 3: Author per-ticket contracts (only where the spec lacks execute-grade ones)
implement-spec diffs its work against a contract. If the spec already states a precise per-ticket contract,
reuse it. Where it only gives loose scope, author the missing contract for each such ticket — concrete enough
that a fresh context can implement against it without re-deriving the design:
- Files/anchors touched (file:line, "re-confirm at build time").
- New symbols / models — types, endpoints, config keys; for anything persisted, its stable wire name and the additive/back-compat rule ("optional, default = today's behavior").
- Exact scope + what it must NOT do (the per-ticket scope-creep guard).
- Acceptance criteria — numbered, each independently checkable (a test, a diff, a measured shift); marked deterministic or agentic.
- Tests to write — unit (pure logic), integration (seams), and (agentic) the benchmark-harness run.
Keep the cross-cutting invariants and out-of-scope list from Phase 0 at the top of the contract set — every ticket's gap-analysis re-checks them. If the spec is so underspecified that contracts cannot be derived (no decomposition and no derivable design), that is design work, not decomposition: stop and tell the operator the spec needs a design pass first — do not invent a design.
Each contract is its own file, from build-memory's templates/ticket.md (BM-TICKET-01) — written to the
tickets directory (committed mode: docs/tickets; otherwise the gitignored scratch dir) as
NN[a-z]?_<ID>__<slug>.md when sequence_prefix is true (the global sequence, zero-padded so lexical order is
chain order), else the legacy T<nn>__<slug>.md / P<phase>.<k>__<slug>.md. It carries the template's full
header (sequence, phase, kind, base_branch: current checkout, depends-on, the literal Run: line, Gate status, Live stage) and body sections, ending with the universal phase-gate AC (BM-TICKET-02), so a fresh
worker loads one small file and nothing else. Two disciplines make the layout safe:
- Cite, don't copy. A ticket file cites the spec's sections and requirement IDs; it never restates the design — a restated design forks the spec, and the drift arrives with the first amendment. To change a requirement: amend the spec first, then the affected ticket's Load/AC lines.
- Stamp requirement IDs. Where the spec carries stable requirement IDs, each ticket lists the IDs it satisfies — to be stamped in its PR — and the Phase-4 coverage check runs on IDs: every in-scope ID maps to exactly one ticket, or to an explicit deferred table naming its target phase.
Phase 4: Adversarial review of the split (the quality gate)
The partition is the ceiling on the build, and the context that just authored it is a biased reviewer of it. Run a fresh-context critique — a subagent given only the spec, the plan, and the invariants where the harness supports subagents; otherwise the explicit discipline of re-reading the spec and plan from disk and arguing each judgment from what is there, not from memory of authoring it. Hunt specifically for:
- Fragmented decisions — any two tickets that secretly share an implicit decision (a name, a helper, a pattern). → merge them, or record the shared decision and assign ownership to the earlier ticket.
- Overflow risk — any ticket whose working set won't fit one fresh-context run at the declared ceiling. → split it at its next-weakest seam.
- Orphan seams — any integration point that no ticket owns because a cut created it. → assign it to a ticket or explicitly to the CAPSTONE.
- Coverage + scope — every spec requirement maps to exactly one ticket (nothing dropped, nothing double-owned); nothing in the out-of-scope list is scheduled.
- Ordering — no cycles, no forward
forks-fromreferences. - Over-factoring — trivially small adjacent tickets that share context → merge to reclaim the fixed cost.
Revise the plan until the critique is clean or the remaining tradeoffs are consciously recorded. The plan is a
starting point, not a contract in stone: orchestrate-build may split an overflowing ticket or merge trivial
ones at run time and write the change back to the ledger. Note that explicitly so a later session knows the plan
is revisable.
Phase 5: Seed the build memory
build-memory owns the layout; decompose-spec fills it. Everything below cites
skills/build-memory/layout.md and does not restate the tree. If ledger=false, skip every write and present
the plan + all sections to the operator instead.
1. Initialise the layout. Invoke the build-memory skill in init mode (idempotent — never overwrites an
existing file). In committed mode this creates docs/build/{README.md (with the marker), LEDGER.md, BUILD_INDEX.md, logs/.gitignore}, docs/adr/{README.md, _TEMPLATE.md}, docs/tickets/{_TEMPLATE.md, DEFERRALS.md}, the AGENTS.md build-memory section and the docs/README.md rows. A repo that has not opted
in stays in scratch mode — byte-for-byte the 0.1.x behaviour (a gitignored ${build_name}-build-ledger.md and a
scratch tickets dir); everything below still applies, minus the commit.
2. Write the state-only ledger (docs/build/LEDGER.md, from the template, BM-LEDGER-01/02). It holds
state only — the manifest is the plan. The CURRENT STATE fenced block carries exactly the keyset in layout
order (projectStatus … round … updatedAt); seed projectStatus: NOT_STARTED, nextTicket: SETUP,
manifest: docs/tickets/00_MANIFEST.md, canonicalSpec, memoryRoot, round: 1. Below it: empty
OPEN FINDINGS, GATE DECISIONS, RETURN PASS tables, and a PHASE LOG with one seed entry (spec, ticket
count, "the plan is revisable at run time"). No PHASE PLAN / SETUP / CAPSTONE sections — those moved to the
manifest (the plan) and to tail tickets.
3. Write the per-ticket contracts (Phase 3) and the manifest.
4. Write docs/tickets/00_MANIFEST.md from the template (BM-MANIFEST-01), the human-facing runbook that
makes the chain self-driving without the ledger: the three banners (committed contract record; cite-don't-copy
with the spec amendment protocol; deferrals companion), a companions: line and an optional req_id_pattern:
line, ## How to build (the four rules + the run-line pattern), ## Human prerequisites, ## The chain
(| # | file | phase | kind | scope | gate |, HUMAN/GATE marker and skeleton rows interleaved in order),
## Milestone gates, ## Phase gates & ownership notes, ## Cross-cutting invariants, ## Out of scope,
## Requirement-ID → ticket index, ## Spec amendments applied, ## Decomposition decisions (incl. the Phase-4
adversarial review record), ## Plan extensions.
5. Append the tail (BM-TAIL-01) when the chain has more than one implement-spec ticket: instantiate
build-memory's templates/tail/ — tail=full (CAP.1, CAP.2, CAP.3, GATE-ACCEPT, REC.1, REC.2, REC.3, DOC.1,
DOC.2) or tail=minimal (CAP.1, CAP.3, DOC.1, DOC.2) — filling the placeholders ({{build_name}}, {{spec_path}},
{{req_id_pattern}}, {{ticket_count}}, {{last_ticket}}). Each becomes an ordinary chain row (kind capstone
| reconcile | docs). tail=none is refused when N > 1. The capstone is tickets — there is no CAPSTONE ledger
section.
6. Persist the hand-off when this run was invoked from a docs/decomposition-prompt.md — keep that file as
the frozen record of the exact invocation and binding constraints.
7. Validate. Run bash skills/build-memory/scripts/check-build-memory.sh . — it must exit 0 (layout, ticket
grammar + unique sequence, manifest ↔ files, backward deps, skeletons w/o run lines, DEFERRALS, ADR index, ledger
keys, REQ coverage). A failure is a real block: fix the seed, don't hand off a red layout. In committed mode,
this is the state orchestrate-build commits at SETUP.
The spec amendment protocol
Ticket files cite the spec; they never copy the design (a restated design forks the spec, and the drift
arrives with the first amendment). To change a requirement: amend the source spec first; bump its
version/delta; requirement ids are append-only; add a ## Spec amendments applied manifest line (date,
section, before/after or pointer, approver); write an ADR when a design decision changes; then update the
affected tickets' Load/AC lines (executed contracts get an appended > Amended <date>: note, never a rewrite).
Hand off
Print a concise summary: the ticket count and the one-line scope of each, the chain shape (with out-of-chain rows called out), any conscious tradeoffs from Phase 4, the ledger path, and the exact next step:
▶ To build: run orchestrate-build with ledger=<path> (it will SETUP, then drive each ticket via implement-spec).
Also print the manual floor — it needs no orchestrator at all:
▶ Or by hand: in a fresh session, run implement-spec spec=docs/tickets/<first ticket file>
then, per docs/tickets/00_MANIFEST.md, chain the next file from the branch each ticket leaves checked out.
Each worker closes its own ticket in docs/build/LEDGER.md (committed mode), so the chain advances with no orchestrator.
Report the validator result (check-build-memory.sh exit 0) as part of the hand-off. Do not create worktrees,
branches, or run anything — that is orchestrate-build's job.
What this does NOT do
- No implementation, no branches, no worktrees, no execution — pure planning + build-memory seeding (the manifest, the state-only ledger, the tickets, the tail; it writes docs, never code or branches).
- No design authoring. It decomposes an authoritative spec; it does not invent a missing design (that's a hard stop in Phase 3).
- No runtime parameters (worktree path, pinned base, autonomy) — those are resolved by
orchestrate-buildat SETUP; the ledger leaves placeholders.