Skip to content

A Look at Runnable Directories: The Solution to the Monorepo vs Multi-repo Debate

Ask any engineering team that has scaled past a few dozen developers: how do you organize your code? The answer shapes everything from build times to team velocity to system reliability.

The industry settled into two camps. Monorepos offer consistency but struggle with scale. Multi-repos provide independence but create coordination overhead. Both work. Neither solves the underlying trade-offs between modularity and unified workflows.

The Problem Space#

Monorepos consolidate all code in a single repository. Companies like Google and Meta built systems this way, proving it works at massive scale. One repository means one version of every dependency, one commit history, one source of truth. Coordination becomes simpler. But consistency has costs. Build times grow as codebases expand. CI/CD pipelines slow down. Teams need specialized tools like Bazel or Buck just to keep the system responsive. Even basic operations like searching code require custom solutions.

Multi-repos split code across separate repositories. Each service evolves independently. Teams ship on different schedules. Services scale on their own terms. The architecture reflects organizational boundaries. The trade-off shows up in dependencies. One service requires version 2.0 of a library. Another needs version 1.5. Synchronizing environments across repositories becomes an ongoing effort. Propagating changes requires careful coordination.

Both approaches work. Neither is ideal.

What We Built#

At Causify, we developed a hybrid approach centered on "Runnable directories". Each directory is a self-contained unit with its own code, configuration, and DevOps pipeline. It operates like an independent repository but can exist within a larger structure.

Directories can nest inside other directories. They can reference each other as submodules. The flexibility lets us organize code based on what makes sense for each component.

Docker containers isolate dependencies at the directory level. Each unit runs in its own container with exactly the packages it needs. No version conflicts. No environment bloat. We can add a package to one directory without affecting anything else. The setup works across Linux, macOS, and Windows Subsystem for Linux.

A thin shared environment bootstraps everything. One lightweight setup serves all directories. Common utilities live in a shared submodule: linters, Git hooks, CI/CD workflows. When we update a shared tool, all directories get the improvement. No code duplication. No drift.

Tests execute recursively with a single command. The system runs each directory's tests in its proper container. We get unified testing with complete isolation. One command validates the entire codebase.

What Changed for Us#

The architecture scaled naturally. We started with a few directories and added more as systems grew. No migration. No architectural rewrites. The structure adapted to our needs.

We used standard tools. Docker and Docker Compose handle everything. We did not need to adopt specialized build systems or retool our CI/CD pipelines. The teams are already familiar with these tools.

Development workflows became more flexible. Engineers work on individual directories without cross-contamination. When changes span multiple components, everything remains accessible. The system supports both independent and coordinated development.

Reference#

This post discusses concepts from the paper "Runnable Directories: The Solution to the Monorepo vs. Multi-repo Debate" available at arXiv:2512.03815 by our team at Causify: Shayan Ghasemnezhad, Samarth KaPatel, Sofia Nikiforova, Giacinto Paolo Saggese, Paul Smith, Heanh Sok.

The implementation details and experiences described reflect our work at Causify applying these principles in production environments.