Sizing Tasks an AI Coding Agent Can Finish / Sep 23, 2026

9 min readBy Emmanuel Akinfulubi
Sizing Tasks an AI Coding Agent Can Finish

I have made both versions of this mistake on the same feature, about a week apart.

The first time I gave the agent one task: build the refund flow, here is the design. It did. Fourteen files, a migration, a service, two routes, a state machine and a screen, all in one diff. Every piece of it was plausible. I sat with it for forty minutes and gave up on reviewing it properly, because working out whether the state table had been honored meant holding fourteen files in my head at the same time. I approved it on vibes.

So the next feature, I over-corrected. Nineteen tasks, each about two lines long. Create the migration. Add the model. Add the repository method. Add the service method. By the middle of the afternoon I was not engineering anything, I was a project manager for a machine, feeding it instructions at a rate that cost more attention than writing the code would have.

One diff I could not review. Nineteen tasks I could not stop supervising. The interesting part is that these are not opposite errors, and fixing one by doing more of the other is how you end up oscillating for months.

Both directions are the same mistake

Both come from sizing work by effort. How much is this, how long will it take, how do I cut it into pieces that feel even. That instinct is inherited from planning human work, where effort is the scarce thing and a person fills the gaps by asking questions.

An agent does not get tired and does not ask. Effort is no longer the constraint. What is scarce now is your attention at review time, and the build's ability to tell you the truth when a task lands.

So the question is not how much work is in this task. It is what has to be true when this task is done, and who checks. Size by verifiability, and the diffs come out roughly even anyway, without you trying to make them even.

The test

One test, same as the other steps in this sequence.

A task is the right size when the build is green the moment it lands, and the diff is small enough to review in one sitting.

Two halves and both matter. Green when it lands means the task is independently completable: nothing is left broken for a later task to repair. Reviewable in one sitting means you can hold the entire change in your head at once, which is the thing that actually broke in my fourteen file diff. Neither half is about effort.

If a task fails the first half, it is not a task, it is half of one. If it fails the second half, split it, and if you cannot find a split that keeps the tree green, that is usually the design telling you something. Two things you thought were separable are not, and that belongs in the design document, not in the task list.

For what it is worth, tasks that pass both tests tend to land between fifty and three hundred lines of diff for me. That is a symptom and not a target. Chasing the number produces tasks split at silly places, which fail the first half of the test to satisfy a number that was never the point.

There is a second reason to keep tasks small, and it is about the agent rather than about you. The further into a long task it gets, the more it is holding at once, and the earlier constraints are the ones that start slipping. The convention you stated at the top of the task quietly stops being applied around the eleventh file. You will not see a warning when that happens, you will just find one module that disagrees with the rest and no obvious reason why. A task that fits comfortably is a task where every instruction in it still carries weight at the end.

Slice vertically, not horizontally

This is the structural error, and it is almost universal.

Given a feature, the natural instinct is to slice by layer. Task one, the migration. Task two, the repository. Task three, the service. Task four, the route. Task five, the screen. Task six, tests. It looks tidy and it maps onto how we describe systems.

Every one of those tasks except the last leaves the build unable to prove anything. There is a table with nothing writing to it, then a method nobody calls, then a route nothing reaches. Nothing is verifiable until the end, which means nothing is reviewable until the end, which means you have one fourteen file diff again, just delivered in installments.

Slice vertically instead. One narrow behavior, end to end, with its test.

Slicing a feature by layer leaves every task unverifiable until the last one. Slicing it vertically gives each task a green build and a reviewable diff.

Same feature, same total work. Only one of these can tell you it is broken on Tuesday.

On the refund flow, vertical looks like this. A refund row that exists and can move between states, with a test asserting the transitions it is allowed to make. Then requesting a refund, which writes a row in requested and calls nothing external yet, with a test. Then the provider call with an idempotency key, covering the confirmed and declined answers, with a stubbed provider. Then the unresolved path and the action a human takes to clear it. Then the customer facing states.

Five tasks. Each one ships something true. Each one is green. After task two you have a refund that records intent and does nothing else, which sounds useless and is in fact the first moment the system can prove it agrees with your design.

Dependencies, and the order that keeps the tree green

Write the order down, and write what each task needs.

