Reading a Monorepo Like an Archaeologist: Forks, Prefixes, and Org Structure

2026/03/05

BUILDbazeldependenciesdevexmonorepopython

Every monorepo is an archaeological site. The directory listing is a table of contents nobody wrote on purpose. Package names are fossils — they tell you what lived here, who brought it, and roughly when. The dependency graph is the stratigraphy. And if you spend enough time doing migration work — tracing transitive blockers, figuring out why package X can’t move until package Y does — you start reading the repo the way a geologist reads a cliff face.

I’ve been doing Bazel migration for a ~104-package Python monorepo that trains multimodal LLMs. Through that work, I’ve developed a kind of X-ray vision for repo structure. Here’s what I see.

Package Names Are Fossils

Names don’t just describe what code does. They tell you where it came from, who wrote it, and what era of the org it belongs to.

The OpenAI Stratum

Several team members are ex-OpenAI. You can tell because their packages followed them:

These aren’t just imports. They’re hiring receipts. When engineers move between AI labs, they bring their tools. The git history of a fork is a timestamp — it tells you approximately when that person joined.

The Meta Layer

Research Forks

Prefixes Map to Org Structure

This is the fastest shortcut. Scan for naming prefixes and you’ve got a rough org chart.

mai_* (~15 packages) — the Microsoft AI platform layer. mai_trainer, mai_distributed, mai_layers, mai_config, mai_cluster, mai_kernels, mai_multimodal, mai_io, mai_job, mai_evaluator… If it starts with mai_, it’s core infrastructure that multiple squads depend on. This is the shared substrate.

caas* — Conversation-as-a-Service. A whole microservice ecosystem hiding behind a prefix: caas, caas_client, caas_server, caas_utils, caas_user_images. Five packages, one team’s boundary. The proliferation of caas_* packages tells you this subsystem has enough complexity to warrant its own client/server split.

rocket* — the RL/RLHF training system. rocket, rocket_lib, rocket_observability. The biggest, most complex package cluster in the repo. Has its own observability stack. Has its own GPU CI workflow. When a subsystem grows its own monitoring, it’s a product within the product.

swe_* — software engineering agent evaluation. swe_world (the environment) and swe_grading (the scoring). Two packages, clear separation of concerns. This is a team that knows where the boundary is.

mai_* vs everything else is the real split. The mai_ packages are the platform. Everything else is a consumer. That prefix is doing the work of an architecture diagram.

Internal Lore Names

Some names only make sense if you’ve been around.

lib/bus — C++/Cython native-code foundation. The name tells you nothing. But in the dependency graph, it’s the single biggest blocker for Bazel migration. ~28 downstream packages can’t migrate because they transitively depend on bus, and bus has C++/Cython code that Bazel can’t currently build in our setup. I call it a “poison pill” — one unmigrable package that contaminates everything downstream.

lib/fortknox — Azure storage auth handling. Multi-tenant credential management across Corp, PME/Falcon, AME environments. “Where the gold is kept” = where the secrets are. The name is actually helpful here — it tells you this is the thing that handles the scary auth stuff so nothing else has to.

lib/bitbank — binary packed file format for random-access record storage. “Bank of bits.” Cute.

lib/sydney — a historically loaded name in Microsoft AI. Sydney was Bing Chat’s internal codename, back when that was a thing people had Opinions about. Seeing it in lib/ is like finding a Roman coin in a medieval wall. It’s from a previous era, still structurally load-bearing.

The Dependency Graph Is the Real Architecture Doc

Architecture diagrams lie. Or rather, they show intent. The dependency graph shows reality.

Here’s a simplified view of how the major subsystems connect:

                    ┌─ torchlab (researcher CLI)
                    │
Researcher writes   │   ┌─ mai_trainer (training loop)
training script ────┤   ├─ mai_distributed (multi-node)
                    │   ├─ mai_layers (model layers)
                    └───├─ mai_kernels (CUDA kernels)       ← C++/CUDA
                        ├─ mai_multimodal (vision+text)
                        ├─ mai_config (config management)   ← uses chz!
                        └─ simplertransformer               ← C++/CUDA
                              │
                              └─ sgl_server (serving)

RL training:  rocket ─── rocket_lib ─── mai_trainer + mai_distributed

Data:         conversation ─── mai_dataplatform ─── mai_io ─── blobfile/fortknox

