ai
The agent prefers the monolith

For most of my career, monolith against microservices was an argument about teams. Microservices let twenty teams deploy without waiting for each other, and a monolith let five people move fast until they could not. The code was written by people, and the architecture followed how those people were organised.
That changed this year. On most days the first draft of a change in our codebase comes from an agent. Before it writes anything, it reads: it searches for the call sites, opens the schema, follows the types, and builds a plan out of what it found. Whatever it cannot read, it guesses. So the question I ask about an architecture now is a different one: how much of the system can the agent see when it plans a change, and how cheaply can we prove the change is right?
I have worked on both sides. At OfficeRnD I helped build the platform that teams used to create and run their services, and I worked on extracting shared capabilities out of the monolith. Today I work on a product that runs on one TypeScript backend, with a couple of small services at the edges. The two answer those questions very differently.
What microservices give an agent
A service with a strict contract is the best specification you can hand a model. An OpenAPI document or a JSON Schema says exactly what goes in, what comes out, and which errors are allowed. The agent does not need to understand the rest of the company to work on one side of it. The service is small, so all of it fits in the context window, and the agent reads every file instead of a sample.
Testing gets easier for the same reason. The contract is the test boundary: generate the requests from the schema, assert the responses against it, and the service is verified without anything else running. Write the contract first, then an SDK and a simulator built against it, and the consumer can be exercised without the real system on the other end. A model is very good at that kind of work, because every answer it gives can be checked mechanically.
Shipping is easier too. A service deploys on its own, rolls back on its own, and a bad change breaks one thing. When an agent makes a mistake, the blast radius is the size of the service, and the contract stops the mistake from leaking into its neighbours.
What they take away
The trouble starts when a feature crosses a boundary, which in a product most features do. Adding a field a customer sees in the app may touch the service that owns customers, the one that owns orders, and the one that sends the notification. The agent working in one repository sees one side of each contract. It does not know who consumes the event it is about to change, so it either asks or it guesses, and it usually guesses. One feature becomes three pull requests, a contract version bump, and a deploy order that someone has to remember.
Then there is the data. Every service owns its own copy of whatever it needs, so the customer lives in four places, kept in step by events that arrive eventually. An agent writing a report has to know which copy is the truth, which ones lag, and which fields were never synced at all. Those facts are rarely written down. They live in the heads of the people who built the sync, and the agent gets none of it. Data duplication was always the expensive part of microservices. With an agent writing the queries, it is also the part most likely to be wrong without anyone noticing.
Running the system locally is the last cost. To verify a change end to end, the agent needs every service, every queue and every database up, or a staging environment it can deploy to. Most of the time it gets neither, so it verifies against mocks, and a mock knows only what it was written to say.
What a monolith gives an agent
In a monolith the agent can read everything a feature touches. One repository, one schema, one type system. When it renames a column, the compiler lists every line that breaks, and the agent fixes them in the same change. A feature that crosses three domains is still one pull request, one migration, and one transaction, and the reviewer sees all of it in one diff.
There is one copy of the data. The customer is a row in one table, and a report joins it to orders and payments directly, consistent at the moment of the query. The agent cannot pick the wrong copy, because there is only one.
Verification is one command. The type check and the test suite run across the whole system, and the agent runs them itself before it hands the change over. That loop is most of how we work now: the agent plans, writes, type-checks, runs the tests, and comes back with a change that has already met the rest of the codebase. In a microservices system the same loop stops at the edge of the repository.
What a monolith costs
A monolith is harder to ship. Every change goes out in the same deploy, a slow test suite slows everyone, and a bad release can take down things that had nothing to do with it. Feature flags and a fast rollback cover most of that, but not all of it.
The other cost is specific to agents. A model takes the shortest path to a working change, and in a monolith the shortest path is often an import straight into another module's internals. Nothing stops it, the tests pass, and after a few months the boundaries are gone. A large monolith also stops fitting in the context window, so past a certain size the agent is back to reading a sample of the code and guessing at the rest.
Both problems have the same fix, and it is the discipline microservices forced
on us. Split the monolith into modules, give each one a public interface and
keep everything else private, and enforce it with lint rules on imports rather
than with good intentions. Give each module its own short AGENTS.md that says
what it owns and what it must not touch. The contract still exists. It is a
typed interface checked by the compiler instead of a schema checked over the
network, and the agent can read both sides of it.
Which one is easier to maintain
Maintenance used to mean typing: reading old code, changing it, and keeping it working. The agent does most of the typing now, so the cost of maintenance moved to verification, and to how much context the agent needs before its change can be trusted.
By that measure the monolith is easier to maintain. Upgrading a library is one change instead of one per service. Fixing a bug that spans two domains is one change with one test run. Microservices are easier to ship one piece at a time, and a contract makes each piece easier to test on its own, but most of the maintenance work is not one piece at a time.
The conclusion
AI development prefers the monolith. Specifically, a modular monolith in one repository, with one database, strict module boundaries enforced by tooling, and the type checker acting as the contract between modules. That is the architecture where an agent sees the most, guesses the least, and can prove its own work before anyone reviews it.
A service earns its place only when the boundary is physical: different hardware, a different network, a different runtime, or a vendor on the other side. The two services at the edge of our product run on networks and devices we do not control, so both sit behind strict contracts. Everything else is one TypeScript backend, one PostgreSQL schema, and one test run that the agent starts before it hands a change back.


