DEV Community

Anton Brilliantov
Anton Brilliantov

Posted on

The Lead Doesn't Write Code

Two executors on the main line, one on fixes, and a lead who only cuts, hands over and accepts. The moment the lead starts writing code, the facts stop being prepared - and the next batch of tasks costs more than the hour that got saved.


πŸ‘‹ I'm Anton - a software engineer working mostly in PHP/Symfony and Go, currently carving a live PHP monolith into Go services. The earlier parts of this series were about what a task must contain and how small it has to be cut. This one is about who does what once the cutting is a habit: the split of roles, and why the number of live executors is capped. Notes: github.com/brilliant-almazov.

Maybe you run this better, maybe you'd draw the line somewhere else. Either way it's one arrangement on one codebase, not advice for yours.


The thesis

The lead doesn't write code.

Not "shouldn't, mostly". Doesn't. The moment the lead starts writing code, the facts stop being
prepared - and the next batch of tasks goes out with holes in it. That costs more than the hour of
typing that got saved.

The chain

Five steps, and the lead owns all five:

  1. Prepare the facts. Exact paths, full type signatures, values, the acceptance command. This is the only place in the whole process where reading the code is allowed.
  2. Hand over. The executor gets the task whole, in one message.
  3. Accept the iteration - by its own package, one command, nothing else.
  4. Accept the stage - the full pass, exactly once, at the end.
  5. Close the executors. One that has finished is closed straight away.

Then, once the set is closed, one more thing that isn't part of the loop: the audit of the set.
It comes after step five, not instead of it.

 1              2            3                4              5
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ prepare  │─▢│ hand   │─▢│ accept the │─▢│ accept the │─▢│ close    β”‚
β”‚ the factsβ”‚  β”‚ over   β”‚  β”‚ iteration  β”‚  β”‚ stage      β”‚  β”‚ executorsβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 the only                  its own          one full pass
 place reading             package,         at the end,
 the code is               one command      exactly once
 allowed
                                                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          after the set is closed:          β”‚ audit of β”‚
                          not part of the loop         ────▢│ the set  β”‚
                                                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Five boxes in a row - prepare facts, hand over, accept iteration by its own package, accept stage with one full run, close executors - with a note under the first box saying this is the only place reading the code is allowed, and a separate box for the audit of the set

Step 1 is the expensive one, and it's the one that quietly disappears first. Steps 2 to 5 feel like
the job; step 1 is the job.

What the executor does

One rule: did it and forgot.

The executor gets the task whole, in one message, and executes it without reading anything further.
No context carries over - not the conversation history, not the neighbouring iterations, not a walk
through the code. One executor, one task, one context. Continuing the work is a new task with a new
full text, not a follow-up message.

That's the part that decides everything downstream. If nothing carries over, then everything the
executor needs has to be written down by the lead beforehand - which is exactly why step 1 can't be
skipped and can't be delegated.

The cap

Live at the same time: two executors on the main work, one on fixes. That's the whole roster.

Fan-out is banned. Not one executor per file, not a batch per stage. Fixes for a whole stage go to
a single executor as one list, not one executor per remark.

live at the same time
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ main work    β”‚ β”‚ main work    β”‚ β”‚ fixes        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

banned:  one executor per file
         β–§ β–§ β–§ β–§ β–§ β–§ β–§ β–§   burns limits, same result
Enter fullscreen mode Exit fullscreen mode

Three slots in a row - two labelled main work, one labelled fixes - and below them a struck-out row of eight small cards labelled one executor per file, captioned burns limits, produces the same result

Why that number

Two reasons, and neither of them is caution.

The bottleneck isn't the executors. It's the facts. Work only exists once the paths, signatures
and values are written out, and that happens at one desk, at one pace. Launching more executors
than that pace produces doesn't add throughput - it just multiplies spend on tasks that aren't
ready.

Parallel executors on one tree start editing the same files. That's not a merge problem to
manage, it's a signal: two iterations touching one file means the cut was wrong. The fix is
upstream, in the split, not in the number of workers.

The audit of the set

When a set closes, every original requirement gets one row: requirement β†’ what was done β†’ what
proves it β†’ gap. Proof is a name, not an opinion: the command that ran, the test that covers it,
the file that holds it.

requirement            what was done         proof                    gap
─────────────────────  ────────────────────  ───────────────────────  ────
read path on the core  4 methods converted   go test ./... -race      none
error naming unified   1 sentinel per pkg    the sentinel test        none
copies removed         44 β†’ 1                the forbidding test      none
─────────────────────────────────────────────────────────────────────────
logic in the code that was NOT in the statement  ──▢  named, then removed
Enter fullscreen mode Exit fullscreen mode

Table with four columns - requirement, what was done, proof, gap - three example rows with monospaced values in the proof column, and a separate row highlighted for logic that was not in the statement

Then a separate section, and it's the one that finds things: logic that is in the code but wasn't
in the statement.
The inputs didn't change during the set, so no new invariant should have
appeared. Every one that did gets named and removed.

What it costs

The lead is busy all day and doesn't write a line. That reads like idleness from the inside, and
it isn't - preparing facts is the dullest and most expensive part of the work, and there's nobody
to hand it to. Delegating it is the same as skipping it.

There's a scale where this stops being a preference. Across the sets written this way the shapes
have been 13, 12 and 40 iterations; the prompt folder is at 298 files with 20+ closed sets. At that
volume the lead's day is entirely reading, writing and accepting. It looks like nothing is being
produced right up until the moment you stop doing it.

What's actually new here

Splitting "who cuts the work / who writes it / who accepts it" is older than any of this tooling -
it's what a tech lead and a team of engineers have always been. Nothing in the chain above would
surprise anyone who ran a team twenty years ago.

One thing is new: the executor remembers nothing between tasks. A human engineer accumulates
context and quietly compensates for a vague task; here that compensation doesn't exist. So the
discipline of cutting stops being a good habit and becomes the mechanism - if the facts aren't in
the text, they aren't anywhere.

The one conclusion

The cap on live executors isn't a limit on how much can be built. It's an honest measurement of how
fast one person can prepare work that needs no interpretation.


I'd rather hear how this is arranged where you are. Maybe you do this better and the roster is
bigger for a reason I'm missing; maybe you've been through it and stopped; maybe you look at the
whole split differently and the lead on your side does write code. What's the arrangement, and what
broke in it?


Working with agents - Part 5.

Next: batches and waves - how work gets grouped so it can be stopped at any point without losing
anything, and where the commit boundary sits.

Top comments (0)