Skip to the content.

AI cannot hold your architecture, so you have to

AI cannot hold your architecture, so you have to

A language model answers the prompt in front of it. It does not carry a model of your whole system between requests, it has no stake in whether your architecture survives the next quarter, and it will happily optimise the current task in a way that is locally sensible and globally corrosive. This is not a flaw you can prompt away — it is structural. The context window is finite, each request is largely fresh, and “make this one thing work” is a different objective from “keep the system coherent.” So the architecture — the boundaries, the invariants, the shape of state — is the one thing that cannot be delegated. Someone with continuity and a stake has to hold it, and that someone is you.

Each locally-optimal task nudges the structure until it drifts A straight architecture line at the top. Below it, a series of individual task fixes each pulling slightly off-line, and the accumulated path drifting far from the intended structure. intended architecture each "just make it work" drift nobody decided on gap
No single task moves far off the line. The sum of a hundred locally-reasonable fixes is a structure no one chose.

Local optimisation, global erosion

The mechanism is easy to watch. Ask for “show the user’s name in the header” and the shortest correct answer is to fetch the user inside the header component. Ask for “add a total to the cart summary” and the shortest answer is to store the total as a field. Each is fine in isolation. Together they put fetching in the UI layer and derived data in the store — two boundaries gone — and no reviewer saw a “bad” diff, only a series of reasonable ones:

// locally reasonable, globally wrong: a presentational header now fetches
function Header() {
  const [user, setUser] = useState(null);
  useEffect(() => { fetch("/api/me").then(r => r.json()).then(setUser); }, []);
  return <span>{user?.name}</span>;   // the boundary didn't break; it dissolved
}

The fix is to externalise the memory the model lacks

You cannot give the model continuity, but you can put the architecture outside it, where every task must pass through it. That is what a written spec plus executable checks are for: the invariants live in the repo, not in someone’s head, so a locally-optimal change that violates them fails loudly:

// the architecture, made explicit and enforceable
test("presentational components never fetch", () => {
  const uiFiles = glob("src/ui/**/*.jsx");
  for (const f of uiFiles) {
    expect(read(f)).not.toMatch(/\bfetch\(|useQuery|useEffect.*fetch/);
  }
});

Now the fetching header does not merge, and the model’s missing memory is supplied by a check that never forgets.

Own the shape, delegate the typing

The healthy division of labour is precise: let the model do the typing — generate the component body, fill in the boilerplate, draft the test — and keep for yourself the decisions that require holding the whole system in mind: where this state belongs, which layer may talk to which, what the store’s shape is allowed to be. Then encode those decisions so they survive the next hundred generations. The engineers who struggle in this era are the ones who tried to compete with the model at typing; the ones who thrive moved up to owning the structure and made that structure executable. The harness-state-shape and presentational-vs-container exercises are deliberately about that boundary — the one the model will erode by default and you have to hold on purpose.