Compilation bottleneck eliminated via crate/module architecture

Compilation bottleneck elimination through crate/module architecture means restructuring a codebase so that the unit of compilation is small, focused, and independently compilable.

L5 · SELF-IMPROVINGWhat this level takes
MUSTNot met, not at this level
  • Build is a commodity: near-instant feedback for agents regardless of codebase size
  • Codebase is structured into self-contained modules/crates to eliminate compilation bottleneck (Cursor lesson)
  • Disk I/O is optimized for concurrent agent workloads (parallel reads/writes across modules)
SHOULDExpected in practice, not required
  • Build latency is under 30 seconds for 90%+ of changes
  • Module dependency graph is automatically maintained and optimized
EVIDENCEHow you would check
  • Build duration dashboard showing near-instant feedback for standard changes
  • Codebase architecture showing modular structure (crate/module boundaries)
  • Disk I/O benchmarks for concurrent agent build workloads
DEPENDS ON
  • Infrastructure L4 (Build System) - sub-2-minute builds and agent-specific profiles must be operational

What It Is

Compilation bottleneck elimination through crate/module architecture means restructuring a codebase so that the unit of compilation is small, focused, and independently compilable. In Rust, this means splitting a large crate into many small, well-defined crates. In Java or Kotlin, it means splitting large modules into fine-grained Gradle subprojects or Bazel targets. In TypeScript, it means separating packages in a monorepo so that tsc compiles each package independently. The structural goal in every language is the same: no single compilation unit is so large that it becomes a serialization bottleneck, and independent units can compile in parallel.

Cursor's engineering team discovered this problem empirically when scaling to hundreds of simultaneous agents. Their Rust codebase had large crates that took several minutes to compile. When 10 agents were all modifying files in those large crates simultaneously, they each triggered full recompilation of the crate, and the compilation jobs serialized on shared resources. The solution was structural: split the large crates into smaller, more focused crates with explicit dependency declarations. Smaller crates compile faster individually and, more importantly, can compile in parallel - an agent modifying crate A doesn't block another agent modifying crate B if A and B don't depend on each other.

The compilation unit structure also affects the remote execution model. A Bazel build for a well-structured codebase with 500 fine-grained targets can distribute those 500 compilation jobs across 500 remote workers simultaneously. A poorly structured codebase with 5 large targets can only use 5 workers - the parallelism ceiling is the number of independent build targets. Architecture directly determines how much remote execution parallelism is available.

This is fundamentally an architectural concern, not just a build system configuration concern. BUILD file ownership conventions, crate design philosophy, package structure in package.json workspaces, and module decomposition decisions all need to be made with compilation parallelism in mind. The principle: prefer many small, focused units over few large ones - not just for software engineering reasons, but because small units enable the parallel agent compilation that makes commodity build speeds achievable.

Why It Matters

  • Compilation parallelism ceiling is set by architecture - a codebase with 10 large modules can use at most 10 parallel compilers; a codebase with 500 fine-grained targets can use 500; the difference is 50x potential parallelism
  • Concurrent agents amplify the cost of large compilation units - when 20 agents are each modifying files in the same large module, all 20 trigger the same expensive full-module recompilation; with fine-grained targets, each agent typically touches an independent target
  • Agent isolation improves with smaller compilation units - agents working on separate small crates/modules truly don't affect each other's build times; agents working in different parts of a large monolithic module still share compilation overhead
  • Incremental compilation effectiveness increases with smaller units - smaller compilation units have fewer files, so a change to one file invalidates a smaller fraction of the unit's compiled output; fine-grained targets approach "one file change = one file compiled"
  • Code review and ownership become cleaner - small, focused compilation units align naturally with team ownership and code review boundaries; BUILD files make ownership explicit

Getting Started

  1. Profile your compilation bottlenecks - Identify which compilation units (crates, modules, targets) take the longest to compile from scratch and have the highest fan-out (most other targets depend on them). These are your primary optimization targets. In Rust: cargo build --timings. In Bazel: build scan profiles. Focus on the top 5 by compile time.
  2. Set maximum compilation unit size guidelines - Establish a convention: no Rust crate over 20,000 lines of code, no Java module over 10,000 lines, no Bazel target over 15 source files. These thresholds are starting points, not absolute rules - calibrate based on your measured compilation times.
  3. Split the largest compilation units first - Choose the largest, slowest-compiling unit and split it along natural domain boundaries. Identify which code within the unit is stable (rarely changes) vs. volatile (changes frequently). Move stable code to a foundation crate/module; keep volatile code in a thinner layer on top. This split immediately speeds up incremental builds for changes to the volatile layer.
  4. Declare explicit dependency interfaces - When splitting a large unit into smaller ones, make the inter-unit dependencies explicit: trait bounds in Rust, interfaces in Java, index.ts re-exports in TypeScript. Explicit interfaces enforce the modular structure and prevent re-entanglement of the split units.
  5. Validate parallelism improvement - After splitting a large unit, measure the time to compile both resulting units sequentially vs. in parallel. If the parallel time is significantly lower than the sequential time and the individual unit times are each faster than the original, the split is working correctly.
  6. Establish BUILD file review as an architectural gate - Require senior engineer review for any BUILD file change that creates a target with more than 15 source files or that adds a dependency from a widely-used library to a new dependee. These are the changes that can inadvertently create compilation bottlenecks.
