Studio story ~6 min read

Chapter 2 — A senior with no structure in his head

AI coding agents are not juniors. They are seniors with no structure in their heads — and without walls, they build excellent Frankenstein code.

Author
Konstantin Vanichkin
Read time
~6 min read
Published
6 Sep 2026
Chapter guide 6 chapters

01 / Metaphor

A senior with no structure in his head

There is a moment everyone who works with coding agents eventually hits. You hand one a real task in a real repo, walk away, and come back to something strange: the code is good. Genuinely good — typed, tested, better than what half the engineers I've managed would have written. And it's in the wrong place. A new helper folder that didn't exist an hour ago. A component split across three locations, each one defensible on its own, none of them where anything else lives.

Nobody was lazy. Nobody was sloppy. The thing just… grew.

The standard line about AI coding tools is that they're like junior developers. Fast, eager, need supervision. I believed a version of that for a while, and it's wrong in a way that matters.

A junior has limited ability and, if they're any good, they know it. The junior who hits something confusing and asks a question — that one grows into a middle developer. The junior who's too shy to ask just stays blocked forever. Either way, a junior's limits protect you. They can only make small messes.

An AI agent is the opposite animal. It's a senior developer — but a senior with no structure in his head. It can technically do anything. It has seen every pattern, every framework, every clever trick in the training data, and it holds none of them as the way we do things here. So if you don't frame it, it doesn't stall like a shy junior. It goes. Confidently, competently, in every direction at once — and builds you a Frankenstein. Every limb excellent. The whole thing a monster.

That's the problem this chapter is about. Not writing rules. Building walls.

02 / Distraction

The thing you cannot npm-install

Let me get one thing out of the way first, because a lot of "how we made AI write good code" posts spend their whole length on it: linting. Lint rules have been a solved problem for a decade. Shared configs exist for every language; you install them, wire them into a pre-commit hook, and you're done. We did that in the first week and it was the least interesting week of work in the studio. If your AI-quality story ends at "we added ESLint," you haven't started yet.

The thing you cannot npm-install is a filing system.

03 / Walls

Give him the structure he doesn't have

Ours started years before the studio did, on human teams, as atomic design — that old front-end idea of sorting components into atoms, molecules, organisms. It was a decent way to make a team of people agree on where things go. Then the team stopped being people, and the convention mutated under pressure into something else.

What it is now, roughly: every project has the same skeleton. Feature code — the stuff that ships together — lives in features/, each feature carrying its own components, hooks, services, and types inside it. Cross-cutting utilities go to lib/. Shared UI goes to components/, and vendor primitives sit in a folder nothing is allowed to refactor. A component isn't a file, it's a folder: Button/ holds Button.tsx, Button.types.ts, Button.hooks.ts, Button.test.tsx, and a barrel index.ts. When a component grows private pieces, they don't wander off — they stay in the folder as Profile.BookLine.tsx, dot-named after their parent. There's even a little decision table: put it here when this is true, there when that is.

None of this is clever. That's the point. Atomic design asked "how abstract is this thing?" — a question two smart people can argue about for an hour. The current system asks "does this ship with a feature or not?" — a question with an answer.

Back when I wrote the first version of this convention, the rationale I put in the document was that consistent naming "reduces cognitive load." I was writing for tired humans — for the next person opening the repo at 2am. There is no next person now. But the convention got re-hired for a better job: an agent that has never seen the repo can guess the path to anything and be right. Where are the profile page's hooks? You already know. You've never been here, and you already know.

That is what a wall does for a senior with no structure in his head. It doesn't limit what he can build — he could build anything anyway. It answers, before he asks, the one question he will otherwise answer differently every single time: where does this go? You give him the structure he doesn't have. A sandcastle with walls: inside them, go as fast as you like.

04 / Enforcement

They do not share a fate

Now the part I'd rather skip.

A while ago, talking to my own agents about this exact convention, I heard myself say: "I wrote the structure requirements, but I'm not sure if you guys actually do it." Half a joke. Then I checked.

There are three layers. They do not share a fate.

File size is a real wall. check-file-size.ts — no source file past 250 effective lines, blank lines and comments don't count, warn at 150. Tests get 200/400 because tests repeat. It runs on every bun run lint, one of seven guards, and exits non-zero. The build dies. That one always worked.

File naming is different. check-file-naming.ts checks three concrete things: CSS and SCSS must be kebab-case; TypeScript and TSX components must be PascalCase — Button.tsx, LandingPage.tsx; migrations must be YYYYMMDDHHMMSS_description.sql. It skips tests and generated files. It has a ratchet so existing violations can't get worse and new files must obey. The script is written. It works. And it had zero call sites — nothing in the seven-guard lint chain, nothing in CI, nothing anywhere invoked it. It sat in the scripts folder like a smoke alarm still in its box.

Worse: standards/naming-conventions.md, the document I wrote, says in plain text that it is "Wired into each repo's lint script and runs in CI before per-directory lints." That sentence was never true.

Folder placement is the third layer — the part this chapter spent its best pages on. Features in features/, shared utilities in lib/, components as folders not files, private pieces nested and named after their parent. There is a placement decision table in the standard. Nothing checks any of it. No guard was ever written. It lives only in documentation agents read and choose to follow.

05 / Admission

Building the walls is not the same as closing them

I fixed the middle one. One line of config. The naming guard is in lint now. It passes clean across 1,535 files — no migration, just a wire that was never connected. The folder convention, the most uniquely ours of the three, is still enforced by nothing at all.

Building the walls is not the same as closing them. I wrote the rules, wrote one of the guards, wrote the documentation claiming that guard ran — and skipped the boring line that would have made the claim true. The most human failure in the whole studio, and it's mine.

06 / Reality

A rule that isn't enforced is a suggestion

With these systems, a rule that isn't enforced by a machine is a suggestion. And a senior with no structure in his head is exactly who won't take a suggestion. He follows the document nine times out of ten. The tenth time, at three in the morning, in a file you won't read for a month, he improvises.

Which leaves me where I still am: one editor window, one agent at a time, reading every diff myself.

Next chapter: why I ditched the editor entirely — and why the way I build software today doesn't have a code window in it at all.

More from the studio

If you want a straight read on your digital presence, write us.