Beyond 2x AI productivity boost: Architecture is the missing piece
Learn how explicit architecture, module boundaries, and automated enforcement help teams move far beyond 2x productivity with AI coding agents.
Most teams can get a meaningful productivity boost from AI coding tools without changing their architecture. Agents write boilerplate, explain unfamiliar code, and accelerate local implementations. That is the 2x version: coding tasks get faster. The 5x version is not just faster code generation; it is about safely delegating much larger units of work.
I use 2x and 5x to describe the difference in leverage. They are not precise benchmarks, although they are directionally consistent with informal industry reports.
That requires moving up an abstraction layer. Instead of spending most of the day manipulating code, a developer describes goals, constraints, architecture, and tradeoffs while the agent handles more implementation. This sounds like a tooling change. It is actually an architecture problem.
A developer can compensate for a confusing codebase by learning its hidden conventions. An agent starts with none of that institutional memory. If the architecture is implicit, it must reconstruct it—or guess. Agents are great at repeating the patterns they find: in a clean codebase that is desirable; in a legacy codebase it produces legacy code faster.
This architecture is not theoretical. I introduced it at a recent job and watched it remain stable in production for a year. We did not need to refactor the high-level shape; core modules stayed stable and reusable and domain code remained largely free of technical plumbing. And product work mostly meant adding or evolving domain modules.
Architecture is a system of complementary patterns
What is architecture? My working definition is pretty simple. Architecture is a system of complementary patterns.
Both system and complementary are important. A codebase can contain dependency injection, repositories, services, events, tests, and clean interfaces without those patterns composing into coherent architecture.
Good architecture answers the same questions consistently:
Where does this behavior belong?
Who owns this data?
How may another part of the system use it?
What context is required to change it?
How do we know the change is safe?
Which rules require judgment, and which can be enforced?
The answers create architectural seams: the places where parts of the system meet—and where we can enforce rules. A module interface is a seam. So is an app boundary, API contract, database ownership rule, or the line between production code and system tests.
Those seams also become the language for discussing change. “Create a returns domain module and add a get_returnable_items operation to the orders module’s public interface” tells an engineer or agent where behavior belongs and which boundary may change. That is far more useful than “add return functionality.”
AI is excellent at repeating patterns, which makes consistency unusually valuable. Humans must own and approve the seams, although agents can increasingly propose them. Without explicit human ownership, an agent may make local architectural choices without the global purview needed for system-level design.
The goals
Before getting into the repository shape, here are the four goals of this architecture.
Human-directed and agent-legible. Humans own goals, constraints, boundaries, and judgment. The repository makes those decisions explicit enough for an agent to act without reconstructing hidden conventions.
Locally understandable and changeable. Most changes should require understanding one bounded area and its explicit dependencies, not the whole system.
Clear ownership, automatically enforced. Every behavior, contract, and piece of state has one owner. And CI rejects violations of critical architecture rules.
Safe to change and govern. Stable seams provide consistent hooks for cross-cutting policy and allow parts of the system to evolve independently where their boundaries permit.
These are not AI-only goals. They also reduce human onboarding time, review scope, accidental coupling, and knowledge trapped in senior engineers’ heads.
Historically, maintaining this level of consistency required substantial communication and oversight, so it was most common in large engineering organizations. AI agents and executable harnesses now make the same discipline accessible to much smaller teams.
Architecture, disassembled
Show me the map
repo/
├── apps/
│ ├── app1/
│ │ ├── app/
│ │ │ ├── core/
│ │ │ │ ├── module1/
│ │ │ │ └── module2/
│ │ │ └── domain/
│ │ │ ├── module1/
│ │ │ └── module2/
│ │ ├── bin/
│ │ ├── docs/
│ │ ├── harness/
│ │ ├── db_migrations/
│ │ └── test_support/
│ └── app2/
├── shared/
│ └── <language>/
│ ├── shared/core/
│ ├── bin/
│ ├── docs/
│ └── harness/
├── e2e/
│ ├── tests/
│ ├── test_services/
│ ├── bin/
│ └── docs/
├── bin/
├── docs/
├── harness/
├── AGENTS.md
└── README.md
The directory tree is not the architecture itself, but it is a useful projection of it. At a glance, a human or agent can infer whether code is system-wide, app-owned, reusable, business logic, technical infrastructure, or test-only. The path communicates likely ownership, dependencies, documentation, tests, and governing policy.
One pattern repeats across the major scopes: bin, docs, and harness.
bin is the stable command surface for humans, agents, and CI.
docs explains the architecture and workflows owned by that scope.
harness implements the checks, generators, and dependency analysis.
The trio appears at repository, shared-language, and app scopes. Modules are leaf scopes: each contains its implementation, tests, and docs, but does not duplicate the command and harness platforms.
Everything has a place, and the place communicates meaning.
Apps, services, and modules are different boundaries
These terms are often blurred, leading to architecture driven by deployment topology rather than software cohesion. Code that belongs together may be separated simply because it runs in different processes.
A service is an independently deployable runtime boundary. It may run on infrastructure you operate—such as an API, worker, or scheduler—or in a client environment, such as a separately deployed SPA.
An app is a durable code, build, and data-ownership boundary. It has its own build target and CI scope and, when it stores data, usually its own database and migration lifecycle.
A module is a bounded package of production code for one capability. It belongs to an app or a shared-language scope and exposes a public module interface. In practice, it owns one job—billing, reviews, authentication, etc.
One app may produce an API, worker, scheduler, and event consumer services. They can scale and deploy differently while reusing the same modules and data ownership.
Splitting out a new app is a big commitment. If the boundary is not obvious, keep the capability as a module. You can still run it as a separate service when it needs independent scaling or failure isolation. A well-structured module is easy to extract later; prematurely split apps are painful to stitch back together.
The same distinction applies on the frontend. A feature area within an SPA is usually a module. An independently built and deployed SPA is a service boundary. Create a separate frontend app only when it represents a durable product, code, and ownership boundary—not merely a new persona, screen, or route. Micro-frontends are an operational choice, not the default module boundary.
One repository
As you may have guessed already, this is a monorepo of independently bounded apps, reusable same-language core modules, and system-level support.
This is not limited to server code. Web, mobile, ML, and other production applications belong in the same monorepo when they meet the app boundary, even when they use different languages and toolchains.
The monorepo is the envelope, not the core idea. It gives an agent one system map, allows atomic cross-app contract changes, and gives shared enforcement one home.
The monorepo provides visibility. The app boundaries provide independence.
Modern build and CI/CD tools provide first-class monorepo support, making builds and deployments much easier to manage than they were a decade ago.
Shared is not a miscellaneous drawer
Code reused by several apps is grouped by language under shared.
Shared is not a home for generic
utilsorhelpers; catch-all modules obscure ownership and become dumping grounds.
Shared contains reusable core capabilities, not app-specific workflows or domain modules. Similar business concepts may remain separate when their rules and ownership differ.
Each app consumes only the shared modules it needs through explicit local dependencies. Shared modules are linked directly during each app’s build; the team does not publish them as internal packages.
The language-specific mechanism can vary. The invariants do not:
The dependency is explicit.
The module keeps a stable namespace and owner.
The same boundary checks apply.
Module dependencies form an acyclic graph, with the repo harness rejecting any circular dependency.
Modules are the primary unit of understanding
Inside an app, modules are the primary architectural boundary. A module owns one cohesive capability, its public interface, implementation, tests, docs, and any tables it declares.
A domain module owns a business capability: its rules, workflows, and state. Its boundary also gives the module’s business language one consistent meaning—the domain-driven design (DDD) idea of a bounded context.
A core module owns a technical capability such as authentication, observability, database mechanics, messaging, auditing, etc.
Core does not have to mean domain-blind. Some core modules are domain-aware. Observability may describe tickets, accounts, runs, and actors. Auditing may record that a user changed an account.
A domain-aware core module may accept small typed domain values or references, as long as doing so does not make core import or depend on the domain module. But it does not own a business workflow or make a business decision.
Domain-aware core uses business vocabulary to perform technical work. Domain owns business behavior.
The appendix includes concrete examples of core and domain modules.
Modules meet the system through explicit seams
A seam is any boundary where a module or app exposes something others depend on, or interacts with something it does not own. Within an app, common seams include public code interfaces, endpoints, table definitions, and adapters. At app boundaries, APIs, events, queues, and other owned contracts are the seams. Apps do not import one another’s implementation or access one another’s databases.
Each seam has one owner and an explicit contract. Other modules use public interfaces rather than internal implementation; clients use module-owned endpoints; integrations pass through owned messages and adapters. Because seams have standard locations, the repo harness can enforce ownership, detect contract changes, attach policy, and identify affected consumers.
Every table has exactly one owning module. Other modules use that module’s public interface rather than querying its tables directly.
Modules own their schemas and table access; the app coordinates them through one physical database and migration stream. This produces a modular monolith: in-process collaboration and one database where useful, without shared global state.
Cross-module composition still needs an owner
Explicit ownership creates an immediate question: if business logic lives in domain modules, where does behavior spanning several modules belong? Composition is not ownerless—the module accountable for the outcome coordinates the work through public interfaces.
A use case spanning several modules therefore belongs to the module that owns its outcome. For example, returns can retrieve order items through the orders interface, apply return-eligibility rules, and expose the assembled result.
Screen-specific combinations can be assembled by the frontend or by a thin client-facing query module when they need a dedicated server-side contract. Clients may assemble and present data, but authoritative business rules remain with the modules that own them. If a query becomes expensive or historical, its owner may maintain an event-fed read model.
Composition must use public interfaces—never another module’s tables or a generic aggregation module that collects unrelated use cases.
Architecture inside a module
Do modules need their own clean, hexagonal, or layered architecture? Sometimes, but not automatically.
Most modules should remain simple.
In the online store, catalog might remain a straightforward set of product operations over persistence. orders, with a multi-step lifecycle such as placement, payment, fulfillment, and cancellation, may benefit from separating state transitions and business rules from persistence and external integrations.
For a complex module, separating business logic from databases and external dependencies is usually the highest-value internal seam. If it still needs many layers, first ask whether it contains several capabilities and should become several modules. Add deeper architecture only when the remaining complexity is cohesive.
Architecture should go down to the level where local patterns are clear. Below that, the agent can figure out the implementation.
Documentation and testing follow ownership
Docs are colocated with the scope they explain. Root docs own system behavior, app docs own cross-module flows, and module docs own the module’s boundary and invariants. Parent docs link downward instead of repeating facts. One fact lives in one place.
Context discovery follows the same structure. For a changed path, it returns the smallest complete doc set. Root context is included for system work, not every local change; irrelevant context can be as harmful as missing context.
Testing follows ownership too:
Module tests verify behavior inside one module.
App-level test support verifies flows crossing modules through public interfaces.
Top-level E2E tests verify the assembled system through the browser, HTTP, and events.
Purpose-built fake external services—such as a fake payment gateway or email provider—live with the E2E topology. This is why the
e2edirectory is top-level rather than another app: it owns verification of the assembled system.
The harness makes the map true
The repo harness turns the architectural map from guidance into an enforced system.
In my earlier framework for AI dev tooling, I separated the repo harness from the coding agent harness. A coding agent harness—such as Claude Code—is the loop around the model: planning, tool use, memory, and execution. The repo harness is specific to the repository and makes any coding agent effective inside it.
The repo harness has two sides.
Deterministic. Scripts and hard checks enforce dependency direction, cycles, public-interface use, table ownership, tests, generated contracts, and other invariants. Violations fail before merge.
Non-deterministic. Docs and AI skills are explicit, deterministic artifacts, but how an agent interprets and applies them is not. They help it load the right context, follow the intended workflow, and apply human-defined judgment where hard checks cannot.
The repo harness should not become a second, drifting architecture description. It treats the tree and build files as sources of truth:
Convention establishes structure.
The harness derives structural facts—including module inventories, dependencies, context, contracts, and affected consumers—from the tree and build files.
Configuration is used only for choices that cannot be inferred, such as review requirements or risk classifications.
The same dependency graph also supports CI/CD: changes can build and test the affected apps and consumers, while contract and migration changes receive additional verification. Deployment ownership, observability, rollback, and failure isolation attach to the same app and service boundaries.
Seams are policy hooks
Stable seams support more than dependency checks.
A module interface is a natural attachment point for tracing, audit logging, authorization, validation, and other cross-cutting policy. If all cross-module operations use instrumented entry points, every incident can at least be narrowed to a module.
The same paths support human ownership, reviews, alerts, dashboards, runbooks, reliability targets, and security responsibility.
They also support AI governance. A good software factory exposes hooks where teams decide how much autonomy an agent receives, and periodically checks whether the architecture still matches its intended shape. Recurring drift checks catch cumulative problems that no single change exposes—dependency creep, blurred ownership, stale docs, or missing enforcement. Their findings become refactoring work and, where possible, new deterministic checks. CODEOWNERS-style policy can govern authority, not just who does the code review.
For example:
An internal module change with no interface change may proceed autonomously.
A module interface change or new dependency may require human review.
Contracts and migrations trigger extra verification.
Security or harness changes remain human-gated.
This is better than reviewing every AI change manually or allowing unrestricted autonomy. The goal is high autonomy inside low-risk scopes, with escalation when a sensitive boundary or contract changes.
Why this compounds
Taken together, these patterns change the size and number of tasks that can be delegated safely.
Cohesive modules reduce context. The agent reasons about one capability and its explicit dependencies.
Standard shapes make patterns reliable. Nearby examples teach the right lesson.
Explicit ownership bounds the blast radius. Interfaces and table ownership make impact clear.
Executable rules enable autonomy. Humans need not supervise every import, table access, contract update, or documentation obligation.
Context discovery reduces setup. The right docs and skills arrive with the task.
The dependency graph makes verification complete. Shared changes trigger every affected consumer without testing unrelated code.
Stable seams make governance cheap. Reviews, permissions, observability, auditing, and authorization use the same model.
These benefits compound: a local task is easier to implement, a standard change is easier to review, a bounded change is safer to automate, and targeted verification makes that automation faster.
Humans get the same map. New engineers navigate faster, reviewers focus on real impact, teams own modules without owning microservices, and incident responders narrow failures to stable boundaries.
This is not architecture optimized for AI at humans’ expense. It is good architecture made explicit enough that AI can participate and that human devs equally benefit from as well.
The architecture has to evolve at AI speed
Some boundaries will be wrong, and modules will sometimes grow beyond what remains locally understandable. Faster implementation makes continuous architectural correction more important because structural problems can spread just as quickly as useful patterns.
When AI lets a team move 5x faster, refactoring also has to move 5x faster so architectural feedback keeps pace. I would expect 10–20% of development time to go toward architecture, the repo harness, and refactoring as a practical rule of thumb. When an agent makes a mistake a check could catch, add the check; when a rule repeatedly needs explanation, improve the docs or skill; when a module becomes confusing, redesign it.
Without equally fast architectural feedback, faster implementation creates a faster-growing legacy system. The promise of AI development is not more lines of code but moving human attention toward product intent, boundaries, tradeoffs, and judgment. Great architecture makes the correct next action locally discoverable and incorrect actions cheap to reject.
As models improve, the delegation ceiling rises
As models improve, the same explicit modules and seams let agents safely own progressively larger units of work. Today that may be a bounded implementation task; over time it can grow to designing a module, coordinating changes across modules, evolving contracts, or planning migrations. The structure does not need to loosen as model capability rises.
Humans can also grant agents more architectural authority over time. Agents may propose boundaries and dependencies while the harness verifies mechanical rules and sensitive changes still require review. Trust expands by scope instead of becoming unrestricted.
This architecture therefore does not depend on the limitations of today’s models. Better models raise the ceiling of work that can be delegated, while explicit ownership, contracts, and verification keep ambiguity and blast radius bounded.
Appendix
The appendix turns the preceding patterns into concrete repository shapes. It includes a complete generic tree and a small online-store example showing how core and domain modules divide responsibility.
Complete repository tree
This generic tree shows the complete repository shape without tying it to a particular product domain.
repo/
├── apps/
│ ├── app1/
│ │ ├── app/
│ │ │ ├── core/
│ │ │ │ ├── module1/
│ │ │ │ │ ├── docs/
│ │ │ │ │ └── test/
│ │ │ │ └── moduleN/
│ │ │ └── domain/
│ │ │ ├── module1/
│ │ │ │ ├── docs/
│ │ │ │ └── test/
│ │ │ └── moduleN/
│ │ ├── bin/
│ │ ├── db_migrations/
│ │ ├── docs/
│ │ ├── harness/
│ │ ├── contracts/
│ │ └── test_support/
│ │ ├── docs/
│ │ └── test/
│ ├── app2/
│ └── app3/
├── shared/
│ ├── language1/
│ │ ├── bin/
│ │ ├── docs/
│ │ ├── harness/
│ │ └── shared/
│ │ └── core/
│ │ ├── module1/
│ │ │ ├── docs/
│ │ │ └── test/
│ │ └── moduleN/
│ └── language2/
├── e2e/
│ ├── bin/
│ ├── docs/
│ ├── tests/
│ └── test_services/
│ ├── service1/
│ │ ├── docs/
│ │ └── test/
│ └── serviceN/
├── bin/
├── docker/
├── docs/
├── harness/
├── AGENTS.md
└── README.md
Example modules
Here is how a small online store might apply the module structure:
apps/store/app/
├── core/
│ ├── auth/
│ ├── observability/
│ ├── database/
│ └── web/
└── domain/
├── catalog/
├── orders/
└── returns/
Core modules
core/authowns authentication and exposes login mechanics once for the entire app.core/observabilityattaches baseline telemetry at module seams so domain code does not recreate tracing and logging.core/databaseowns connections, transactions, and migration mechanics without owning domain tables.core/webowns HTTP server setup, shared middleware, request and response mechanics, and module route registration. Domain modules still own their uniquely prefixed endpoints.
Domain modules
domain/catalogowns products, their descriptive information, tables, public operations, and/catalogendpoints.domain/ordersowns the order lifecycle, order tables, public operations, and/ordersendpoints.domain/returnsowns return eligibility and workflows and publishes endpoints under/returns. It uses the orders module’s public interface rather than querying order tables.