TIP

In Rust, use cargo tree --depth 1 to see the direct dependencies of each crate in your workspace. Crates that appear as dependencies of many other crates are your "wide-impact" crates - changes to them trigger widespread recompilation. These are the highest-priority crates to make stable and slow-to-change.

Common Pitfalls

Splitting without defining stable interfaces. Splitting a large crate into 5 smaller crates with circular dependencies or too-tight interfaces doesn't deliver the expected parallel compilation benefit. The split must expose clean interfaces. If you find yourself splitting and then immediately pulling split units back together to share implementation, the split boundary was wrong.

Over-splitting to the point of excessive dependency management overhead. 500 crates with 5 files each is not better than 50 crates with 50 files each - the overhead of managing 500 dependency declarations, 500 BUILD files, and 500 versioned APIs creates maintenance work that exceeds the build time savings. Find the granularity sweet spot: small enough for fast incremental compilation, large enough to represent a coherent unit of functionality.

Not aligning compilation units with team boundaries. A compilation unit that multiple teams co-own becomes a coordination bottleneck - two teams simultaneously changing different parts of the unit merge their build costs. Align compilation unit ownership with team ownership. If team A owns crate A and team B owns crate B, they can work in parallel with no build interference.

Ignoring the cost of interface evolution. Fine-grained crates with explicit interfaces create more interface evolution work. Adding a method to an interface in a widely-used crate requires updating all dependents. Teams that split aggressively but then frequently evolve shared interfaces may find that the merge and API evolution cost exceeds the build time savings. Stabilize interfaces before splitting aggressively.

Treating this as a one-time refactoring. Compilation unit structure degrades over time as code is added, shortcuts are taken, and BUILD files get large targets appended rather than split. Schedule quarterly BUILD file audits to identify target granularity violations. Make "target too large" a CI lint rule that fails the build when any target exceeds the maximum size guideline.

How Different Roles See It

BobHEAD OF ENGINEERING

Bob's infrastructure team has implemented Bazel and remote execution. Build times are good for most changes but consistently bad for changes involving the company's core data model library - a large, central module that many services depend on. Any change to the data model triggers rebuilds that take 4-5 minutes even with remote execution. Data model changes are common since agents frequently need to extend or adjust data structures.

What Bob should do: Bob should commission a data model library refactoring project with a clear objective: reduce the average rebuild time triggered by data model changes from 4-5 minutes to under 30 seconds. The approach is to split the monolithic data model library into stable core types, domain-specific extension layers, and volatile fields/enums. Most agent changes only touch the volatile layer; splitting means those changes no longer trigger rebuilds of the stable core. Bob should budget 2-3 engineer-weeks for this refactoring and measure the before/after impact on agent build times in the data model area. The ROI calculation is straightforward: if agents make 20 data model changes per day at 4 minutes per rebuild, that's 80 minutes of wait time daily. A 30-second rebuild reduces this to 10 minutes - a 70-minute daily savings.

SarahPRODUCTIVITY LEAD

Sarah has identified "data model area" as a hotspot in her build time analysis. Agents assigned to features involving data model changes consistently show longer iteration cycles and lower completion rates than agents working on pure service logic. The pattern is clear: slow builds in the data model area are causing agents to make fewer iterations and therefore produce lower-quality results.

What Sarah should do: Sarah should use the data from her analysis to make the business case for data model library restructuring as a DevEx investment. She should calculate the correlation between build time and agent task completion rate - if agents in slow-build areas complete tasks at 60% of the rate of agents in fast-build areas, the build time difference has a direct, measurable impact on output quality and quantity. Sarah should present this to Bob with a clear framing: the data model restructuring is not just an infrastructure project, it's a developer and agent productivity project with a quantified return.

VictorSTAFF ENGINEER - AI CHAMPION

Victor has done extensive Rust crate splitting work and has reduced the largest crate in the codebase from 45,000 lines to a workspace of 12 crates averaging 3,500 lines each. Incremental builds for changes in any single crate now take under 8 seconds. He's noticed that agents can now work in different crates simultaneously without any build interference.

What Victor should do: Victor should document the crate splitting methodology as a reusable guide. The guide should cover: how to identify split boundaries, how to define crate interfaces using traits, how to handle the initial split (create the new crate, move code, update Cargo.toml references, verify CI still passes), and how to validate that build times actually improved. Victor should also propose a "crate health score" metric: lines of code, compile time, fan-out (number of crates that depend on this one), and rate of change. Crates with high scores on all four dimensions are the next splitting candidates. This systematic metric prevents future architectural debt accumulation.

Where does your team actually sit on this?

This guide describes one level of one area. Run the assessment to place your team across all 16 areas, see which gates you have passed, and get a report you can take to your stakeholders.

Start the assessment