Engineering Context

Code Search Index Architecture for Monorepos

Building search for monorepos requires rethinking indexing from the ground up.

Editor at Large · · 12 min read
Cover illustration for “Code Search Index Architecture for Monorepos”
Enterprise Code Search · September 7, 2026 · 12 min read · 2,600 words

A monorepo is a single repository holding many projects, teams, and languages under one trunk, and its search problem differs structurally from multi-repo search. That difference has to shape the index architecture from the start, not get bolted on after the fact. Get this choice wrong and the cost shows up months later: a stale index that agents and engineers alike learn to stop trusting.

Three things change once code search operates over a monorepo instead of a scattered set of repos. Cross-project symbol references stop being the exception and become the default, since a shared internal library gets called from a hundred different services in the same tree. Change rate multiplies, because every team committing to trunk adds to one stream of writes that any index has to keep pace with. And the query surface stops being just engineers: PMs, support staff, new hires, and now AI coding agents all hit the same corpus, often asking very different kinds of questions.

Put those three together and the problem sharpens fast. As the codebase grows and commit rate climbs, a full re-index strategy falls further behind with every passing hour. Google's monorepo, serving tens of thousands of engineers, forced this issue decades ago, and the fix required redefining what "indexing" even means for code that never stops changing. Everything below, trigram versus semantic, incremental versus full, in-memory versus distributed, answers to one tension: freshness against completeness, at a scale where a team rarely gets to keep both.

What trigram indexing does well and where it runs out of headroom

Trigram indexing breaks source code into overlapping three-character sequences and records where each one shows up. Search for a string, and the engine breaks the query into its own trigrams, pulls the files containing all of them, then checks the full pattern against those candidates to throw out false positives. Regex queries get handled the same way: the engine pulls out literal substrings it can turn into trigram keys, so a pattern matching two class names decomposes into the trigrams belonging to each, tools like Sourcebot, an on-prem code search platform for teams and agents, are built on this foundation before adding a semantic layer.

Zoekt is the reference implementation worth knowing. Built originally at Google, the actively maintained fork is a good case study in what makes trigram search work at monorepo scale. Shards are memory-mapped, so posting lists can sit on SSD instead of RAM, keeping memory overhead low relative to how much code is actually indexed. The design separates indexing and querying concerns so that updates can proceed without interrupting ongoing searches. Symbol-aware ranking pushes function definitions and class declarations above plain-text hits. The shard model is designed to remain manageable as the indexed corpus grows and changes over time.

All of that makes Zoekt genuinely good at answering one kind of question: where does this string show up? A more structural question, what calls this function, everywhere, across the whole dependency graph, has no good answer in that model, and that's the point most teams get wrong when they assume a fast grep is the same thing as understanding code. This is outside what a trigram index was ever built to represent, and no amount of tuning changes that. Add the plain mechanical fact that a full re-index of a very large repo takes real wall-clock time, long enough that the index sits stale against a trunk that never stops moving, and the ceiling comes into full view. Trigram search is a strong foundation for fast text and regex lookups at nearly any enterprise scale, but it runs out the moment questions turn structural instead of textual, and pretending otherwise just pushes the failure downstream to whoever has to explain why the agent invented a caller that doesn't exist.

Incremental indexing as the architectural response to high-velocity trunks

The freshness problem is simple to state: if dozens of teams push to trunk all day, a full re-index is always chasing something that already moved. Incremental indexing exists to make update cost scale with the size of the change, not the size of the repository. Cost should look like O(changes), not O(repository), and any architecture that can't make that trade isn't ready for a high-velocity trunk.

Getting there requires three things working together. Change detection has to know, precisely, which files a given commit or push touched. Partial invalidation then updates only the shards or facts tied to those files, leaving everything else untouched. Consistency guarantees make sure a query landing mid-update sees one coherent snapshot of the world, not a mash of old and new facts sitting side by side.

Glean, Meta's open-sourced semantic indexing system, is the clearest public example of this design philosophy: built for monorepos at serious scale, with incremental storage treated as first-class from day one rather than added later. Google's own indexing history tells a similar story. Google's indexing history shows a push toward architectures where updates could be more surgical as the codebase kept growing. The payoff is concrete: an index that only re-ingests what changed can stay current through a commit cadence that would leave a full-rebuild system perpetually hours behind, and hours matter when an agent is proposing a change against code that may have already moved under it.

