Monorepos and Large Codebases
Agent-ready patterns for monorepos, microservices, and codebases with 1000+ files
Overview
Everything we have covered so far -- structure, naming, documentation -- becomes exponentially more important as codebase size increases. A 50-file project with mediocre structure is navigable. A 5,000-file monorepo with mediocre structure is a maze. AI agents working in large codebases face a fundamental constraint: their context window cannot hold the entire codebase, so they must navigate selectively, and every wrong turn wastes tokens that could have been spent writing correct code. This lesson addresses the specific patterns that make monorepos and large codebases agent-ready.
The Scale Problem
When an agent opens a small repository, it can often read the entire file tree and key files within its token budget. But at scale, this strategy breaks down:
Scale comparison: in a small repo (50 files), listing all files costs about 200 tokens and reading 10 key files costs 5,000, achieving full understanding for about 5,200 tokens total. In a large repo (5,000 files), listing alone costs 20,000 tokens, and even after reading 10 key files, the agent has only partial understanding at 25,000 tokens.
In a large repository, the file listing alone can consume a significant portion of the context window. The agent must make strategic decisions about what to explore and what to skip -- and without strong structural signals, those decisions are often wrong.
In small codebases, agents can compensate for poor structure. In large codebases, poor structure is a showstopper.
Monorepo Fundamentals
A monorepo stores multiple packages, services, or applications in a single repository. Common monorepo tools include Turborepo, Nx, Lerna, pnpm workspaces, Go modules, and Bazel. From an agent's perspective, a monorepo is a codebase with multiple entry points and multiple sets of conventions -- which makes clear boundaries essential.
Monorepo structure: the root contains packages/ (shared-ui, auth-sdk, db-client) and apps/ (web, api, worker), plus root config (tsconfig, eslint, CLAUDE.md). Dependency arrows show which packages are imported by which apps.
Pattern 1: Hierarchical CLAUDE.md Files
In a monorepo, a single root CLAUDE.md cannot cover the specific conventions of every package. The solution is a hierarchy of CLAUDE.md files: a root file for cross-cutting conventions and package-level files for specific instructions.
The root CLAUDE.md covers what applies everywhere:
A package-level CLAUDE.md covers what is specific to that package:
When an agent starts working on the API service, it reads the root CLAUDE.md first (learning cross-cutting rules), then the API-specific CLAUDE.md (learning the stack, conventions, and common mistakes). This hierarchical approach gives the agent precisely the context it needs without overwhelming it with irrelevant information about other packages.
Pattern 2: Clear Package Boundaries
In a well-structured monorepo, each package is a self-contained unit with a defined public API. The agent should be able to work within a single package without needing to understand the internals of other packages.
The barrel file draws a clear line between public and private. When an agent in the web app needs authentication, it imports from @repo/auth-sdk and sees only the public API. It does not need to understand the internal token storage mechanism or the refresh loop.
Enforcing Boundaries
Document and enforce import rules so agents do not bypass package boundaries:
When an agent attempts to import from @repo/auth-sdk/src/internal/tokenStore, the linter catches it. This feedback loop teaches the agent the correct import pattern.
Pattern 3: Workspace Configuration
A well-configured workspace helps agents understand the dependency graph and build order without manual exploration.
The --filter scripts give agents a way to build and test a single package and its dependencies. This is crucial for large monorepos where building everything takes too long for the agent's iteration loop.
Pattern 4: Shared Code as Explicit Packages
In large codebases, shared code tends to accumulate in a shared/ or common/ directory that grows without clear ownership. For agents, this becomes a black hole of undifferentiated utility functions.
The agent-ready approach is to treat shared code as explicit packages with defined scopes:
Each shared package has a focused purpose, a barrel file, and -- for complex packages -- its own CLAUDE.md. When an agent needs date utilities, it imports from @repo/shared-utils and finds formatDate in the barrel file. It never has to search through an unstructured utils/ directory.
Navigating at Scale: Search-Friendly Structure
In codebases with thousands of files, agents rely heavily on text search (grep, ripgrep) to find relevant code. Your structure should be optimized for search:
Agent search flow in a large codebase: agent receives a task (fix billing webhook timeout), searches for 'webhook' in file names. If found (e.g., billing/webhook.controller.ts), reads the file directly. If not found, searches file contents. If results are manageable (under 10), reads each. If 100+ results, narrows the search to 'billing.*webhook' before reading.
Structure decisions that improve searchability:
- Include domain terms in file names so
grep -r "webhook"in file names returns the right files immediately - Avoid re-exporting with renamed symbols which breaks the agent's ability to trace a name across files
- Use consistent terminology across the codebase -- if one module calls it "customer" and another calls it "client" and a third calls it "user," agents searching for the right code will miss results
- Keep import paths readable so the agent can trace dependencies by searching for the import path
Dependency Graphs for Agent Orientation
In large monorepos, a dependency graph in the root README or CLAUDE.md helps agents understand which packages depend on which. This prevents the agent from introducing circular dependencies or importing from the wrong layer.
This simple text representation tells the agent the dependency direction and prevents it from, for example, importing shared-ui components into the backend API.
Large Codebase Survival Patterns
Beyond monorepo-specific patterns, these practices help agents work effectively in any codebase with more than a few hundred files:
Consistent Entry Points
Every package, service, or module should have a predictable entry point. In a Node.js project, this is typically index.ts. In Python, it is __init__.py. In Go, it is the package name itself. Agents look for these entry points first -- if they do not exist, the agent must scan every file to determine where to start.
Feature Flags and Configuration
When features are controlled by feature flags, document them centrally. An agent modifying a feature needs to know whether it is behind a flag and how to test both states:
Code Ownership Files
A CODEOWNERS file tells the agent which team owns each part of the codebase. While primarily a GitHub feature, agents can read this file to understand organizational boundaries:
Summary
Large codebases and monorepos amplify every structural decision. An agent that can navigate a 50-file project by brute force must navigate a 5,000-file project strategically -- and strategy requires clear signals. Hierarchical CLAUDE.md files give agents scoped context for each package. Clear package boundaries with barrel files let agents work within one package without understanding the internals of others. Explicit shared packages replace unstructured utility directories. Search-friendly naming ensures text search returns relevant results quickly. Dependency documentation prevents agents from introducing circular imports or crossing architectural boundaries. These patterns do not just help agents -- they are the structural foundations that make any large codebase maintainable. The difference is that agents expose structural weaknesses immediately, while human developers work around them silently until the cost becomes unbearable.