BrainGrid
How-tos

What Is a Modular Monolith? The App Structure That Keeps Your AI Agent From Hitting the Complexity Wall

What is a modular monolith, in plain language for AI builders: one app with clear internal boundaries, and why it is the structure that stops your agent from fixing one thing and breaking ten as your codebase grows.

BrainGrid Team
14 min read
What Is a Modular Monolith? The App Structure That Keeps Your AI Agent From Hitting the Complexity Wall

Your AI agent builds the first three features of your app beautifully. Somewhere around the fifth, it starts breaking the first three. Nobody warns you that the structure of your code, not the skill of your agent, is what decides when that day arrives.

The default is a single tangled blob. Everything can reach everything, because the agent had no reason to draw a line anywhere, and no prompt told it to. For a weekend project that is fine. But a blob has a ceiling, and you hit it the moment your app grows past what the agent can hold in its head at once. After that, a change to the billing logic reaches into the user logic reaches into the dashboard, and the agent, unable to see all those connections, "fixes" one and quietly breaks two. The usual advice at this point is to split into microservices. For a solo builder, that is almost always the wrong move. There is a structure in between, and it has a name.

The confusion is real enough that the top Reddit result for the term is literally titled "I don't understand the point of modular monolithic". This post is the plain-language answer, aimed at the person whose situation the architecture blogs never picture: someone whose agent is writing most of the code, fast, in an app that is outgrowing a single prompt. The hypothesis is simple: a modular monolith is the structure that keeps an agent coherent as your codebase grows, because it draws the boundaries the agent cannot infer on its own.

#What a modular monolith actually is

Start with the two words. A monolith is one application you build and deploy as a single thing. One codebase, one process, one deploy. Most apps an AI agent builds start life as a monolith, whether you called it that or not. Modular means that inside that single app, the code is split into separate modules with clear walls between them: users here, billing there, notifications over there, each one a self-contained box.

Put them together and you get a software design pattern where an application is built as a single, unified deployment unit but is strictly divided into independent, loosely coupled modules. The word doing the work in that sentence is strictly. In a messy monolith, any part of the code can reach directly into any other part, grab its data, call its internal functions, depend on how it works inside. In a modular monolith, each module hides its insides and exposes only a small, deliberate front door. The billing module does not reach into the user table directly. It asks the user module through a defined interface, the same way one service would ask another, except nothing leaves the single app and nothing travels over a network.

Shopify, which runs one of the largest modular monoliths in production, learned this the hard way. In their engineering writeup on deconstructing their monolith, they named the root cause of the pain plainly:

All of the issues we experienced were a direct result of a lack of boundaries between distinct functionality in our code.

Read that again, because it reframes what the problem actually is. The pain of a growing codebase is not that it got big. Plenty of huge codebases stay workable. The pain is the lack of boundaries, code that can reach anywhere, so a change anywhere can break anything. Size is just what makes the missing boundaries start to hurt. And notice what Shopify did not do: they did not break the app into microservices. They kept the single deployment and drew the walls inside it, because, as they put it, splitting into services would mean they would "have to maintain multiple different test and deployment pipelines and take on infrastructural overhead for each service." The fix for no boundaries is boundaries, not a dozen new servers.

Here is the analogy that makes it stick. A messy monolith is a studio apartment where the bed, the kitchen, and the bathroom share one undivided space. Cheap to build, fine for one person, and completely unworkable the moment you add a second occupant who needs to shower while you sleep. A modular monolith is the same square footage with interior walls and doors. Same building, same single front entrance, same lease. But now a change in the kitchen does not wake up the bedroom, because there is a wall, and the only way between rooms is through a door you can see. Microservices, by contrast, are three separate buildings across town connected by courier. Sometimes you need that. Usually, for one person, you just need walls.

#Why AI builders hit this wall specifically

Search "modular monolith" and you get Milan Jovanović, Thoughtworks, a six-year-old DZone post, and a stack of .NET and Java tutorials. Every one of them is correct. Every one is written for a professional backend engineer on a team, someone choosing an architecture on purpose, up front, with a whiteboard. None of them is written for the person whose situation is genuinely new: someone who never chose an architecture at all, because the agent just started writing, and now there is an app and no walls anywhere in it.