Eval:         judges ─── swe_world ─── swe_grading

Infra:        caas* ─── mai_cluster ─── mai_job ─── launcher*

A few things jump out:

mai_config is the quiet chokepoint. It depends on chz (the OpenAI fork with heavy metaprogramming), and nearly everything depends on mai_config. A configuration utility becomes a single point of coupling. This is how “just a config library” ends up blocking 50 packages from migrating.

The CUDA/C++ packages form a hard boundary. mai_kernels, simplertransformer, lib/bus — these are the packages that can’t be built with standard Python tooling. They define the frontier of what’s migrable and what isn’t. In Bazel migration terms, they’re the cliff edge.

The training→serving path is a straight line. mai_trainersimplertransformersgl_server. Training and serving share model implementations. This is an architectural choice — some orgs have completely separate serving stacks. This one doesn’t.

Deprecated packages are ghost limbs. common_utils, research_utils, launcher, tokenization — all deprecated, all still have dependents. They show up in the dependency graph as nodes that nothing new should point to, but plenty of old things still do. Technical debt has a literal shape in the graph.

What Dependency Analysis Actually Teaches You

Doing Bazel migration means running wave analysis on the dependency graph. You mark packages as migrable, then check what they unblock, then check what that unblocks, and so on. It’s like pulling threads.

Here’s what that teaches you:

Poison pills propagate

lib/bus has C++/Cython code. It can’t be Bazel-built (yet). It feeds into lib/oai_api, which feeds into common_utils (deprecated but still depended on), which feeds into… about 28 packages. One unmigrable package holds back a quarter of the repo. The dependency graph doesn’t care about your migration timeline.

The graph IS the org chart

Clusters of tightly-connected packages map to teams. The sparse edges between clusters are the team APIs. You can literally see squad boundaries in the dependency matrix — the caas_* cluster, the rocket* cluster, the mai_* platform layer, the swe_* eval pair. Conway’s Law isn’t a metaphor when you have a graph visualizer.

Forks are a hiring timeline

OpenAI forks appear in the repo around the time ex-OpenAI engineers joined. Meta forks correlate with research collaborations. If you could see the commit dates of when each fork was vendored in, you’d have an approximate hiring and partnership timeline for the org. The code carries the history of the people who wrote it.

Wave analysis reveals the real bottlenecks

When we added system torch support in Docker (so torch-dependent packages could be Bazel-tested), wave analysis showed:

That’s 30 packages from solving one blocker. But 35 packages remain stuck behind lib/bus and other C++/CUDA dependencies. The wave analysis tells you exactly where to invest your next effort — and where not to bother until the root blocker is resolved.

How to Read a Monorepo You Just Joined

Practical advice. You just cloned a 100-package monorepo. You have no idea what anything is. Here’s the fastest way to build a mental model:

Read the top-level ls like a table of contents. Not the code — just the directory names. Sort them mentally by prefix. You’ll see clusters immediately.

Look for naming conventions. Prefixes = teams or layers. If you see mai_trainer, mai_config, mai_distributed, you know mai_ is a thing. Count how many packages share each prefix. More packages = bigger team or more foundational subsystem.

Check lib/ for forks. Most vendored forks have READMEs that say where they came from. Each fork tells you something about the team’s technical lineage and what they chose not to build from scratch.

Run the dependency analyzer. If the repo has one (pipdeptree, a custom script, Bazel’s query command, whatever), run it. The graph tells you more about the real architecture than any wiki page. Look for:

Find the poison pills. Every monorepo has them — packages that block migration, refactoring, or upgrades because they sit at the root of the dependency tree and can’t be easily changed. Usually they’re native code, or metaprogramming-heavy, or deprecated-but-load-bearing. Knowing where they are saves you from proposing changes that sound good but are actually impossible.

The Monorepo as Narrative

A monorepo isn’t just a code organization strategy. It’s an accidental autobiography of the team that built it. The package names record who was hired and what they brought. The dependency graph records what decisions were made about coupling and boundaries. The deprecated packages record what the team tried to move away from and couldn’t, yet.

You don’t need access to anyone’s org chart or design docs. ls and pip install -e . && pipdeptree will tell you more than most onboarding documents.

Next time you join a team with a monorepo, don’t start by reading the README. Start by reading the directory listing. Then draw the graph. The codebase will introduce itself.