How We Build Software: The Decisions Underneath
Should this be one service, or three? Where does this boundary belong? Has this complexity been earned? Questions like these decide whether a system is still serviceable in five years, and none of them is a coding problem.
Where the Decisions Live
On every project we run, the architecture lives in its own Git repository: the project’s constraints, the drivers shaping the system, its architecture style, its quality goals, and the decisions themselves. An Architecture MCP Server, which we built in-house, serves that repository to AI agents on demand. We have written up the reasoning in Design-First Vibe Coding: Making AI Work at Enterprise Scale and the mechanics in The Implementation Guide.
Significant decisions are kept as architecture decision records, each with the same parts: the context, the decision, the options we considered and why each one lost, and the consequences we accepted. The one below is real, from a modernization program we’re delivering, with the client details removed.
The Question
The program was splitting a set of legacy applications into 28 services. Each application kept its modules’ data in one shared database schema. In one of them, a single central table was read by most of the modules and written to by most of them as well. Work was delivered one business process at a time, so the same question came up with every process: when it moves to its new services, how does its data move out of the shared schema?
The Options
Keep the shared schema. Rejected in one line: it is the coupling the new design exists to remove. It is in the record anyway, so the first question a newcomer asks already has an answer.
Split the code first, migrate the data later. Run the new services against the old schema for a period, then separate the data afterwards. This option has a real advantage, and the record says so: rollback is just a routing change. It lost anyway. The new services would have had to read and write the old data model through a translation layer. The riskiest step, splitting the data under a live service, would have landed after go-live. And bridging that period would have needed change-data-capture tooling.
A separate database instance for each service. Rejected on cost. Running 28 instances is expensive, and nothing at that scale justified it when logical separation gives the same ownership.
One schema per service, on the shared database instance. This is the one we chose. Each service gets its own schema, and ownership is enforced by database permissions rather than by convention. Every table has exactly one service allowed to write to it, and every other service reaches that data through the owner’s APIs or events. Data moves by migrating first and activating second. Within a scheduled window, the old services stop, the data is migrated into the new schemas and checked against record counts and control totals, and only then is traffic switched to the new services.
What the Choice Costs
The record is just as specific about the price:
- Downtime. Each migration happens in a scheduled window in which the affected endpoints are unavailable. Its length is driven by data volume and agreed with the business for each one.
- Rollback becomes a data operation. Switching traffic back restores the old path, but anything the new services wrote exists only in the new schemas, so going back means migrating that data in reverse or agreeing to give it up. The old tables stay read-only until a deliberately short rollback window closes.
- Some releases get bigger. Processes that write the same data have to move together.
What it buys is the property the whole decision rests on. Every service owns its data from the first day it takes traffic. There is no interim period where old and new share a schema, and no translation layer to maintain and later remove. The record puts the trade in one sentence: migrating first buys a simpler steady state at the cost of a scheduled window.
Why the Rejected Options Matter Most
The chosen option is easy to find later, because it is in the code. The rejected ones leave no trace there. Without the record, splitting the code first can easily be proposed again, because its advantage is real and easy to see and the reasons it lost are not.
A record like this does two jobs. It keeps the decision auditable years later, even if the people who made it have moved on. And it gives an AI agent a worked example of how this kind of call gets made here: which drivers mattered, which costs were acceptable, and which were not.
Today, and later
An agent working from the architecture repository can do more than write code against it. Because the drivers, constraints and quality goals are written down, it can check a proposed decision against them, surface conflicts, and lay out the trade-offs before anything reaches code. A developer working through a story can reason a question out with it. What it produces is a recommendation, and a person decides.
The boundary between recommending and deciding isn’t fixed. As agents get more reliable, some decisions like this one may be partially or fully automated. When that happens, it will depend as much on the record as on the model, because the rejected options and the costs behind a call are exactly what an agent cannot infer from the code. We are building that record now.
What doesn’t move is accountability. Design ownership and the final say stay with the people who answer for the system. It is the same line we hold for our operations agents, which investigate incidents but do not act on them.
Written for people. Read by machines. Ready for what’s next.



