For decades, architectural knowledge lived inside the implementation, because the implementation was expensive to reproduce. That expense is disappearing. And as it goes, we're going to find out how much of what we called "architecture" was really the cost of change wearing a disguise.
Cheap code is supposed to make architecture matter less. It's doing the opposite. As implementation gets cheap, architecture becomes both more important and, for the first time, visible.
The argument about AI coding has moved fast.
A year or two ago, the interesting question was whether models could write useful software at all. Then it became whether they could complete features, navigate repositories, run tests, fix their own mistakes, and work for long stretches without someone watching over their shoulder.
For anyone working seriously with the current generation of tools, that argument is mostly over. They can produce a lot of code. Sometimes an astonishing amount.
The argument now is about what happens afterward.
You hear it more and more from people at the frontier of these tools: code generation is no longer the bottleneck. Verification is. The same conversations keep circling back to harness engineering, cognitive debt, apprenticeship, and whether architecture and design still matter when the programmer is increasingly an agent rather than a person. Martin Fowler has framed a live disagreement here. Maybe sufficiently capable models will eventually deal with whatever spaghetti we hand them. Or maybe the properties that make systems understandable to humans (modularity, naming, clear boundaries) matter just as much to agents.
Meanwhile, the people actually attempting highly autonomous software development are running into the walls.
Dex Horthy of HumanLayer has described trying to run a lights-off software factory. For a while it worked. Then, after several serious failures, the team reportedly got to the point where rebuilding parts of the system by hand was easier than recovering from what their agents had produced.
His argument, as reported, is stronger than "tests aren't enough." Tests can give an agent a reward signal in seconds. Most of the costs of bad architecture show up over months or years. Cohesion, conceptual integrity, whether an abstraction makes the next hundred changes easier — those are hard to compress into a fast oracle. His conclusion for right now is blunt: "we're stuck reading the code."
I think he's right about right now.
I don't think he's describing a permanent boundary.
The deeper problem isn't that humans stopped typing the implementation. It's that we spent decades letting architectural knowledge stay implicit in the implementation. Cheap code removes some of the friction that used to protect that architecture, and in doing so it makes architecture both more visible and more necessary.
What was architecture when change was expensive?
In Who Needs an Architect?, Fowler quotes Ralph Johnson's description of architecture as "the important stuff, whatever that is." Fowler follows that to a useful consequence: maybe architecture is the set of decisions developers wish they could get right early. The things people perceive as hard to change. One of an architect's jobs, he argues, is to eliminate irreversibility wherever possible.
The difficulty-of-change test has always been useful because important architectural decisions tend to pick up enormous consequences. Change the tenancy model, the authorization scheme, or the ownership of an important data set, and the effects spread everywhere.
But the history of software has quietly mixed together two very different reasons a thing can be hard to change.
Some decisions are hard to change because their meaning is deeply embedded in the system.
Others are hard to change because rewriting the implementation is expensive.
For a long time those looked like the same thing. A framework choice could shape a company for a decade because replacing it meant rewriting millions of lines of code. A database engine could seem architectural because migrating away from it might cost years of work.
Fred Brooks made a related distinction in No Silver Bullet, separating the essential difficulty of software from the accidental difficulty introduced by the machinery we use to build it. Generative systems are an unusually powerful solvent for some forms of accidental implementation cost. They do much less to the essential difficulty of deciding what a system must mean.
Then code generation gets cheap.
Suddenly some supposedly architectural choices become trivial to replace while others stay every bit as consequential.
Cheap code separates accidental irreversibility from intentional durability. Imagine regenerating a system tomorrow.
The new version uses a different HTTP framework. You probably don't care. It organizes internal types differently. Also fine. It swaps a cache for another implementation while keeping the same behavior and operational characteristics. Still nothing to object to.
But suppose the regenerated system lets the payments service modify the ledger directly, even though the whole thing was deliberately designed so only a narrowly scoped accounting capability could do that.
You care.
Suppose externally visible operations that used to be idempotent can now execute twice.
You care.
Suppose customer data can cross a regional boundary that was intentionally impossible to cross before.
That one you care about most of all.
Which gives us a useful test:
If I regenerated the system tomorrow and this changed, would I care?
If the answer is no, the thing may have been an implementation choice masquerading as architecture because changing it used to be expensive.
If the answer is yes, ask why. The answer will usually point at a boundary, invariant, authority, dependency rule, interface, or safety property that gives the system its shape.
Call it the regeneration test for architecture.
There's an important limit. The test doesn't manufacture architectural understanding. Someone who no longer knows why the ledger boundary exists can't recover that knowledge just by asking whether they'd care if it disappeared.
The test works best as a transcription mechanism. While we still understand the system, it helps us spot which parts of that understanding need to become durable somewhere other than the current implementation.
Regeneration itself has a second job. When we regenerate something and find that a seemingly harmless difference causes a failure, an audit problem, a security hole, or an operational surprise, we've found architecture we didn't know we were carrying.
The regeneration test captures known architecture. Regeneration failures expose unknown architecture.
Architecture is a constraint on possibility
When implementation was expensive, architecture often looked like a plan for construction.
We designed layers, modules, services, schemas, and interfaces, then spent months or years translating those choices into working software. It was natural to think of architecture as the skeleton the implementation got built around.
Jack Reeves argued back in 1992 in What Is Software Design? that source code is the real design of software, because it's the artifact detailed enough to be executed. Cheap regeneration puts new pressure on that idea. If executable detail becomes cheap to reproduce, more of the expensive design work has to become visible somewhere else.
Suppose an agent can produce a hundred plausible implementations of a capability. Eighty pass the obvious functional tests.
The architectural problem was never about producing implementation number 101. It's really about ruling out the implementations we should never be willing to operate.
Payments cannot acquire this capability. Customer data cannot cross this boundary. This operation must remain idempotent. These components cannot share persistence. This subsystem cannot know that another subsystem exists.
There might be thousands of legal implementations inside those constraints. That freedom is useful.
When code was expensive, architecture helped us build it. When code is cheap, architecture helps us reject it.
Better generators don't make architecture disappear. They increase the number of possible answers on the table.
Architecture defines the acceptable region. A definition I find useful: architecture is the set of constraints on how a system is allowed to change. That holds whether the change is made by a person, an agent, or a compiler. The generative-era corollary is that architectural decisions are the things we deliberately refuse to leave unconstrained.
Architecture has to push back
This all sounds abstract until an architectural rule actually rejects an implementation.
Say we've decided that code in the payments domain may not depend directly on ledger persistence. Payments can request accounting operations through an approved interface, but it cannot write ledger state itself.
In Java, an architectural fitness function using ArchUnit might look like this:
ArchRule paymentsCannotTouchLedgerPersistence =
noClasses()
.that().resideInAPackage("..payments..")
.should().dependOnClassesThat()
.resideInAnyPackage("..ledger.persistence..");Now an agent gets a task:
Fix the refund race condition.
It inspects the system and finds a perfectly reasonable local shortcut. Instead of going through the accounting boundary, PaymentService writes directly through LedgerRepository.
The feature tests pass. The race condition disappears. The implementation is simpler.
The architecture rejects it.
Now the agent has to find another path. It routes the operation through the accounting interface and lands the fix a few minutes later.
The constraint didn't tell the generator how to solve the problem. It steered the search away from a solution we'd already decided was unacceptable.
A diagram describes an architecture.
A fitness function participates in maintaining one.
Neal Ford, Rebecca Parsons, Patrick Kua, and others working on evolutionary architecture have spent years arguing for architectural fitness functions for exactly this reason: important architectural characteristics should be continuously evaluated as a system evolves. That advice gets more urgent as the gap widens between a system's implementation and the team's shared understanding of how and why it works.
There's another reason written guidance alone won't cut it.
Empirical work on coding agents at repository scale has started testing what happens when agents do or don't have the facts required for a large change. In The Working Set of a Coding Agent: Coherence Debt in Repository-Scale Tasks, Bardia Mohammadi, Lars Klein, Aman Chadha, Akhil Arora, and Laurent Bindschaedler report a particularly interesting result: when an expected fact was missing, agents often acted anyway, fabricating a file or guessing a value. When a coding standard and the existing code disagreed, agents tended to follow the standard even when it prescribed the worse implementation. In those cases, a stale convention file could cost more than having no convention file at all.
That's a useful warning. Making architectural knowledge explicit isn't enough. Wrong explicit knowledge can steer a generator with more confidence than missing knowledge.
Documented constraints aren't the goal. You want current constraints with consequences.
Where architecture still needs humans
The ArchUnit example is intentionally easy.
Dependency direction is static, syntactic, and cheap to check. A whole useful class of architectural rules fits that shape: package dependencies, API compatibility, capability restrictions, some deployment rules, some data-location policies.
Other properties are much harder. Idempotency is behavioral. Distributed consistency may depend on sequences of events and failures. A security invariant may cross processes and storage systems. You can sometimes attack these with property testing, model checking, runtime verification, or carefully designed evaluations, but doing it takes real work.
Then there are properties like cohesion, conceptual integrity, the quality of an abstraction, and whether the next ten changes are getting easier or harder. We don't have reliable mechanical oracles for those.
This is where the lights-off experiments matter. An agent optimizing against tests can make locally sensible decisions while quietly degrading qualities whose cost won't show up until much later. More loops around the same oracle don't fix that.
Harness engineering helps. A good harness gives an agent the context, tools, permissions, observations, and feedback it needs to navigate a search effectively.
But a harness and an architecture answer different questions.
The harness helps the agent navigate the search. Architecture defines the region in which a successful answer is allowed to land.
Right now we can't describe that entire region mechanically. Experienced engineers can spot architectural trouble in code that every automated check accepts. Reading code is still one of the sensors we use for those qualities.
So no, I don't think teams should stop reading generated code on Monday.
Code review is the bridge. The destination is to keep shrinking the portion of architecture whose preservation depends entirely on tacit human judgment over the current implementation.
Every boundary we make explicit helps. So does every invariant we can evaluate, every authority relationship we can represent. Some judgment may stay irreducible. That doesn't make the rest of the effort pointless.
It tells us where the boundary actually is.
The theory dies with the code
There's an objection here, and it predates AI.
In 1985, Peter Naur's Programming as Theory Building argued that the primary product of programming isn't the program text. Programmers build a theory of the problem and its solution. A mental model that lets them explain the system, modify it, and reason about cases the source never explicitly captured.
This is one of the deepest roots of the current cognitive-debt worry.
When a programmer builds software by hand, some of that theory develops almost by accident. You remember the awkward branch because you wrote it. You know why the abstraction is strange because you tried two others first. You can picture the call path because you spent an afternoon stepping through it.
Agentic coding can strip out that incidental learning.
The agent changes twenty files in ninety seconds. The tests pass. The diff looks plausible. You move on. Do that enough times and the software starts to feel like something happening to the team rather than something the team understands.
A widely circulated Hacker News discussion got attention for the deliberately extreme suggestion that developers manually retype generated code to preserve comprehension. I don't think typing is the right answer, but the anxiety behind the proposal is palpable.
There's a related mechanical reason the theory has to live outside the source. One way to model repository work is as a coupled-fact graph: APIs, tests, migrations, configuration, and other facts an agent must have to make a safe change. Mohammadi and colleagues show that when a fact necessary for correct work is unavailable, the agent doesn't reliably stop at the edge of its knowledge. It can confidently walk right over it.
A tangled system therefore has a large working set. Changing one thing safely requires knowing seemingly unrelated facts scattered all over the place. This is David Parnas's 1972 argument about information hiding (modules should hide the decisions likely to change, so parts of a system can be understood with less knowledge of the rest) now made measurable by an agent's bounded context. A well-structured system shrinks the amount of the world you have to understand correctly before a local change can be trusted.
Even before AI, no senior engineer understood a large system by memorizing every line. We relied on architectural models, domain language, boundaries, interface contracts, operational knowledge, and the distributed theory held by a lot of people.
Humans don't need to hold onto every ephemeral line. They need to hold onto the theory of the system: what it's for, where its boundaries are, who's allowed to do what, what must stay true, which assumptions the design rests on, and what evidence tells us a change is still acceptable.
The regeneration test depends on this understanding. Which means there's a harder problem waiting behind the one in this essay.
Architectural judgment has historically been learned partly by writing software, making mistakes, debugging it, and living with the consequences. If agents increasingly remove that apprenticeship path, we'll need another way to teach engineers how to form the theory that architecture depends on.
I don't think we know what that path looks like yet.
For now, the important thing is not to confuse preserving the theory with preserving every line we happened to learn it through.
If human understanding stays tied exclusively to today's generated source, we've built an architecture that depends on the scarcity of code.
That scarcity is disappearing.
Constraints can become fossils too
Making architecture executable brings its own failure mode.
Constraints aren't eternal truths.
Say we encode the rule that payments cannot depend on ledger persistence. Five years later, the business changes, the accounting model changes, or we discover the boundary itself rested on an assumption that's no longer true.
If nobody knows why the constraint exists, it can become exactly the kind of accidental inertia this whole essay is arguing against.
Only now the inertia has a build failure attached to it.
This is Chesterton's Fence with a build failure attached.
The old warning is that you should understand why a fence exists before removing it. Executable architecture raises the stakes, because the fence can now actively block change long after everyone's forgotten its reason.
So architectural constraints need governance of their own.
A rule should be connected to its reason. Who established it? What decision introduced it? What threat, incident, regulation, or domain principle motivated it? Who has authority to change it? What evidence would make us revisit it?
This is where provenance becomes part of architecture. An architectural constraint without provenance is just another mysterious conditional.
The fact that a rule can push back makes its history more important, not less. When a constraint rejects a generated implementation three years from now, the engineer should be able to ask not only what rule did I violate? but why do we still believe this rule should exist?
Architecture can't become a collection of immortal assertions. The constraints have to be able to change deliberately too.
Cheap change is dangerous
A more unsettling consequence falls out of all this: large systems may become dangerously easy to change.
Historically, the cost of changing software gave us accidental resistance to architectural change. If altering the authorization model required six months of work across dozens of services, nobody did it casually.
That friction was expensive, but it was protective.
An agent can remove an authority boundary in seconds.
Not maliciously. Locally, the change may be completely sensible. Maybe a component needs information that currently takes three calls through a capability boundary. The generator notices the underlying database is right there and simplifies the path.
The tests still pass. The user-visible behavior is unchanged. The diff might even look cleaner.
But the system now means something different. A component holds authority it was deliberately denied.
This is the dangerous part of cheap code.
It can make a semantically significant change look mechanically trivial.
As that protective resistance vanishes, architectural intent has to become more explicit. Otherwise a capable enough generator can make the system easier to modify and easier to accidentally redefine at the exact same time.
Architectural gravity
Not every important decision fits comfortably into a fitness function.
Some decisions exert gravity because so much of the system's meaning depends on them.
Data ownership is a good example.
Say a company decides each domain owns its own authoritative data and other domains interact only through defined interfaces. Years later, someone proposes a shared operational database because it simplifies a handful of workflows.
The code changes may be easy.
The meaning changes everywhere.
Which domain is authoritative? Who can mutate shared state? What happens when one schema needs to evolve independently? Which service is responsible during an incident? Which team can promise what to customers?
The original data-ownership decision has architectural gravity because so many other decisions orbit it.
Implementation complexity measures the work required to express a decision.
Architectural gravity measures how much of the system's meaning depends on it.
Generative tools cut implementation complexity dramatically. They do much less to architectural gravity.
So a high-gravity decision can become mechanically easy to rewrite without becoming any safer to redefine.
Regeneration lets us see the difference.
The hidden architecture becomes visible
Cheap code exposes how much of what we historically called architecture was really expensive implementation.
A framework choice may have stayed put because rewriting was prohibitively costly. A service boundary may have survived because restructuring it meant a dangerous migration. Their long life was never proof of their architectural importance.
Regeneration separates two kinds of stability.
Things stay stable because we want them to. Things stay stable because we can't afford to change them.
Traditional software made those look identical. Generative software starts to pull them apart.
This is also the connection to the Phoenix idea, my own framing for systems whose implementations should be able to burn down and come back while the important knowledge survives them.
But regeneration needs resistance.
Boundaries, invariants, authority relationships, the interfaces we intentionally made durable, operational and security expectations — all of it has to survive. So do the reasons behind the constraints.
Regenerate the constraints, and you get your system back. Regenerate only the code, and you get a different program that happens to pass the same tests.
As implementation gets cheaper, engineering value moves toward deciding what must stay true, where authority lives, which things are allowed to vary, what evidence makes an implementation acceptable, and which parts of the system should stay independently understandable.
Those were always architectural questions. Implementation just consumed enough effort that it was easy to mistake producing an answer for deciding which answers should be allowed.
The model can produce another implementation. It can't, on its own, decide which authority relationships an organization should promise its customers for the next decade. It can't know that a test suite fails to encode the property everyone will care about three years from now. It can't know that an inconvenient boundary exists because of an incident nobody remembers — not unless that knowledge survived somewhere outside the code it's replacing.
So we come back to the test:
If I regenerated the system tomorrow and this changed, would I care?
When we know the answer, we should make that knowledge durable before we lose it. When we don't know the answer, regeneration may eventually teach us, sometimes painfully.
For decades, software architecture was a mixture of deliberate constraints, expensive inertia, and assumptions we no longer remember making. Cheap code is dissolving the inertia. Some of what remains will be the architecture we intended. Some of it we'll only discover when regeneration breaks something we didn't know mattered.
The difference between those two outcomes is the only architecture that was ever real.