The orchestrator that wrote the code itself
When the coordinator writes a layer instead of dispatching its generator, the conventions the generator carries are gone and the output still compiles.
A pipeline dispatches a generator per layer: one for the data layer, one for the view model, one for the screen. Each generator carries conventions — where files go, what they are named, which patterns the codebase has settled on.
Then the coordinator writes one of those layers directly, because it already had the context.
The case this post is about
One layer, written by the coordinator instead of its generator, because it was four files and faster:
const OWNERS: Record<string, string> = {
"core/dto/": "dto-gen",
"core/store/": "store-gen",
"feature/*/ui/": "screen-gen",
"core/di/": "di-gen",
};
The four files compiled. They were also missing the conventions the generator carries — the module placement rules, the naming, the annotations the downstream codegen reads — because those live in the generator's prompt and nowhere else.
Nothing objected. This post follows those four files, because the output of a shortcut and the output of the real path are both valid Kotlin, and the difference only surfaces when something downstream looks for an annotation that was never written.
The symptom
The output compiles. It is reasonable code. It does the right thing, and it does it in a way that does not match anything else in the project — a file in a plausible but wrong directory, a naming scheme from general practice rather than from this codebase, a pattern the project moved away from two years ago.
The second thing is that none of this fails. There is no error, no warning, no test that encodes "files of this kind live there". The conventions the generator carries were never written down as rules; they live in the generator, which is exactly why they were not applied.
The third is that the divergence is small enough to survive review. One file in the wrong place looks like a judgement call. Reviewers see working code in a sensible shape and have no particular reason to object, especially when the project is large enough that nobody holds the whole convention in their head.
The fourth is what it does to the next generation. The improvised file is now part of the codebase that subsequent work reads as precedent, so a convention that was violated once starts being reproduced on purpose — and at that point it is no longer a mistake, it is a second convention.

Two conventions is worse than one wrong one. Each half of the codebase is internally consistent, each is defensible, and every new file requires somebody to decide which one they are in — which is a question nobody should ever have to answer and which has no good answer once it is asked.
The question also arrives at the worst moment. Nobody asks it while writing the file; they ask it in review, or six months later when something has to be changed in both halves and the two halves do not have the same shape. By then the cost of unifying is a migration, which is how projects end up documenting the split instead of resolving it.
Why the obvious fix failed
Write the conventions down. The instinct, and it is a lot of writing for something that is already encoded. The generators are the written form; transcribing them into prose produces a document that drifts from the generators and loses whenever they disagree.
Review for consistency. Asks a reviewer to compare one file against a convention they would have to reconstruct from the rest of the codebase. People are bad at this and get worse as the codebase grows, which is exactly backwards from what you need.
Have the coordinator consult the generator's instructions. Reasonable, and it reproduces the generator's knowledge in a second place at the moment of use, which is the definition of drift. It also quietly assumes the coordinator will consult them every time, including the time it is in a hurry.
Lint for the specific violations. Works for the ones you have seen. Conventions are numerous and mostly tacit; a lint rule per convention is a long tail nobody finishes, and the violations that matter are usually the ones nobody anticipated.
Let the coordinator write it and have the generator check it afterwards. The compromise that sounds like it has both, and it inverts the economics: now somebody has to express every convention as a check, which is the documentation problem again with a worse failure mode — a check that passes is read as approval rather than as silence.
The actual mechanism
Make it a rule with one line: the generators are the only writers. The coordinator dispatches and never authors.
coordinator decides what to generate, in what order, with what inputs
generator the sole writer of files in its layer
rule a coordinator write to a generated layer is a defect, not a shortcut
The temptation this exists to defeat is real and it is specifically a context temptation. The coordinator has just read the specification, holds everything in mind, and dispatching a subagent means that context is assembled again somewhere else. Writing the file directly genuinely is faster, and that is why it happens — the cost it skips is visible and the cost it incurs is not.
What makes the rule hold is that it is binary. "Dispatch unless it is obviously simpler to do it yourself" has no stable meaning; every case looks simple from inside. A rule that permits no judgement is enforceable by something that is not a person, which is the only kind that survives.
It is also the kind people stop arguing with. A rule admitting judgement invites a conversation on every borderline case, and those conversations cost more over a year than the dispatches would have; a rule with no border produces no conversations at all.
The sub-case that is worth stating explicitly: a trivial file is still a generated file. A one-line binding, an empty module, a constants file. These are the ones most likely to be improvised, because dispatching a whole generator for four lines feels absurd — and they are where conventions live most densely, because there is nothing else in the file but convention.
There is a second-order benefit that is easy to miss and is probably the larger one. If generators are the only writers, then improving a generator improves everything it will produce, forever. Every file the coordinator writes by hand is a file outside that loop — it will not inherit the next improvement, and nothing will ever tell you it did not.

