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:
lib/chz— forked from openai/chz. Pronounced “चीज़” (Hindi for “thing”). It’s a CLI config and dataclass library with heavy metaprogramming —exec-based__init__synthesis, lazy thunks, runtime factory dispatch. The kind of library that makes Gazelle give up and walk away. The name is pure OpenAI culture: playful, non-descriptive, inside-joke-as-identifier.lib/blobfile— fork of openai/file-handling. Cloud-agnostic blob I/O.lib/boostedblob— fork of hauntsaninja/boostedblob. Async companion to blobfile. Same author, same lineage.lib/oai_api— OpenAI-compatible API client. Theoaiprefix is the tell.
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
lib/codeshield— fork of Meta’s PurpleLlama/CodeShield. Regex-based code vulnerability scanning. Meta’s safety research, absorbed into a Microsoft repo. That’s the AI industry in one sentence.
Research Forks
lib/tau2— clone of sierra-research/tau2-bench. Agent evaluation benchmark. When you see a research benchmark vendored into a monorepo, someone is actively evaluating against it. It’s not aspirational — it’s operational.sgl_server— wraps SGLang for inference serving. The name tells you the inference stack choice before you read a single line of config.simplertransformer— “simpler than HuggingFace Transformers.” Custom transformer implementation with CUDA kernels. The name is a statement of intent: we don’t want the HF dependency tree.
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_trainer → simplertransformer → sgl_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:
- Wave 0: 20 packages directly unblocked (mai_config, mai_layers, mai_trainer, etc.)
- Wave 1: 2 more (conversation, mai_cluster) — needed Wave 0 first
- Wave 2: 7 more — needed Wave 1
- Wave 3: 1 more — needed Wave 2
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:
- High fan-out nodes — packages that everything depends on (your config libraries, your utility packages)
- Isolated clusters — teams with clean boundaries
- Long dependency chains — where a change will ripple farthest
- Deprecated-but-depended-on nodes — the tech debt hotspots
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.