Your agents keep stopping. Greenlight keeps them working — so you get what you're paying for.

Code

A Theory of the Monolith

A Coasean account of software boundaries

July 12, 2026

Why do we build monolithic applications? And when do we decide they’ve become too large and start breaking them up?

Ronald Coase asked a similar question about firms. If markets are so efficient, why do firms exist at all? His answer was transaction costs. Coordinating work through the market incurs costs—discovering suppliers, negotiating contracts, enforcing agreements. Firms exist because, up to a point, performing work internally is cheaper.

Software exhibits a similar phenomenon. Rather than building every feature as an independent service, we begin with a monolith. Only later, and often reluctantly, do we break it up.

We might say:

A firm will continue to add to its monolith until the marginal costs exceed those of an alternative.

Unfortunately, this explains very little. Behold:

A firm will continue to add to its sales team until the marginal costs exceed those of an alternative.

That’s another way of saying that people make rational decisions. Man is the rational animal. It’s tautological.

Beyond Conway’s Law

Melvin Conway observed that software architectures often resemble the communication structures of the organizations that produce them. That observation may explain why certain architectures arise, but it does not explain why those architectures are economically attractive in the first place.

So rather than asking how organizations shape software, let’s ask a different question:

What is “mono” in the monolith?

The deployable

It’s temping to say “That’s easy! It’s the application functionality, stupid”. But feature flags, configuration and blue-green deployments show otherwise. A single monolith can present radically different behaviour in different environments.

But notice at the time of deployment, the code and other artifacts of the monolith are synchronized. Let’s call this a “deployable”.

A deployable then, is the application unit that is versioned, deployed and operated together. It consists of one or more processes together with the persistent state on which those processes depend. For example, an instance of a rails app and its postgres database is a deployable.

The defining property of a deployable is that it synchronizes certain decisions. When you add code to a monolith, you implicitly agree that these dimensions will evolve together.

The dimensions include:

We might say then that the ideal service boundary is one whose components genuinely want to evolve independently. A deployable reduces explicit coordination by requiring these dimensions to remain synchronized.

Why monoliths work

A deployable internalizes coordination by synchronizing decisions. Just think: a function call requires no API versioning. Shared state avoids distributed consistency issues. A single deployment requires no release choreography.

The monolith wins precisely because many decisions only have to be made once. Like firms in Coase’s theory, monoliths internalize coordination.

Why they eventually fail

The assumption behind a deployable is that its constituent parts benefit from evolving together. Early in a project’s life, that’s usually true. New features tend to touch the same code, share the same infrastructure, and have similar operational requirements.

Over time however, those concerns diverge. One subsystem wants to be deployed daily, another monthly. One demands high availability, another is an internal reporting tool. One would benefit from a new language, while the rest would not. The monolith insists these concerns remain synchronized anyway. This is forced synchronization.

Eventually, the cost of synchronizing unrelated concerns exceeds the cost of coordinating them explicitly. At that point, introducing another deployable becomes economically attractive.

Deployables don’t have to remove coupling

Moving code into another service does not automatically break the synchronization, it only makes it optional. This explains why many microservice migrations disappoint.

Consider a service that:

Has anything meaningful changed? The code now communicates over HTTP instead of function calls, but the synchronization requirements remain. The monolith has simply been distributed.

A useful way to evaluate a proposed service boundary is to ask:

Which synchronization constraints does this boundary eliminate? If the answer is “none” then it’s not a microservice, it’s merely LARPing as one.

Hysteresis

When microservices were trendy, many organizations adopted them prematurely. The forced synchronization costs were not yet high enough to justify paying the coordination costs and they regretted it.

These days well-run organizations will do the opposite, and tend to overshoot. I call this lag to adopt microservices “architectural hysteresis”.

If a monolith becomes more expensive today than an ideal service-oriented architecture, organizations will not migrate immediately.

The migration is expensive and risky: systems must be redesigned, operational tooling must be built, teams must learn new practices and new failure modes appear.

Architectures therefore exhibit hysteresis. Organizations tolerate increasingly expensive monoliths because architectural change is a substantial investment. Only once the benefits of microservices are so obvious to everyone that they cannot be denied, does decomposition begin.

AI changes the calculus

The most expensive part of microservices is complexity. Microservices come with a barrage of new tools, configuration and behaviors that are hard for humans to understand. Much of this work is coordination work rather than creative work.

Take a typical AWS deployment as an example. Permissions live in IAM, are defined in Terraform, and are exercised by applications running in Kubernetes. Each system has its own concepts, configuration language and debugging tools. By the time you discover a permission is wrong, you’re often three layers removed from where the mistake was made.

AI handles much of this complexity. It knows the typical IAM patterns and what to avoid. It speaks native terraform, and can deploy it for you, and fix common issues when they come up. It remembers all the different Kubernetes commands to debug errors, and can tell the difference between a missing IAM role and a misconfigured egress.

Whilst AI will (of course) help reduce the complexity of managing a monolith too, what’s notable about the synchronization constraints of the deployable, is that they are not affected by AI.

Just as Coase explained the boundary of the firm as the point where internal organization becomes more expensive than market transactions, perhaps the boundary of the monolith is the point where forced synchronization becomes more expensive than explicit coordination.

The architecture of the future may therefore not be determined by new programming languages, services or hardware, but by this question:

Where should change be synchronized, and where should it be coordinated?

Tags: software-architecture artifical-intelligence economics