When Code Gets Cheap, Architecture Gets Expensive

May 31, 2026

The agent did exactly what I asked. I deleted all of it.

I was fixing a small bug in a side project. The fix I had in mind was four lines. The agent came back with a subsystem: configuration, structured logging, documentation, and a test suite that passed on the first run. It handled edge cases I would never hit. By every local measure, it was better than my fix.

It was also wrong for the project.

Keeping it meant that six months later I would need to understand a configuration system just to change a default. The code worked. The cost arrived after the code was written.

It wrote the tests. It added the logging. It passed CI. The right answer was still: delete the branch, change one line.

That is the part of agentic coding I think we are underpricing. Code has become cheap to produce. It has not become cheap to understand, change, or remove. Every line you accept is still a promise to know why it exists.

Cheap code changes the default

Before agents, larger solutions had to justify their cost.

The four-line fix took a minute. The subsystem took an afternoon. Both were possible, but the difference in effort forced a decision before the code existed. Most of the time, the larger solution was not worth typing.

That difference has disappeared. The one-liner and the subsystem now cost the same prompt. The agent reaches for the complete-looking answer because complete-looking code is what it knows: configuration, retries, abstractions, tests. It knows what production code looks like. It does not know how little this particular problem deserves.

I do not think agents are uniquely bad architects. They make it possible to produce a large solution before deciding whether the problem deserves one.

Fred Brooks had the useful distinction. Accidental complexity comes from the tools: syntax, boilerplate, glue. Essential complexity comes from the problem itself: deciding what the system should do and how its pieces should fit.

Agents crush accidental complexity. They can produce the syntax, boilerplate, and glue almost instantly. But deciding whether the subsystem should exist was never a typing problem.

Understanding is the cost

Code has two prices. The price to produce it and the price to keep it.

Keeping code means reading it, validating it, fitting it into the rest of the system, debugging it later, and explaining it to the next person. Agents pushed the first price toward zero. The second price barely moved.

Every prompt is a chance for scope creep. Ask for one behavior without drawing a hard boundary and the agent will add whatever makes the solution look complete. It will not stop to ask whether those additions belong in the system. If you don't read the diff, then bam: you own a subsystem that you never decided to build.

One line is how much code can be produced. The other is how much one engineer can hold in their head. Agents bend the first line upward. The second barely moves. The shaded space between them is code that exists without anyone carrying a complete model of it. The curves are illustrative, not measured.

The bottleneck is not generation. It is comprehension.

Clean inputs, clean outputs

The practical answer is clean I/O between components.

Each component should accept a clear input, produce a clear output, and keep the rest to itself. That gives me room to vibe code the heck out of one area without spreading its implementation across the codebase. If I get it wrong, I can revert or replace that component. I do not have to unwind the whole system.

Without a clean interface, the generated code reaches outward. It adds shared state, new configuration, assumptions in other modules. Reverting the feature now means finding every place it touched. The experiment stopped being local.

A component should expose what it promises and hide how it works. Code outside the component depends on the interface, not the implementation. The inside can change without forcing the rest of the system to change with it.

The same components, wired two ways. In the monolith, every component can reach the others. In the bounded version, a few interfaces carry the traffic between groups. The amount of code does not change. The amount of code each part must understand does.

Microservices took years of justified hate. They turned function calls into network calls and brought deployment, observability, and distributed-state problems with them. For a lot of teams, the extra boundary cost more than it protected.

That trade changes in an agent-heavy codebase. If the development model is to get lazy and let the LLM rip, microservices probably win. The network boundary is expensive, but it is real. The agent can make a mess inside one service without quietly threading its assumptions through the entire codebase. If the service is wrong, it can be reverted, replaced, or thrown away while the rest of the product keeps talking to the same API.

That only works when the API is clean. Shared databases, cross-service state, and imports that reach around the interface create the worst version of both architectures: a distributed monolith generated faster than anyone can understand it.

Know when you can be lazy

Agents generate a lot of code. A smart, productive person will use them to get more done and generate even more. That creates a review problem.

Keeping code responsibly means reading it and understanding what it does. That takes energy. Every review pulls new names, state, and assumptions into your head. Do it across several tasks and the constant context switching gets exhausting. Eventually you either stop moving quickly or start approving code you did not really read.

The answer is not to review everything the same way. Generated code needs a level of scrutiny that matches how long it will live and what it can break.

  • Throwaway code: a script that reproduces a bug, inspects a file, or fixes one piece of data. Check that it will not destroy anything and that the output makes sense. Run it. Delete it. There is no prize for building a complete mental model of code that will be gone in ten minutes.
  • Tools: a plot generator, benchmark, formatter, or internal utility that sits outside the product's core behavior. Its inputs and outputs should be clean, its permissions narrow, and its failures obvious. It deserves tests around the result, but not the same review as production code in the hot path.
  • Core code: the code that serves users, owns persistent state, makes security decisions, controls hardware, or sits in the hot path. Read it. Understand the state it adds, the assumptions it makes, and how it fails. A passing test suite does not replace that understanding.

A plotting script can be ugly and still be fine. An authentication path cannot.

This is permission to spend attention unevenly. Be lazy with code that is isolated and disposable. Be exacting with code that can become load-bearing. The mistake is not generating a lot of code. The mistake is letting lightly reviewed code drift into a high-consequence role.

You can be lazy with throwaway code. You cannot be lazy about deciding which code is throwaway.

Delete more

The best engineers reduce complexity and aggressively delete code.

Agents make it easy to add more than the problem needs. When a solution comes back in three hundred lines, do not polish all three hundred. Find the behavior that matters, keep the smallest version, and delete the rest.

Code that does not exist needs no review, documentation, tests, debugging, or room in anyone's head.

Agents made code cheap. Good engineers keep systems small.

References

  1. Frederick P. Brooks, Jr. “No Silver Bullet: Essence and Accidents of Software Engineering”. University of North Carolina at Chapel Hill, Technical Report 86-020, 1986.
  2. James Lewis and Martin Fowler. “Microservices: a definition of this new architectural term”. martinfowler.com, 2014.