That is the real problem, and it is worth being precise about why AI makes it sharper. In the old world, structure was a coordination problem between people. You drew module boundaries so that twelve engineers would not step on each other. The boundary was a social contract.

AI changed what the boundary is for. Your agent is not twelve engineers. It is one worker with a memory that fills up and then starts dropping things. When your whole codebase fit in its context window, it could see every connection, so it kept them straight. Once the app outgrows the window, the agent is working blind at exactly the places where one part of the code depends on another, and those invisible dependencies are where things snap. This is the same failure mode we described in why your AI agent keeps breaking things that worked: the agent did not get worse, it just ran out of room to hold how the pieces connect. A module wall is one of the few things that fixes it, because it makes a connection impossible unless it goes through a visible door. The agent no longer has to remember that billing secretly reads a field on the user record. The wall will not let it, so there is nothing to forget.

Consider the difference between two ways the same app can be shaped.

Blob: every file can import every other file. The orders code reads the user's rows directly, writes to the notifications table directly, and reaches into payment internals. When the agent changes how users are stored, it has to find and fix every place that touched users, and it cannot see all of them.

Modular: orders, users, payments, and notifications are separate modules. Orders can only talk to users through the user module's front door. When the agent changes how users are stored internally, nothing else even notices, because nothing else was allowed to depend on the internals in the first place.

Same features. The first shape gets more dangerous with every feature you add, because the number of hidden connections grows faster than the agent's ability to track them. The second shape stays flat, because the walls cap how far any single change can reach.

Loading diagram...

In the blob, a change to any box can ripple to every other box, and the agent cannot see all the arrows. In the modular version, each arrow is a deliberate door, so a change inside one box stops at its wall.

#The reframe: the module boundary is a decision you make, not a default the agent picks

This is the move that runs through everything we write about building with agents. The thing that keeps AI from quietly wrecking your app is not a smarter model, it is intent someone wrote down. A modular monolith is that idea applied to the shape of your application itself.

Left alone, an agent will not draw boundaries. It has no opinion about where the walls in your app should go, because walls come from understanding the domain, which parts of your product are genuinely separate concerns and which always change together. That understanding is yours. "Billing is its own module and nothing outside it touches a Stripe object directly" is a decision about your product. "The notification system is separate from the thing that triggers notifications" is a decision. The agent can lay the bricks once you have decided where the walls go, but deciding where they go, naming the modules and what each one is allowed to know about the others, is judgment the agent does not do for you.

There is a real trade-off here, and pretending otherwise would be dishonest. Walls have a cost. A modular monolith takes slightly more effort up front than letting everything touch everything, and if you draw the boundaries wrong, in the wrong places, too many of them, too rigid, you can make simple changes annoying instead of safe. Over-modularizing a tiny app is its own kind of mess. And a modular monolith is genuinely not the answer to every scaling problem; when you truly need independent teams deploying independently, or one piece of your app has wildly different scale needs than the rest, real microservices start to earn their complexity. The honest version is this: for the overwhelming majority of solo and small-team AI builders, a modular monolith is the right ceiling, far higher than a blob and far cheaper than microservices, and you will know you have outgrown it long before you actually have.

#Where this fits in the loop

This is the gap BrainGrid is built to close. When you describe what you are building, the Planning Agent turns it into a requirement with explicit structure and acceptance criteria, including the decisions you would otherwise leave to whatever the agent invented that afternoon: what the modules are, what each one owns, what is allowed to depend on what. "The billing module is the only thing that talks to Stripe, and nothing else reads payment state directly" becomes part of the standard the Builder Agent builds against, whether that build runs in the BrainGrid Cloud sandbox or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP.

