21STARK
All posts
8 min read views

Slices, not sprints

Hand a coding agent the whole feature and you get a 2,000-line diff you can't review and shouldn't trust. A better prompt won't fix that. A smaller unit will: the review-sized slice. Spec first, failing test second, one worktree each, straight through the door.

A +3,192-line diff can fit through review. A 2,000-line diff can be useless.

The fastest way to get a useless result out of a coding agent is to give it a big job. Ask for the whole feature and you get a 2,000-line diff across forty files. It mostly works. It's completely unreviewable. You can't tell the parts it got right from the parts it confidently invented. So you rubber-stamp it or you throw it away, and both are bad.

The reflex is to write a better prompt. Wrong lever. The lever is the size of the unit.

Review is a door in a wall. The whole feature is a crate wedged against the frame. You can shove harder; the crate doesn't care. A slice glides through, then the next one. If it does not fit through review, it does not ship.

Review is the bottleneck

Agents made typing free, and that moved the bottleneck to review. Sprints haven't noticed.

A sprint batches work by calendar: whatever got typed in two weeks lands together, and the review at the end gets whatever attention is left over, which is exactly backwards.

The scarce resource is honest attention, the capacity to read a change and say with a straight face: this does what it claims. That capacity is small. It doesn't grow with headcount. Every oversized diff burns it and pays back nothing.

So I size everything to review and let the board complain.

The unit is a slice

My unit of work is a slice: the smallest vertical cut that's independently deployable and independently reviewable.

It has to pass both tests at once. Deployable keeps the cut honest about coupling. Reviewable keeps it honest about size. My rule: if I can't review it in one sitting and tell whether it's honest, it's too big. Call it a review-sized slice.

Flat vector illustration of a layered cake being cut with a knife, with four identical vertical slices standing on separate plates, each showing all the layers.

A real example, receipts included. May 2026, meridian, my engineering-analytics platform. The repo was still called stark-night-watch back then; it has since been archived with a named successor.

I needed a capacity engine: per-member, per-day sprint capacity, ported off a 523-line Python capacity.py in a data service I was decommissioning. It shipped as four slices, layers hard separated:

  • Slice 1, the engine. PR #889: +3,192 lines across 29 files, and the biggest file in the diff is engine_test.go. Pure compute. No database. No clock. Deterministic. You can test it to death because it has no world to mock.
  • Slice 2, the API on top of it. PR #892: +1,261 lines, 10 files. Day, week and window buckets over the engine's output.
  • Slice 3, the query path that feeds it. PR #893: +1,472 lines, 12 files. Capacity joined to PR activity.
  • Slice 4, reconciliation. The tail that makes the cutover honest: surfaces that had been faking the numbers get the real engine. The flavor of it is PR #895, the next morning: one resolver that had been scaffolded to return an empty list, replaced with real numbers. +155 lines, 2 files.

Yes, slice 1 is bigger than the horror diff in the intro. Count again. A third of it is tests. 263 lines are the spec riding along in the same PR. Every remaining line lives in one layer with zero I/O.

Review-sized was never a line count. It's whether one sitting can hold the blast radius.

The spec is dated 2026-05-26 and rode into main with slice 1. The engine merged that Tuesday at 18:06 UTC. The API at 21:20. The query path at 21:49. Spec in the morning, three separately reviewed slices by night, and nobody ever reviewed "the capacity engine". Each layer landed alone.

Slice 1 is the move worth stealing: cut the pure logic away from the world first. The scariest part of the system becomes the easiest to prove because it touches nothing.

A live migration, sliced

A bigger migration ran the same way. Same month, same repo: replace the Python calendar-ingestion pipeline, gcal.py plus its absence materializer, with a Go reader job. Eight numbered slices, planned 2026-05-19:

SliceWhat landsPR
1Calendar API wrapper lifted into a shared librarytagged lib release
2Consumers swapped onto the lifted library#724
3Schema migration only, no reader code yet#725
4Transform + store layer, pure Go, zero API calls#726
5Reader loop + syncToken state machine, behind a disabled flag#730
6The absence materializer port#734
7A parity CLI proving old and new pipelines agree#741
8Production enablement plumbing + a 7-day parity watch#738

Slices 2 through 8 merged in 26 hours.

Two details carry the whole method. Slice 5 ships the first end-to-end pull dark, behind JOB_GCAL_EVENT_SYNC_ENABLED=false; deployable never meant enabled.

Slice 7 is the one most people skip: a diff tool whose only job is proof. When you replace a live pipeline, a green test suite proves your code, not your cutover. The parity CLI proves the cutover. That watch is the only ceremony in the trail, and it earned its place.

Big-bang migrations die in review. Sliced ones land on a Wednesday and nobody notices. That's the goal. Boring is what shipping looks like.

Spec first, then a failing test, then code

Every slice gets a short spec before a line of code. Dated. Three parts: the problem, the data model, and an explicit list of non-goals.

The non-goals matter more than the goals. An unleashed agent will happily build three slices ahead and call it helpful. The capacity spec names slices 2, 3 and 4 only to declare them out of scope, then adds three flat lines: no new GraphQL surface, no new tables, no new env vars. Cheapest fence you'll ever build.

Then TDD order, on purpose. Failing test, implementation, passing test, commit. One commit per logical unit.

This isn't nostalgia. With an agent, the test is the leash. It catches the most dangerous kind of bad code: the plausible kind. Code that reads fine, runs fine, and quietly does the opposite of what you asked.

Review alone doesn't catch it reliably, because the reviewer reads the same plausible surface the agent generated. A test that started red doesn't care how plausible the code looks.

Drop the spec and the agent builds three slices ahead. Drop the test and the plausible garbage ships. Drop the slicing and you're back at the 2,000-line diff.

One slice, one worktree

Slices live in their own git worktree, each with its own agent, its own branch, its own clean tree.

Mine sit under .claude/worktrees/, one directory per slice, boring names on purpose, because a boring name is a name you never think about again. No stashing, no half-finished state bleeding across tasks. I've had five slices in flight at once without them touching each other, and dozens over the life of a single repo.

The throughput is not theoretical: in the ten days around the calendar port, that one repo absorbed 191 merged PRs.

Low-poly isometric scene of five parallel walled garden lanes, each with a robot; four tend green trees while one drops a wilted tree into a bin.

Sometimes one of the five is stuck at red. It blocks nothing. Nobody rebases around it, nobody waits on it. Killing it costs one rm -rf, and that's the entire process.

That price changes behavior: a slice that's cheap to abandon gets abandoned honestly instead of pushed through on sunk cost.

Because these are my own projects, there's no rollout ceremony. No soaking, no canary, no gating. A green slice ships straight to main. Live-pipeline cutovers are the one exception, and they earn a parity watch, as above.

The discipline goes into the slicing and the review, not into a deployment pipeline that pretends every change is a moon landing.

What this is not

It's not ceremony for its own sake. A two-line fix needs no spec and no worktree; wrap one in process and you've missed the point entirely.

The discipline scales with how much you're letting the agent touch. Small blast radius, small process. Large blast radius, slice it down until the blast radius is small again.

Cut until it fits

A sprint ends because the calendar says so. A slice ends because the proof is in.

The capacity engine fit through four times. Cut until it fits.

Get in touch

I write about AI-first engineering on LinkedIn. Specs in, production out, nobody types code. Follow along there, or send a note.

hi@21stark.com · LinkedIn opens my profile, message me from there


Or send it from here

Providing your name, email address, and message is voluntary; without them you cannot use this form. Aryeh Kiovetsky, operating as 21Stark, controls this information. We use it to receive and answer your message, and provide it to Google Cloud for hosting and storage.