What incremental indexing doesn't solve is a separate question entirely. Keeping facts fresh is one problem; how rich those facts are is bounded by what the index's schema can express in the first place. That's a different constraint, and it points straight at the semantic layer.

Semantic and fact-graph indexing: what you get when text search is not enough

Some questions sit outside what trigram search can answer, no matter how fresh the index stays. What are all the direct subclasses of this interface, across every service in the tree? Which callers of this internal API break if the signature changes? Where is a symbol actually defined, as distinct from every place it's merely imported? These are graph questions, and no amount of trigram tuning turns one into the other.

Glean's approach is to store typed facts about code, definitions, references, inheritance edges, call edges, and expose a Datalog-inspired query language for composing precise structural questions over that graph. Once code is represented that way, more opens up than search alone. Richer navigation over the stored fact graph becomes possible for teams querying the index directly. Structural queries like impact analysis and cross-service symbol navigation become things a team runs against the index rather than scripts by hand. For an LLM agent the difference is stark. It can pull the exact call chain or inheritance graph for a symbol instead of sifting through a pile of text matches that merely look relevant.

Portable index formats that encode a language server's symbol graph are worth knowing here, since they enable cross-repository symbol navigation without a language server running live. That matters enormously in a monorepo, where a single change routinely spans several package boundaries at once.

None of this comes free, and anyone selling it as a drop-in upgrade to trigram search is skipping the bill. Semantic indexing needs a language-specific indexer for every language present in the monorepo, so a shop running Java, Python, Go, and TypeScript needs four separate pipelines feeding one fact graph. The load is meaningfully higher than standing up a trigram engine, and building the full fact graph can take a long time even with incremental updates softening the blow. That complexity earns its keep for teams navigating large, multi-language monorepos where impact analysis, safe refactoring, and cross-service symbol navigation are daily workflows and not occasional ones. For anyone else, it's overhead chasing a problem they don't have yet.

Distributed vs. in-memory index topology: where you put the data and why it matters

Zoekt's shard model keeps hot data memory-mapped on each search node, fast for single-node setups, and it scales by adding nodes and spreading shards across them. That's the in-memory tier, and for most organizations it's as far as the topology question ever needs to go. Reaching for a distributed cluster before the corpus demands one is the single most common overengineering move in this entire space, and it's worth naming plainly as a mistake rather than a matter of taste.

Distributed architecture becomes necessary under a few specific conditions, not as a general upgrade path. The corpus outgrows what a single machine can address, even with posting lists kept off main memory. Query concurrency from a large user base, engineers and AI agents both hammering the index at once, starts creating real contention on one node. Or the monorepo spans enough languages, each needing its own indexer process, that coordination overhead becomes its own problem to manage.

Meta's combination of BigGrep and Glean is the clearest production example of distributed search at extreme scale: search spread across a corpus measured in billions of lines, with pre-built indices and routing logic that gets retrieval down to milliseconds, the two systems handling different aspects of search over the same underlying codebase. Getting a topology like that right means making real decisions. Which repositories or subtrees live on which nodes, since that affects both fan-out and update locality. Whether search nodes hold redundant shard copies and how updates propagate without taking anything offline. How a coordinator layer fans a query out to the right nodes and merges the ranked results back together.

None of it is free. A distributed system buys headroom and tolerance for concurrent load, but it demands continuous operational attention: shard rebalancing, coordinator failure handling, keeping cross-node consistency intact. Most enterprise monorepos are nowhere near the scale that requires any of this, and the more common bottleneck is index freshness and query expressiveness, not raw throughput. Building for Meta's scale while operating at a tenth of it isn't ambition, it's a significant and avoidable engineering cost.

How AI coding agents turn index architecture into a product-critical dependency

Many AI coding assistants on the market today are primarily oriented around a local workspace or project context. Few of them are built to automatically discover and search across an entire enterprise monorepo, and that gap sounds minor until you trace what it actually costs downstream.

An agent that only sees the files open in someone's editor reasons about a slice of the codebase, not the whole thing. It can't trace a call chain across a package boundary, can't find the canonical implementation of a shared abstraction, and can't assess what breaks downstream from a proposed change. That's a structural limit on what the agent can be trusted to do, and it's the reason "an AI agent reviewed this PR" still needs a human who actually knows the codebase reading it too.

