Decomposing It | WhiteFish Creative

Decomposing It

Spoke 2 of 6 in the spec-driven development series. Previous: Forming the Spec.

TL;DR. Decomposition has one rule: if a task can’t be verified on its own, it isn’t a task, it’s two tasks or it’s drawn wrong. Don’t split by size or by component, split by verifiability. Every task gets a one-sentence objective, the criteria it satisfies, its dependencies, an explicit out-of-scope line, and a flag where a human decision is needed. “Large” isn’t a size, it’s a warning that you haven’t thought about it yet. The whole point is that clean tasks make the last two stages, proving and auditing, possible at all. Mush makes them impossible.

You have a specification. It’s precise, it says what you’re not building, its acceptance criteria could be checked by a stranger. And it’s too big to build, because everything real is too big to build in one go. So you break it down, and how you break it down determines whether the next three stages work at all.

Decomposition sounds like project management, which is why people do it badly. They treat it as slicing: cut the big thing into smaller things of roughly equal size, assign them, track them on a board. That’s not what this is. Decomposition in a spec-driven flow has exactly one rule, and every other consideration is subordinate to it:

If a task cannot be verified on its own, it is not a task. It’s two tasks, or it’s badly drawn.

That’s it. Hold that and the rest follows. Ignore it and you get a task list that looks organised and proves nothing.

Why independent verifiability is the whole game

Think about what you’re going to do at the end. You’re going to take each acceptance criteria and ask: which piece of work satisfies this, and what proves it? If your tasks are drawn so that a criteria is spread across five of them, you can’t answer that question for any of the five. None of them satisfies the criteria. All of them, together, sort of do. “Sort of, together” is precisely the fog that spec-driven development exists to burn off, and a bad decomposition puts it right back.

When each task maps cleanly to the criteria it satisfies, verification becomes mechanical. This task claims to satisfy AC3 and AC4; here are two tests; they pass; the task is done in a sense you can actually defend. When tasks and criteria are tangled, “done” goes back to being a feeling, and we’ve already established what feelings are worth as engineering signals.

So you don’t decompose by size, or by component, or by which developer likes which area. You decompose by verifiability. The question for every proposed task is: could someone confirm this is finished without reference to any other unfinished task? If no, it’s drawn wrong. Redraw it.

The anatomy of a task worth the name

A task that can be picked up, by a person or an agent, and finished without a series of clarifying conversations needs a specific set of things, and each of them exists to prevent a specific failure.

An objective, in one sentence. What is true when this is done that wasn’t before. If the sentence needs an “and,” you have two tasks pretending to be one. “Add the login endpoint and the rate limiting” is two objectives, they’ll be tested differently, and welding them together means neither gets verified cleanly.

The criteria it satisfies, by ID, pointing back at the feature spec. This is the thread that makes the whole method hold together. Every task points at the criteria it exists to meet; every criteria is pointed at by some task; anything unpointed-at is either work you forgot or a criteria nobody’s going to build. Both are findings, and you want to find them now, on a list, not later, in production.

Its dependencies, explicitly. What must exist before this can start. Undrawn dependencies are how you discover, on the morning you planned to build task four, that it needed task two which needed a decision nobody’s made. Draw the graph. It takes ten minutes and it surfaces the sequencing problems while they’re still cheap.

What’s out of scope for this task specifically. This is the small-scale echo of the not-building list, and it matters most when an agent is doing the work. A task that says “build the login endpoint” without saying “not the rate limiting, that’s task three” is an invitation for a capable, literal thing to helpfully build the rate limiting too, badly, because it doesn’t have task three’s context, and now you have two implementations of the same thing fighting each other. Per-task scope boundaries aren’t bureaucracy. They’re the thing that keeps parallel work from colliding.

A flag for where a human is needed. Some tasks contain a decision that requires judgement, taste, or authority the task itself can’t have. “What should the default be for existing customers” is not a thing to let an agent decide, because it’s not a technical question. It’s a question about risk appetite and customer trust, and the answer belongs to a person who’ll own the consequences. Marking these means they get escalated instead of guessed.

Sizing, and the letter L

There’s a sizing convention I use, and it has three values, and one of them is a trap I’ve left in on purpose.

Small is one sitting, one concern, obvious when it’s done. Medium is about a day, might touch two areas, still one clear objective. And Large is not a size. Large is a warning. When a task comes out Large, it means nobody has actually thought about it yet. It’s a placeholder holding the space where several real tasks will go once someone does the thinking. The category exists on my template purely so that when you write “L” next to something, you recognise what you’ve done and go break it down.

A Large task will not be verified independently, because it can’t be. It’s too big to have a single clean objective, which means it fails the one rule that matters. Every Large task is a future argument about whether it’s done. Break it now, while breaking it is free.

The traps, concretely

A few patterns that look like tasks and aren’t, because naming them is more useful than describing the ideal in the abstract.

The task with “and” in its objective. Two tasks. Always. “Add the export button and wire up the CSV generation,” the button is one concern with its own criteria, the generation is another with different ones. Joined, they get one test that half-covers each. Split, they get two that fully cover both.

The task with no criteria. If a task doesn’t point at any acceptance criteria, ask why it exists. Sometimes the answer is legitimate, scaffolding, a migration, pure setup, and then you note that it’s verified by its own narrow tests rather than by a feature criteria. But often the answer is that it’s speculative work nobody agreed to, and it should go on the not-building list instead of the task list.

The task nobody but the author can verify. If confirming it’s done requires the person who built it to stand there and explain, it will be marked done and it will not be done, because the marking and the doing were never connected by anything a second person could check. Every task should be verifiable by someone who wasn’t there. If it isn’t, the criteria are too vague and you need to go back to the feature spec, not paper over it here.

The tasks that only make sense in sequence. If task three is incomprehensible without task two in your head, their dependency is real and undrawn. Draw it. The graph isn’t decoration; it’s the thing that tells you what can happen in parallel and what genuinely can’t.

What decomposition is actually protecting

Step back and notice what all of this is in service of. The rule about independent verifiability isn’t an aesthetic preference. It’s the thing that makes the last two stages, proving it and auditing it, possible at all.

If your tasks each map to specific criteria, then when the build is done you have a table: every criteria, the task that met it, the test that proves it. That table is the audit. It’s the thing that catches the criteria with a passing test that doesn’t actually test it, the feature that got built and points at nothing, the agreed behaviour that quietly never got made. None of that is available to you if the decomposition was mush, because there’s nothing to hang the audit on.

Bad decomposition doesn’t announce itself. The board looks fine. The tasks have names and sizes and assignees and they move left to right like tasks are supposed to. It only reveals itself at verification, when you try to prove a criteria is met and find the work for it smeared across six cards, none of which owns it. By then, redrawing is expensive. So you spend the ten minutes now, drawing tasks you can actually verify, and you draw the dependency graph, and you flag the human decisions, and it feels like overhead right up until the moment it’s the only reason you can prove anything at all.

Next, building without drift: what happens when the work starts, the agent gets going, and the gap between “what we specified” and “what’s being built” starts to open, quietly, helpfully, one reasonable addition at a time.


The Task Breakdown template is in the action pack. The worked example decomposes the login feature from Spoke 1 into six tasks, one of which starts life as an L and gets broken up on the spot, because the template is supposed to eat its own dog food.