The fix
dispatch every file in a generated layer comes from its generator
author the coordinator writes nothing into those layers, ever
gate a write to a generated path from outside its generator fails
improve conventions change by changing the generator, not the output
The gate is a path question, which is what makes it mechanical: every generated layer declares who may write it, and anything else touching that path fails.
const OWNERS: Record<string, string> = {
"core/dto/": "dto-gen",
"core/store/": "store-gen",
"feature/*/ui/": "screen-gen",
"core/di/": "di-gen",
};
/** Called by the harness around EVERY write, including the coordinator's own. */
export function assertMayWrite(path: string, writer: string) {
const owned = Object.entries(OWNERS).find(([prefix]) => matches(path, prefix));
if (!owned) return; // not a generated layer
const [prefix, generator] = owned;
if (writer === generator) return;
throw new Error(
`${writer} tried to write ${path}\n` +
` ${prefix} is generated by ${generator}; dispatch it instead.\n` +
` writing here by hand produces code that will not inherit the next\n` +
` improvement to ${generator}, and nothing will report that it did not.`,
);
}
The case it exists for is the coordinator taking a shortcut on something small:
$ generate profile
dto-gen core/dto/ProfileDto.kt ok
store-gen core/store/ProfileStore.kt ok
✗ orchestrator tried to write core/di/ProfileModule.kt
core/di/ is generated by di-gen; dispatch it instead.
writing here by hand produces code that will not inherit the next
improvement to di-gen, and nothing will report that it did not.
A four-line DI binding is exactly the file somebody writes inline, and exactly the file that is nothing but convention.
The third line is the only enforcement that works, and it has to be mechanical because the behaviour it prevents is produced by good judgement under time pressure. There is no version of this rule that survives as an instruction, because every violation of it felt sensible at the moment it happened.
The fourth line is what makes the first three worth the trouble. A convention held in a generator is a convention that can be changed once; a convention held in the output is a migration. That is the whole argument for the discipline, and it only pays if the generators really are the sole source.
What this costs is real and worth saying plainly: dispatching for small work is slower, sometimes by a lot, and the first few times you follow the rule over a four-line file it feels ridiculous. The answer is not to make an exception — it is that the generator for that layer should be cheap to invoke, and if it is not, that is the thing to fix.
That reframing is worth taking seriously rather than treating as consolation. Every time the rule feels absurd, it is pointing at a dispatch that is more expensive than it should be — and making dispatch cheap is work that pays on every generation afterwards, whereas making an exception pays once and costs thereafter.
How we proved it
The improvised layer is identifiable after the fact, which is how we found ours: files that do not match the generator's output shape for their layer. Comparing a directory against what its generator would produce turns a vague sense of inconsistency into a list.

The precedent effect is observable in the history. Once an improvised file existed, later files of the same kind followed its shape rather than the generator's — not because anybody decided to, but because reading the neighbouring file is how people work. The divergence compounds in the log.
What I have not measured is how much faster the improvisation actually was. The whole premise is that it saves context assembly, and I never timed it against a dispatch; the argument here is about what the saving costs, not about denying that there is one.
What I have not solved: the generator that cannot express something. When a layer genuinely needs a file the generator does not produce, the rule says improve the generator, and sometimes that is a large job for a small need. I do not have a sanctioned small escape, and I am wary of adding one — the escape is exactly the shape of the original problem.
And a limitation of the rule as stated: it governs who writes, not whether the generator is any good. A disciplined pipeline that dispatches faithfully to a generator producing poor code will produce a lot of consistent poor code. Single authorship makes conventions enforceable; it does not make them correct, and the two are easy to conflate once the gate is green.
The thing that reframed it was noticing that the conventions were never the point of the generators — they were a side effect of having one author. A codebase written entirely by one generator is consistent for the same reason a building raised by one mason is consistent: not because the rules were written down, but because the same hand made every decision.
So the rule is less about code generation than about authorship. The moment a second author exists — human or otherwise — consistency stops being free and starts being a thing somebody has to enforce, and the cheapest enforcement available is to not have a second author.
Which also explains why this gets worse with capable coordinators rather than better. A coordinator that cannot write the layer never faces the temptation; one that can write it well faces it on every task, and will be right about the code every time and wrong about the authorship every time. The rule exists for the capable case.
What this generalizes to
A convention that lives in a tool is enforced only on the work that goes through the tool.
The shape recurs constantly: a scaffolding command everybody is meant to use, a migration generator that is bypassed for a quick ALTER, a formatter that one directory is excluded from, a deployment script somebody skipped once with manual steps. In each case the tool encodes accumulated knowledge and the bypass produces output that works — which is why it is a bypass rather than an error.
So the question worth asking of any such tool is what happens to work that does not go through it. If the answer is "it is fine, just slightly different", the conventions are optional, and the drift has already started — it is simply still small enough to look like judgement.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".