Make Your Monorepo Searchable by an Agent
Priya maintains a monorepo. Services, runbooks, ADRs, design docs, onboarding notes — deep folder structure, hundreds of files. Her first attempt at codebase search was a classic vector store: split every file, embed, query. It returned text, but the text was adrift. A runbook fragment with no folder context. A design doc chunk with no link to the service it describes. The model had no idea that services/billing/README.md and runbooks/billing/deploy.md were about the same thing.
The problem wasn’t retrieval — it was that her pipeline discarded the repo’s structure before retrieval ever ran. The folder tree is the knowledge graph. She just needed a tool that kept it.
What you’ll build
Section titled “What you’ll build”A relationship-aware knowledge space over a monorepo that:
- Preserves the folder tree as a relation graph —
sibling,parent,references, andcontinuesedges, not just chunks. - Lets you filter by document type at query time — ask for runbooks only, or design docs only.
- Rebuilds incrementally with
--resumeso a pre-commit hook or local loop stays fast. - Can run structure-only with
--no-embedwhen you want the graph without paying for vectors.
By the end you’ll have a handbook.indx archive, a committed indx.toml, and a query surface your agents can call.
The build
Section titled “The build”-
Install indx.
Terminal window pip install "indx[cloud]"export OPENAI_API_KEY="sk-..." -
Build over the repo directory.
The directory is the input — no manifest, no glob. indx walks the tree, parses every file, chunks it, derives the relation graph from the folder structure and cross-file mentions, enriches each document with a detected type and summary, and embeds everything into one portable archive.
Terminal window indx ./repo --out ./ai-readyindx ./repo → ./ai-ready01 walk 318 files, 42 folders02 parse 314 ok, 4 skipped03 chunk 2740 chunks04 relate 891 relations05 enrich 314 documents (openai:gpt-5-mini)06 embed 2740 vectors → qdrant, sealed handbook.indxdone: 2740 chunks, 314 docs, embed_dim=1536 (28.4s)That
04 relate 891 relationsline is the part a flat splitter never produces. indx derives four relation types from the repo structure:sibling— files in the same folder.services/billing/andrunbooks/billing/are linked at the folder level.parent— folder lineage. A deep config file inherits relations up to the root.continues— adjacent chunks within the same file, so a long runbook reads as one continuous thread.references— cross-file mentions. Whendeploy.mdreferencesconfig.yaml, indx traces that edge.
These four edges are the signal a hand-rolled pipeline throws away at split time.
-
Commit an
indx.tomlso every contributor’s build is identical.Drop this file at the repo root. Every flag you’d pass on the CLI can live here, pinned to the project.
[parser]engine = "docling"[enrich]llm = "openai:gpt-5-mini"vlm = "none"metadata = ["type", "topics", "tags", "summary"][embed]model = "openai:text-embedding-3-small"[store]backend = "qdrant"[output]format = ".indx"Now
indx ./repo --out ./ai-readyis the full command — the config is resolved automatically. CLI flags override the file when needed; see the Configuration reference for the full precedence order. -
Inspect the space before writing any agent code.
indx inspectshows you the type histogram and a sample of the relation graph — the two things worth verifying before you wire retrieval.Terminal window indx inspect ./ai-ready/handbook.indxarchive: handbook.indxdocuments: 314 chunks: 2740 vectors: 2740 (dim 1536)store: qdrantdocument typesrunbook 47guide 38design-doc 31policy 18reference 91code 89relations (sample)sibling services/billing/README.md ↔ runbooks/billing/deploy.mdparent runbooks/billing/deploy.md → runbooks/references runbooks/billing/deploy.md → services/billing/config.yamlcontinues chunk_1042 → chunk_1043That sample is the graph in action:
deploy.mdis a sibling ofREADME.md, a child ofrunbooks/billing/, and it references the config file it describes. A flat splitter had none of that. -
Query restricted to a document type.
Once the space looks right, run a query. Use
--typeto restrict results to a single document type — exactly what you want when the question is deployment-specific.Terminal window indx query ./ai-ready/handbook.indx "how do I deploy the billing service?" -k 3 --type runbook#1 score 0.91 runbooks/billing/deploy.md (runbooks/billing · runbook)"To deploy billing, run `make deploy ENV=prod` from the service root…"neighbors: chunk_1042, chunk_1044#2 score 0.83 runbooks/billing/rollback.md (runbooks/billing · runbook)"If the deploy fails, run `make rollback` — this reverts the last…"neighbors: chunk_1051, chunk_1053#3 score 0.79 runbooks/billing/smoke-test.md (runbooks/billing · runbook)"After each deploy, hit /health and /metrics — both must return 200…"neighbors: chunk_1060, chunk_1062All three hits are runbooks in
runbooks/billing/— siblings of each other. Without the relation graph, a flat query for “deploy billing” can surface an unrelated CI config or a design doc that happens to mention billing. -
Use
--resumefor fast incremental rebuilds.--resumereuses cached stage outputs for every file and config key that hasn’t changed. Only modified files re-run through the pipeline. Ideal in a pre-commit hook or a local edit loop.Terminal window indx ./repo --out ./ai-ready --resumeindx ./repo → ./ai-ready (resume)01 walk 318 files, 42 folders (cache hit: 311 unchanged)02 parse 7 ok, 0 skipped (311 from cache)03 chunk 58 chunks (2682 from cache)04 relate 12 relations updated05 enrich 7 documents (openai:gpt-5-mini)06 embed 58 vectors → qdrant, sealed handbook.indxdone: 2740 chunks, 314 docs, embed_dim=1536 (3.1s)
Why the relation graph changes retrieval
Section titled “Why the relation graph changes retrieval”Priya’s first pipeline retrieved text. The indx space retrieves grounded text — every chunk knows its source document, its folder, its detected type, and the chunks next to it.
That changes three things a flat splitter can’t do:
- Siblings surface together. When
deploy.mdscores highly, its folder-siblings (rollback.md,smoke-test.md) are connected bysiblingedges — an agent can widen to them without a second query. - Type filtering is structural, not heuristic.
--type runbookisn’t a keyword filter; it’s filtering on thetypefield Enrich assigned each document. The same query over--type design-docreturns a completely different set. - Neighbor chunks close the context gap. Each hit returns neighbor chunk ids. An agent that reads those gets the full runbook section, not an orphaned paragraph.
The Relate stage explains how each relation type is derived and how to tune the cross-reference detector.
You can also drive the same space from Python directly:
from indx import DirectoryPipeline
space = DirectoryPipeline(config="indx.toml").run("./repo", "./ai-ready")hits = space.search("how do I deploy the billing service?", k=3)
for hit in hits: print(hit.score, hit.source.path, hit.source.type) print(hit.chunk.text) print(hit.neighbors)space.documents(type="runbook") returns all runbooks if you want to walk them programmatically.
What you learned
Section titled “What you learned”indx <dir> --out <dir>turns a folder tree into a relation-aware knowledge space —sibling,parent,continues, andreferencesedges are derived automatically.- A committed
indx.tomlpins the build config so every contributor and CI run produces the same archive. indx inspectshows the document-type histogram and a relations sample — verify the graph before writing any agent code.--typeat query time filters by the Enrich-detected document type, so “how do I deploy?” can be scoped to runbooks only.--resumemakes incremental rebuilds fast enough for a pre-commit hook or local loop.--no-embedproduces a graph-only space for cheap structural inspection.