Own the Architecture, Not the Implementation

AI made code cheap to write. It did not make complexity free to own. Where I've landed on overengineering, maintenance and what an engineer is actually responsible for now.

contents (5)
  1. Cheaper to write is not cheaper to own
  2. The devil’s advocate case
  3. Where it actually breaks: invariants
  4. What I think the engineer owns now
  5. Verifiable beats readable

I started with a question I half expected to lose: is overengineering acceptable now, as long as it doesn’t raise the infrastructure bill?

The old argument against overengineering was mostly about time. Why spend three days building an extensible architecture when one function solves today’s problem? Developer hours were the expensive part, so you spent them on what you needed and nothing else.

That argument has weakened. An AI agent can produce the extensible version in minutes. If the hosting cost is the same either way, the main reason to keep things small seems to disappear.

I don’t think it disappears. But I’ve changed my mind about where the cost went.

Cheaper to write is not cheaper to own

Picture the same app built two ways:

Simple Overengineered
Time to build 2 hours 3 hours with AI
Infrastructure $20/month $20/month
Code 1,000 lines 5,000 lines
Built for Today’s requirements Today’s, plus some guesses about tomorrow
Things that can interact badly Few Many more
Attack surface Small Larger

The infrastructure line is identical. Most of the other lines are not. The question is no longer whether AI can write 5,000 lines as cheaply as 1,000. It can. The question is whether the extra 4,000 lines create more value than liability over the life of the system.

It helps me to split “overengineering” into three different things, because they deserve different answers.

Defensive engineering is almost always worth it now. Input validation, bounded retries, idempotency, structured logging, failure handling, concurrency tests. These used to feel like too much for a small app. AI has made them cheap to write and cheap to test, so skipping them to save time is harder to defend than it used to be.

Architectural engineering depends. Modules, interfaces, adapters, queues, plugin systems. These earn their keep when they separate things that really do change independently. They don’t when they are built around requirements nobody has.

Accidental complexity is still bad. Five layers around one database query, a generic factory with one implementation, two mechanisms keeping the same state in sync. Even if AI wrote every line, someone (or some model) has to work out how those pieces interact when something breaks.

The devil’s advocate case

Then I pushed the other way, and I think the argument is stronger than it first sounds.

Maintenance used to mean reading code. If you can describe a problem precisely, an agent can now read the code for you: trace the call path, find the defect, write the regression test, make the fix. The engineer doesn’t have to dig through the complex parts personally. So the classic claim that every extra abstraction costs a human maintainer a lot of time is no longer quite true.

The one risk I couldn’t argue away is dependency. If you understand the workflow but not the code, and the AI is unavailable, that code is dead weight.

Even that is narrower than it sounds. A running system doesn’t need AI to keep running. The servers, databases and compiled code carry on without it. The exposure is a critical defect during an outage, when nobody can read the code and the agent can’t be reached. That’s a real risk, but it’s an argument for planning around it (tests, rollbacks, written architecture, incident runbooks), not an argument against the complexity itself. And AI is good at producing exactly those safeguards.

Where it actually breaks: invariants

What knocked a hole in my own position wasn’t comprehension. It was correctness.

I learned this one the hard way. I had a rate limiter whose rule was simple: anonymous users get three scans a week. I understood the workflow perfectly. The implementation passed every test I had. Then a script sent forty requests in the same second and all forty got through, because the code checked the count and recorded the scan in two separate steps, and concurrent requests slipped between them.

Nobody misunderstood the workflow. The specification just didn’t say “including when forty requests arrive at once”. If I had handed an agent “fix the rate limiter, three scans per week”, it could easily have produced another version that passes sequential tests and fails under load.

So AI doesn’t remove the cost of complexity. It moves it. You pay less in writing and reading code, and more in verification. If every change to a tangled architecture needs extensive testing because its parts interact in ways nobody predicted, you are still paying for complexity. It is just billed in a different currency.

What I think the engineer owns now

Where I’ve landed: the engineer owns the architecture, the constraints and the correctness of the system. AI owns much of the implementation.

That isn’t handing over judgment. It’s moving it up a level. For a scanning service, the architecture I would write down looks like this:

  1. A user submits a URL.
  2. Gate: validate the URL and refuse anything that resolves to a private network.
  3. Gate: authenticate the user and enforce their plan limits.
  4. Gate: enforce rate limits atomically, including under concurrent requests.
  5. Run the checks within fixed time and resource limits.
  6. Produce a report with a consistent schema, findings that reproduce under the same conditions, and enough diagnostic detail to explain why two scans of the same site differ. (Websites change between scans, so “identical every time” is the wrong promise.)
  7. Test every gate, including how it fails.

When something goes wrong, I don’t need to know which function holds the bug. I know which boundary failed. The instruction to an agent stops being “something’s off with scans” and becomes “the rate-limit gate allows concurrent requests past the quota. Trace the enforcement path, fix it, and show the limit holds under concurrency.” The scope is clear, and the regression can be traced to a part of the system instead of guessed at.

That assumes I’ve picked the right boundary, and I won’t always have. A symptom can show up in one place while the cause lives somewhere else: a broken report might really be a database transaction, a queue retry or a bad intermediate result. Architecture narrows the search. It doesn’t promise the defect is inside the first boundary you point at.

There’s one adjustment I’d make to my own rule. You don’t need to know how every function works, but you must know why the system is shaped the way it is, and you must own the implementation choices that carry an architectural guarantee. An atomic update versus read-then-write is an implementation detail that decides whether the system is correct. If an agent proposes moving rate limiting from a shared store into application memory, the change will pass every test on one machine and fail the moment there are two. Approving that is an architecture mistake, not a coding one. That’s why concurrency, consistency, security boundaries and failure recovery still matter even when you write very little of the code yourself.

Verifiable beats readable

The conclusion that surprised me most: readability may matter less than verifiability.

A complicated system with good tests, clear contracts, observable behaviour and deterministic output may be easier to own than a beautifully simple one with no tests and no written design. A few years ago I would have called that heresy. I don’t any more.

That isn’t permission to write unreadable code. The two aren’t opposites. Readable code helps us understand what a system might do. Verifiable behaviour establishes what it actually does. Readability still matters; it’s just no longer the quality I’d put first.

The question I ask has changed too. It used to be “what is the simplest thing that works?” Now it’s “what is the most robust design whose complexity I can still verify?” Those lead to different systems, and the second one isn’t always the smaller one.

Which brings me back to where I started, with a different answer. I asked whether overengineering is acceptable now. What I ended up arguing for is more rigorous engineering, which is not the same thing. Concurrency tests, idempotency, observability and written contracts are just good engineering, and AI has made them cheap enough that there’s little excuse to skip them. Six layers of abstraction nobody needs are still overengineering, no matter who wrote them.

The technical debt I worry about now isn’t code nobody understands. It’s behaviour nobody can verify.