The importance of modularization for AI-assisted software projects

Posted on Apr 5, 2026

I’ve written already how Software Engineering and Architecture is the most important thing to focus on in the AI-assisted software development age (even correctly predicting the LiteLLM Supply Chain attack from a few weeks ago).

I am already seeing how Software Architecture will change in the age of AI assisted software development. For example, one of the biggest problems that we’ve had (and other teams, from what I’ve heard) is reviewing the changes made by AI Agents. The bottleneck is no longer the “development”, but the reviewing part. A simple bug fix can result in hundreds of files changes, just because the AI Agent decided that it had to change something else in between, making it very hard (and slow) to do code reviews.

In this post I explore the single most important thing that I’ve done to improve this: Extreme Modularization.

The advent of a new user

We’ve been perfecting our Software Architecture and Engineering techniques for the past 60 years with a particular user in mind: humans.

We humans have different traits, some advantageous some disadvantageous. For example, we’re very slow at typing, but we’re very fast at thinking. We have “big context windows”, but they’re very fragile and slow to build. If you give me a new codebase for me to understand, I can most likely fit the entire architecture in my brain (RAM, let’s say?) but it’ll take me several hours (or even days). As Paul Graham puts it in his “house of cards” analogy:

Different kinds of work have different time quanta. Someone proofreading a manuscript could probably be interrupted every fifteen minutes with little loss of productivity. But the time quantum for hacking is very long: it might take an hour just to load a problem into your head (…)

This is why hackers give you such a baleful stare as they turn from their screen to answer your question. Inside their heads a giant house of cards is tottering.

The mere possibility of being interrupted deters hackers from starting hard projects. This is why they tend to work late at night, and why it’s next to impossible to write great software in a cubicle (except late at night).

AI assistants have different traits, and all more or less opposite to us humans. They’re very fast at “typing” (making edits) and priming their context windows, but they’re very bad at thinking and understanding the broader scope and architecture of a project.

That’s why AI-assisted codebases will need different approaches to architecture.

Modularization

Modularization is nothing new, of course. But it’s become EXTREMELY important now in the age of AI Agents. Allow me to explain.

Modularization is usually a tradeoff between “compartmentalization and separation of responsibilities” and complexity. The more layers you create and the more you separate, the harder it is to “comprehend” the architecture. The fewer layers you introduce, the more coupling that your architecture will have. More layers: more boilerplate, more directories, more interfaces, etc. Fewer layers: fewer files, fewer tests, but more coupling.

As we humans are slow at typing and fast at thinking, we tend to break it in the least amount of layers that keep somehow the responsibilities separated. Any standard software project will have basic layers like: presentation, data storage, services, etc.

Given that typing is such a big effort for us, whenever we have to introduce a change (new feature, refactoring, fixing a bug, etc), we tend to put a lot of thinking into which layers will be affected and how and then we’ll proceed at writing the changes. The result is that we have optimized for the least amount of changes in the least amount of places. This results in PRs that are easier-ish to review, given that our own limitation will aim for “minimization” of changes.

But for AI Assistants that’s not the case. They’re very fast at introducing changes. A simple bug fix can result in hundreds of files edited, just because the AI Agent found something else in the middle and decided to change it. This results in very hard to review Plans and PRs.

Extreme Modularization is the answer

We started a brand new software project a few weeks ago and I decided to go to the EXTREME with modularization. I’ve broken a single public endpoint into ~9 different layers (more or less in something like this):

View > Serializer > App Service > App Provider > Registry Layer > Core Service > Core Adapters > Core Providers

Borrowing from ancient wisdom, I’m relying heavily on “Design by Contract”: each layer has a very well defined interface (implemented using Pydantic models).

Each layer has its own set of tests, and these tests obviously test ONLY the responsibilities of that layer and mock anything else.

Thinking in dimensions

Even though this might look overkill and excessively complex for a simple project (from a human’s perspective), it’s proven to be very useful for AI Agents. Whenever we need to implement a change, we know precisely which layers will be affected, and we can command the Agent to NOT make any changes outside of those layers.

If you think of your software as a multi-dimensional structure (with dimensions being: layers, libraries, features, modifiers, data storage, etc), the goal is to ensure that a change (new feature, bug fix, etc) will be constrained to modify the least amount of dimensions possible. Any new dimension introduced grows the complexity exponentially.

In my example above, if I want to introduce a new feature, this will result in a tiny addition to each layer:

  • One method per layer
  • One tiny model per layer
  • One test case per layer

If I want to completely refactor a layer (changing the data stored, or the underlying packages I use), that results only in a major change in said layer, but the others should not be affected. The result of the change is always: N x 1 or 1 x N, never N x N.

I like to think about it in this way:

Results of a change with vs without modularization

What does this mean for you

If you’re starting a new project, here’s a short list of action items/recommendations that I can give you:

  • Try to break it in AS MANY small layers as you can think of, each with a very clearly defined interface.
  • Rely heavily on Models. The core of modularizing is interfaces. Interfaces are given by models.
  • Avoid sharing state: you’ll be tempted to create a “GlobalConfig” object that will be shared among layers, this is just more coupling. Instead, make sure that the interfaces clearly define what they need. Even if this means “repetition”.

AGENTS.md

Something that helps once the codebase grows is to have different “agents” and roles for each layer. I started with a single CLAUDE.md where I had all the architecture, but as it grew, I felt that the context windows got too big. So I just split it into different “expert agents” per each layer and then a single “Software Architect” agent that is encouraged to work across all different layers.