Brutalist Software

For as long as I’ve written code, the virtues were fixed: DRY, clean code, small functions, abstract the moment you see a pattern twice. We called them timeless. They were never timeless. They were ergonomics for one specific reader — the human, with limited working memory, easily bored, slow to type, allergic to repetition. We optimized code for that reader because that reader was the only one who ever read it.

That reader is no longer alone. Agents write code now, and they read nothing like us. Generating text is almost free for an agent, so the verbosity we flinch at barely costs it. But its context window is small and expensive, it holds no memory of the rest of the repository between glances, and the instant it can’t see something, it doesn’t pause — it guesses. Our scarce resource was patience. Its scarce resource is attention: how much of the system it can hold in view at once.

I want to give the response a name: brutalist software. Not the bare-HTML “brutalist web design” you may have seen — something else, borrowed from the buildings. Architectural brutalism stripped structures down to raw concrete and exposed frame: no cladding, no ornament, nothing hiding what holds the thing up. Its bet was that honesty about structure beats the comfort of a facade. Brutalist software makes the same bet, for the same reason a building does — so that anyone standing in front of it can see what is load-bearing. What you read is what runs. No action at a distance.

This is not “always go lower-level.” fetch instead of an SDK goes down a level; raw SQL instead of an ORM goes up one. The axis isn’t altitude at all. It’s locality — how many jumps a reader has to make to be sure that touching one thing won’t break another. Brutalism minimizes that number.

Which gives one rule. For a first principle, I’ll state it plainly enough to be argued with:

An abstraction earns its place when it is self-evident at the point of use — you understand it by looking at it. It is forbidden when understanding it costs more than one cheap, local jump.

stripe.charges.create() explains itself; you never open its source to know what it does. user.posts, in an ORM that quietly fires a query per row, does not — nothing at the call site tells you that a single attribute access is an N+1 waiting to happen. One reads like what it does. The other reads like a lie of omission. That gap is the whole philosophy, and it is the gap an agent pays for, in context, every time it reads your code.

That’s the cornerstone, and on purpose it is only the cornerstone. There is more to build on it — when brutalism is the wrong call, how it rots in careless hands, what it does to code review, why it isn’t just “keep it simple” wearing a new coat. I’ll get to those. But it starts here, with a single claim I’m willing to defend: the rules of good code were written for a reader who is no longer the only one in the room, and it’s time we wrote some for the other one.