The Decisions No Agent Makes
AI has changed how our code gets written. But it hasn’t changed the decisions that determine whether a system will still be serviceable five years from now: how it is decomposed, where its boundaries sit, and the judgment behind those choices.
Yet most conversations about AI-assisted development focus somewhere else.
How is the MCP server built? Which agent do we use? How much of the code is generated?
These are useful questions, but they are questions about tooling. The more important question is what guides those tools in the first place.
This is about those decisions: what they look like in practice, who makes them, and where they get written down.
Since we published our two posts on design-first vibe coding, nearly every question we’ve had has been one of those. The first two we answered in the implementation guide; the third has no honest answer, since the proportion moves with the project and the phase.
Every one of them is about tooling, and none reaches what decided whether any of it worked. An agent can only work from the design it is given, and no tool produces dependable judgment.
AI didn’t lower the bar, it forced us to raise it. This post is about what the bar consists of.
We have been building software for banks, large enterprises and government entities for more than 20 years, with around 300 engineers delivering to clients in more than 17 countries across EMEA and beyond. The systems are long-lived, the environments are regulated, and structural mistakes are expensive to unwind.
Technology is Rarely the Hard Part
We are not attached to a particular stack. Most of our current work is Java, on Spring Boot or Quarkus, with Angular or React on the front end, and we have delivered in .NET where the client’s estate called for it.
Language and framework are among the easier decisions on a project. The harder ones sit underneath, and the one that comes up most often is how far to decompose. Over-decomposition is the failure we see most in enterprise microservices work: each service is easy to justify on its own, and the result is an estate nobody can operate comfortably:
- Dozen deployables that always release together
- Distributed transactions where a local one would have been fine
- A network hop where there used to be a function call
Much of our early architectural work is spent testing that first cut and re-cutting it: merging where a boundary was never load-bearing, splitting where one service held two models that change independently.
It usually settles coarser than it started, and that direction is deliberate. A service that grew too large can still be split; services whose data has become entangled are expensive to pull apart. There is no correct decomposition, only ones that are cheap or expensive to live with for years.
Three Calls, and How They Went
Complexity We Added
We are currently moving a high-volume billing and payment platform off a polling and batch pipeline onto an event-driven design. Settlement confirmations arrive from a third-party network on timing we do not control, the reconciliation window is imposed from outside, and a batch cycle that silently missed one surfaced the problem long after it was cheap to fix.
Events match how the business process behaves. So far the difficult part has had less to do with Kafka than with deciding what a domain event should mean when the legacy pipeline only ever produced row changes.
A Design We Refused
On another enterprise engagement we objected to a design that would have routed business process case operations through a component built for a different purpose. It would have demonstrated well enough, and it would also have left process state somewhere that could not be queried, supported or audited — a cost arriving years later, for whoever was running the system by then. We raised the objection formally, in writing, and it was accepted.
A constraint we inherited. Not every call goes this way. On one modernization project we built clean services and then connected them to the legacy estate on the estate’s terms, keeping the old channels alive on an integration style our own architecture would not have chosen. Every accommodation was defensible on its own. What we did not do was confirm a date for retiring them, and an interim arrangement with no end date stops being a transition.
That one is a longer story for another post.
All three calls point the same way: consulting on the design is not a service bolted onto delivery. It is the part of delivery that decides whether the rest was worth doing.
The Tooling Came Second
Decisions like these have to stay consistent across technologies with little else in common, which raises the question of where they get written down.
Every project gets its own architecture repository in Git: constraints, drivers, quality goals, decisions, high and low level design. An Architecture MCP Server syncs from it and exposes it to agents on demand, so an agent implementing a story reads authoritative design artifacts rather than inferring the architecture from the codebase.
We built the server in-house. The engineers who work against it are the same ones who sit in the design sessions with the architects, rather than receiving the output.
That arrangement, not the tooling, is the part that is hard to copy. The dependency runs opposite to how this story usually gets told: the MCP server did not give us architectural discipline, it had something to serve because the discipline was already there.
What Review Has to Catch
Not all of our code is AI generated, engineers write a great deal of it themselves, usually where the design is least settled. What is AI generated goes through two reviews. An agent checks the implementation against the design artifacts and coding standards, returning findings traceable to whichever artifact it contradicts. The human review covers what the artifacts do not, because agent-written code fails differently: it is clean, idiomatic and locally sensible, and the problems are structural, like a boundary crossed because the shortest route to a working result went through it.
Tests have the same problem in a sharper form: an agent that wrote the implementation will happily write tests asserting that it does what it does. So tests derive from the design artifacts and business requirements, not from the code. None of it matters unless an engineering manager holds the gate closed when a date is slipping.
What We Don’t Hand to an Agent
Building AI systems for clients, we are always asked for agents that inform a decision-maker rather than replace one, especially where the decisions carry real consequences. It is the same practice we apply to our own generated code.
Coding agents are widely available and the differences between them are narrowing. They will write whatever code you ask them to. What none of them supplies is the decision about whether a service should be split, where a boundary belongs, whether complexity has been earned, or whether a requirement fits the problem behind it. Those decisions get made by people, they get made early, and everything downstream inherits them.
An agent given a good design accelerates good work, and an agent given a poor one accelerates the opposite of good work faster than anybody can review it.
So the question we find interesting is not how much of your code an AI writes. It is whether the design it is working from was ever worth writing down.