That is the loop: Plan the shape of the app along with the features, Build against it, Verify the boundaries actually held, Repeat. A change that quietly tunnels through a wall does not slip by, because "orders never touches the payment internals directly" is written down as a criterion, and a feature is not done until every criterion is verified with evidence. The agent will happily tear a hole in a wall to make a feature work faster, exactly the way it will happily hardcode a secret or skip an auth check, unless the wall is part of the standard it is checked against. Structure that exists only in your head is structure the agent cannot honor.

#What this means if you are building right now

If your app has more than three or four features and your agent has started breaking things it built earlier, the fix is probably not a better prompt and definitely not microservices. It is walls. Look at your app and name its real parts: the handful of concerns that are genuinely separate, users, billing, the core thing your product does, notifications, whatever yours are. Make each one a module with a clear front door, tell your agent that modules talk to each other only through those doors, and write that rule down as something the build gets checked against, not a note you hope it remembers. Then the next time the agent changes one module, the blast radius stops at the wall instead of rippling through the whole app.

The uncomfortable truth underneath this is the same one that runs through every honest conversation about building with AI. The agent is not too dumb to keep your codebase coherent as it grows. It is working blind at the connections, because the connections were never turned into boundaries it could see, and no amount of context window will hold a structure that exists only in your memory of what the app is supposed to be. Decide where the walls go, make them criteria, and verify they hold. For the agreement that governs a single one of those doors, we wrote about what an API contract is, and for turning any structural rule like this into something an agent can actually check, how to write acceptance criteria an AI agent can verify.

#FAQ

#What is the difference between a modular monolith and microservices?

Both split your app into modules with clear boundaries. The difference is deployment. A modular monolith keeps all the modules inside one application that you build and deploy as a single unit, so the modules talk to each other through fast in-process calls and you avoid the hard parts of distributed systems: network latency, services being down independently, data that is briefly out of sync across services. Microservices split those same modules into separate applications, each deployed on its own, talking over the network. Microservices buy you independent scaling and independent deployment, at the cost of real operational complexity. For a solo builder or small team, a modular monolith gives you most of the organizational benefit (clear boundaries, changes that do not leak) without the distributed-systems tax, which is why it is usually the right first structure and why many teams treat it as a stepping stone they may never need to leave.

#What are the pros and cons of a modular monolith?

The pros: clear internal boundaries mean a change in one module is far less likely to break another, which is exactly what keeps an AI agent coherent as the codebase grows; it deploys as one simple unit with no networking or orchestration to manage; in-process calls between modules are fast; and if you ever do need to extract a module into its own service, the wall is already there, so the split is straightforward. The cons: it takes a little more discipline up front than a tangled blob, since someone has to decide where the walls go; badly chosen boundaries (too many, too rigid, or in the wrong places) can make ordinary changes feel bureaucratic; and it does not give you independent per-module scaling or deployment, so if you genuinely need those, it is not enough on its own. For most AI builders the pros dominate, because the main thing you are fighting is not scale, it is your agent breaking things as the app grows.

#How modular can your monolith go?

As modular as your product's real seams, and no more. The boundaries that earn their keep are the ones where two parts of your app are genuinely separate concerns that change for different reasons: billing versus users, the core feature versus notifications, anything that talks to an outside service versus the rest of your code. Those deserve walls. The parts of a single feature that always change together do not need a wall between them; splitting those just adds ceremony without adding safety. The skill is not "make everything a module," it is knowing which boundaries are load-bearing, the ones where a silent cross-dependency becomes a feature the agent breaks later, and drawing walls only there. Getting that judgment right is the part of architecture an AI agent does not do for you, which is exactly why it belongs in your plan and not in the agent's short-term memory.

BrainGrid is the AI Product Planner that turns your idea into a requirement with acceptance criteria, including the structure your agent builds against, so a growing codebase stops meaning "fix one thing, break ten." Try it at braingrid.ai.

About the Author

The BrainGrid team building tools to help developers ship better software with AI.

Want to discuss AI coding workflows or share your experiences? Find me on X or connect on LinkedIn.

Get Started

Ready to build without the back-and-forth?

Turn messy thoughts into engineering-grade prompts that coding agents can nail the first time.

Describe what you want to build