It takes a minute and it removes an entire class of failure, where the agent reaches for something that does not exist yet and invents a stand-in. It will not tell you it did that. You will find the stand-in later, usually when the real thing arrives and quietly disagrees with it.

There is one phrase to watch for while writing the list. "To be fixed in the next task." Any time that appears, in your head or in the file, the split is wrong. A task that hands a broken build to the next task has failed the first half of the test, and the two should be one task.

Tests go inside the task. Not a phase, not a follow up, not a final sweep. A task without a test has no way of proving it is done, so the proof falls to you reading the diff, which is exactly the work you were trying to avoid doing fourteen files at a time. This matters more now that review is the real bottleneck rather than typing.

Every task names its requirement

One line per task, pointing at the criterion it satisfies.

Two reasons, and the second is the one people miss. First, when the diff arrives you review it against a stated criterion instead of against your memory of what you wanted an hour ago. Second, and more useful: at the end you can read down the list of acceptance criteria and find the ones with no task attached. Those are the things you are about to not build, and this is the only cheap moment to notice.

It also makes the reverse visible. A task that satisfies nothing is either scope you invented or a criterion you never wrote down, and both are worth knowing about before the agent starts.

What the file actually looks like

1. Refund record and its states
   Satisfies: R4, R7
   Depends on: nothing
   Done when: a refund row exists with the six states from the design,
     illegal transitions are rejected, and a test covers each one.

2. Requesting a refund
   Satisfies: R1, R2
   Depends on: 1
   Done when: a valid request writes a row in `requested` with an
     idempotency key, an out of window request is rejected with the
     window in the message, and nothing external is called yet.

3. The provider call
   Satisfies: R5, R6
   Depends on: 2
   Done when: a confirmed answer sets `succeeded`, a declined answer
     sets `failed` with the provider's reason stored verbatim, and the
     same key is reused on retry. Provider stubbed in tests.

4. The unresolved path
   Satisfies: R8
   Depends on: 3
   Done when: no answer within the timeout sets `unresolved`, no retry
     loop exists, and a human action moves it to a terminal state.

5. What the customer sees
   Satisfies: R9
   Depends on: 2
   Done when: each of the six states renders its own message, and no
     state renders a spinner that cannot end.

Five tasks, one page, about fifteen minutes to write. Notice task five depends on task two rather than on task four, which means it can be built in parallel with three and four. You only know that because the dependency is written down.

Notice also what is absent. No file names, no function signatures, no library choices. Those are the agent's to pick, and a task list that specifies them is the prose version of writing the code yourself, slowly.

The habit

Write the list so that any single line could be the only thing you ship today.

That is the whole heuristic. If shipping just that line would leave the build red or leave a feature half connected, it is not a task yet. If reviewing it needs two coffees, it is two tasks.

Requirements say what must be true. Design says what exists and in what order things change. Tasks say what lands, in what order, and how each one proves itself. Three documents, none longer than a page, written before the prompt rather than reconstructed from a diff afterwards.

That is the sequence I am turning into a course, planned for November. The waitlist there sees the date and the price first. The box below is for the writing, and nothing else.

Frequently asked questions

How many lines of diff should a task be?
There is no correct number, and treating one as a target produces silly work. For me a well sized task usually lands somewhere between fifty and three hundred lines, but that is a symptom rather than a rule. The test is whether you can review it in one sitting and whether the build is green when it lands.
Is this not just writing tickets?
The shape is similar and the audience is not. A ticket is written for a person who will come and ask you what you meant. A task is written for something that will never ask, and will instead decide. That changes what has to be on the page, particularly the acceptance criterion and the dependency.
Should tests be their own task at the end?
No, and a task list with a testing phase at the end is a list that has never once been green. Tests belong inside the task that makes the behavior, because a task with no test has no way of proving it is finished, which means you are the proof.
Can the agent break the work down for me?
It can draft a list, and it is fast at it. It will almost always slice by layer, schema then service then interface, because that is what most code it has read looks like. You will need to re-slice it vertically. Judging the slicing is the part that stays yours.
What about a refactor that touches forty files?
Some work genuinely does span the codebase, and pretending otherwise produces fake tasks. Keep those strictly behavior preserving, give them their own task, and never combine one with a feature change. A reviewer can answer did this change behavior, or is this the new behavior. Not both at once.