MCP, the Model Context Protocol, is the layer emerging to close that gap. It's an open standard now adopted across major AI platforms. It lets an agent send a structured query to an external code search server and get back a targeted answer instead of a dump of files. The clearest production pattern involves agents querying systems like BigGrep and Glean for symbol graphs and reference chains from the full monorepo at low latency. A second pattern is worth naming too: retrieved documentation that mentions a file path can automatically trigger a follow-up code retrieval, and that only works if the underlying index is both broad enough to cover the whole corpus and structured enough to be queried precisely, not just grepped.

That puts three demands on the index underneath. Freshness matters directly, since an agent acting on a stale index might propose changes to code that's already been refactored out from under it. Structural queryability matters just as much: trigram search answers "find this string," but agents increasingly need "find every caller of this function," which only the semantic layer provides. Latency is a live constraint, not an abstraction, because agent workflows run synchronously from the user's point of view; slow index queries mean a developer sitting and staring at a spinner.

There's a data residency question sitting underneath all of this, and it doesn't belong in a footnote. Routing an agent's code queries through a third-party cloud service means sensitive source, the whole monorepo, not a single file, leaves the enterprise environment every time context gets retrieved. Teams that can't accept that exposure need an index running inside their own infrastructure, full stop, with no exception carved out for convenience. Cursor's architecture illustrates the constraint well: its architecture is oriented around cloud-hosted infrastructure, and that design may not extend naturally to an organization's full repository estate or to teams with strict data residency requirements.

Choosing an index architecture for a real monorepo: the decision framework

Diagram: Trigram vs. Semantic: What Each Layer Can and Cannot Answer. Visualizes: Show a ranked or tiered breakdown of three query types mapped to which index layer can answer them: (1) substring/regex search — answered by trigram engines like…

The right first question is what questions a given team actually asks of its codebase most often, day to day, more than which engine to reach for. Substring and regex search across the whole corpus points straight at a trigram engine as the foundation. Symbol navigation, call graphs, cross-service impact analysis point at a semantic or fact-graph layer sitting on top of that foundation. Natural language queries and AI agent context point at an embeddings pipeline or an MCP-connected semantic index.

Freshness requirements act as a forcing function on their own. Teams pushing to trunk many times a day need incremental indexing; a full-rebuild cycle creates staleness that's simply unacceptable at that pace. Teams with a slower commit cadence can reasonably tolerate periodic full rebuilds if operational simplicity matters more to them than shaving hours off freshness, and there's no shame in choosing simplicity when the commit rate doesn't punish it.

Data residency is non-negotiable for a lot of enterprises, and it should be treated that way rather than as a nice-to-have. Cloud-hosted index services, including AI coding tools with cloud-embedded search, send code outside the company's environment as a matter of how they're built, not as an edge case. A self-hosted deployment, a single containerized instance wired to internal repositories, is the only architecture that satisfies strict data residency and compliance requirements, and a well-built one can match cloud-hosted search quality without the exposure. Anyone treating self-hosting as a mere capability tradeoff rather than a compliance floor is misreading the constraint entirely.

Scale and operational complexity carry their own tradeoffs. Most enterprise monorepos are served perfectly well by a trigram engine with incremental updates running on one well-provisioned node or a small cluster. Only the largest organizations, operating at the scale of Meta's fbsource or Google's monorepo, actually need a fully distributed, multi-node search cluster. Adding a semantic layer on top is a real operational investment, and it should be justified by whether structural questions, callers, subclasses, impact analysis, are genuinely blocking daily workflows today, not by whether they'd be nice to have someday.

For teams mapping this to actual tools, a rough decision tree holds up in practice. Fast text and regex search over a large monorepo: Zoekt or Livegrep. Lower-overhead browse-and-search: OpenGrok or Hound. Semantic and natural-language queries: an embeddings pipeline built on an open model and a vector database, or a self-hosted platform with a built-in AI context layer. Agent-native context wired through MCP: newer self-hosted engines, Gortex among them, released under Apache 2.0, that expose code intelligence, callers, call chains, impact analysis, as MCP tools across many languages at once.

One group gets underweighted in most of these conversations: the non-engineers. PMs, support staff, and new hires also need to query the monorepo, and a natural-language interface sitting on the same index infrastructure takes that load off senior engineers' Slack queues without requiring a second system built and maintained just for them.

Sources

  1. cursor.com
  2. github.com
  3. engineering.fb.com

More in Enterprise Code Search