Lesson 3 · Principal Track · Senior

Codebase Context, Chunking & RAG

~16 min · 3 modules · how real tools turn a giant repo into the right few tokens — and why retrieval, not the model, decides the answer

Published

RAGChunkingCodebase contextSemantic searchRetrieval augmented generation

Your bar: explain how Cursor and Claude Code make sense of a repo too large for any window, why chunking exists and how it breaks, and how RAG works from first principles. Then argue, against an interviewer pushing back, why most “the LLM is dumb” bugs are retrieval bugs. By the end you can answer:

given a 2M-token codebase and a 200k window, how does the system surface the right symbols — and where does that pipeline silently break?

1

This lesson is the productization of Lesson 1’s “context is the new database query.” Three steps build the query engine:

find the right code via indexing or live tools

split documents into retrievable chunks

retrieve & assemble them with RAG

where bad retrieval becomes fluent wrong answers

Repository ~2M tokens Pre-indexed embeddings Cursor · semantic recall Live tools (read-on-demand) Claude Code · grep/read Discover files Discover symbols Traverse dependencies Assemble window window ~200k
Same goal, two doors. Both paths reduce a 2M-token repo to the few right symbols that fit a 200k window. Cursor pre-indexes; Claude Code reads on demand — but both must discover files, discover symbols, and follow dependencies before assembling.2
1

Codebase Context

A repo is far bigger than any window. So the tool must discover the relevant files and symbols, follow their dependencies, and assemble the smallest sufficient slice — the way a human navigates an unfamiliar codebase.

Two strategies, side by side

Cursor — pre-indexed Claude Code — read-on-demand At setup: chunk + embed files Store vectors in an index At query: semantic retrievalnearest vectors → window + strengthsfast recall, hugerepos; finds itbefore you name it − costsindex goes stale;misses exact IDs;upfront indexing No persistent embedding index Tools: grep · glob · read Search live filesystemfollow imports like a human + strengthsalways fresh;precise on exactnames; transparent − costsmore tool round-trips: latency +tokens; needs good
The centerpiece contrast. Pre-index = precompute recall (fast, but a snapshot that drifts). Read-on-demand = compute recall live (fresh and exact, but pays in round-trips). Neither is "better" — they trade staleness against latency.34

The three discovery steps, pinned down

StepWhat it findsClassic-tooling analogue
File discoveryWhich files are even relevant (by path, glob, directory tree).

glob / file tree walk.

Symbol discoveryDefinitions & references of the functions/classes in play.

An LSP index or ctags.

Dependency traversalWhat those symbols import & call — the surrounding graph.The import / call graph.
Context assemblyPack the slice within budget — prefer signatures over full bodies.Building a query result set.

Is Codebase context is the slice of a repo a tool surfaces for one task: the right files, their symbols, and the dependencies that make them make sense — assembled within the token budget.

Why it exists The repo is ~10× any window1, so the tool can never load it all. It must reproduce, automatically, how a human finds the few relevant places before reading.

Like (world) A new hire who can’t read the whole wiki — they search, open the few right pages, follow links between them, then answer. Pre-index = they read a summary deck first; read-on-demand = they search fresh each time.

Like (code) A search index vs a live SELECT: a materialized view (embeddings) is fast but can lag the source; querying the table directly (grep) is always current but slower per hit.

✗ “Cursor and Claude Code do the same thing under the hood.”

✓ Cursor leans on a precomputed embedding index; Claude Code searches the live filesystem with tools. Different freshness/latency trade-offs, same end goal: the right symbols in budget.

✗ “An embedding index always beats grep for code.”

✓ Embeddings shine for fuzzy/semantic recall, but can miss an exact identifier that lexical search nails — and a stale index returns deleted code. Each tool wins different queries.

📥 Memory rule: Pre-index = fast but can go stale; read-on-demand = fresh but costs round-trips. Same goal: surface the right symbols within the budget.

Memory check
  • Name the three discovery steps before assembly. → file discovery, symbol discovery (definitions/references), dependency traversal (imports/call graph)
  • One downside each of pre-index vs read-on-demand? → pre-index can go stale / miss exact IDs; read-on-demand pays in tool round-trips (latency + tokens)
  • Why prefer signatures over full bodies when assembling? → they convey the interface at a fraction of the tokens, leaving budget for more relevant code and the answer

Your team’s embedding-indexed code assistant keeps citing a function that was deleted last week. What’s happening and how do you fix it?

Hit these points: a pre-built embedding index is a snapshot — if it isn’t reindexed on change, it serves stale vectors pointing at code that no longer exists → the fix is incremental reindexing on commit/save, plus a freshness check or TTL on the index → pair embeddings with a lexical/grep pass so exact current identifiers are verified against the live tree → or move latency-tolerant queries to read-on-demand so they always hit fresh files → root cause is index/source skew, not the model.

2

Chunking

You can’t embed or retrieve a whole file — it’s too big and too coarse. Chunking splits documents into pieces that each fit the embedder and are granular enough to return as a precise hit. How you cut decides what you can ever retrieve.

The size trade-off

Too big diluted, averaged embedding; imprecise hits; wasted tokens Too small lost surrounding context; many scattered fragments Just right fn calcTax() fn applyRule() fn format() split on meaning (function bounds); each self-contained
One chunk = one retrievable unit. Too big averages many ideas into one vague vector; too small strips the context that made a passage meaningful. "Just right" splits on structure, so each chunk is one coherent idea.5

Overlap & the fragmentation failure

No overlap → fact split "The retry limit is set …" "… to 5 per minute." neither chunk answers "what is the retry limit?" retrieval misses the fact Overlap → fact survives "…retry limit is set to 5 / min" shared boundary text keeps the fact whole in chunk 1 retrieval finds it
Context fragmentation: a single fact split across a boundary lives whole in neither chunk, so no chunk is a complete answer and retrieval misses it. Overlap shares boundary text so the fact survives intact in at least one chunk.5

Common mistakes → the better move

Common mistakeWhy it hurtsBetter
Fixed-size character splittingCuts mid-function / mid-sentence; chunks are incoherent.Structure-aware: split on function / class / heading bounds.
Zero overlapBoundary-spanning facts fragment and become unretrievable.Modest overlap so cross-boundary facts survive.
Ignoring document structureA chunk lacks the heading/section it belongs to; ambiguous alone.

Contextual chunking: prepend a short doc/section summary to each chunk so it self-describes.

Is Chunking is the rule for splitting a document into retrievable units. A good chunk is complete (answers on its own) and retrievable (its embedding matches the questions it should serve).

Why it exists Files exceed embedding input limits, and retrieval needs granularity — you want the relevant passage back, not a whole file. Chunking is the unit of both storage and recall.

Like (world) Index cards from a book. Cut them on chapter/section lines and each card stands alone; cut every 500 letters and you get cards that start mid-sentence and answer nothing.

Like (code) Choosing a primary key / row grain in a table: too coarse and a query returns bloated rows; too fine and you must join many rows to answer. Chunk grain is the retrieval grain.

✗ “Just split every N characters — simple and good enough.”

✓ Fixed-size splits cut mid-thought and fragment facts. Split on meaning (function/heading bounds) and add overlap so each chunk is complete and retrievable.

✗ “Smaller chunks are always more precise.”

✓ Too small strips the surrounding context that made the passage meaningful — and multiplies fragments. Precision needs the right grain plus enough context, not minimum size.

📥 Memory rule: Chunk on meaning, not character count. A chunk should be retrievable and complete on its own.

Memory check
  • What does overlap protect against? → context fragmentation — a fact spanning a boundary that would otherwise be split across two chunks and retrieved by neither
  • Too-big vs too-small chunks fail how? → too big = diluted/averaged embedding, imprecise hits, wasted tokens; too small = lost surrounding context, many fragments
  • What is contextual chunking? → prepend a short summary of the doc/section to each chunk so it's self-describing and matches better (Anthropic Contextual Retrieval)

A RAG bot answers most questions but always whiffs on “what is the rate limit?” even though the docs clearly state it. Where do you look first?

Hit these points: suspect context fragmentation — the fact (“limit is set to 5/min”) likely straddles a chunk boundary, so neither chunk is a complete, retrievable answer → inspect the actual chunks around that sentence, not the model output → fixes: add overlap so the fact survives whole; switch to structure-aware splitting so the sentence isn’t cut; apply contextual chunking so the chunk carries its section heading → verify by checking whether the right chunk now appears in top-k for that query — a retrieval check, not an answer check.

3

RAG — From First Principles

Retrieval-Augmented Generation is Lesson 1’s idea, shipped: take a question, retrieve the relevant chunks, assemble them into the window, and let the LLM answer — grounded in your data instead of its frozen weights.

The pipeline

Question Retrievalsearch the index Assemblepack into window LLM Answer Chunk index the LLM only ever sees what Retrieval surfaced — everything downstream rides on this one step
RAG is retrieval wired in front of generation. The model answers from the retrieved chunks rather than its training weights, which is what lets it cite a source, stay current, and reach your private data. The whole answer rides on whether retrieval pulled the right chunk.6

Why RAG exists vs the alternatives

NeedStuff it all in the windowFine-tune the modelRAG
Private / current dataDoesn’t fit; truncatedStale at next data change✓ fresh, pulled per query
Citations / provenanceBuried in noise✗ weights can’t cite✓ returns the source chunk
Cost to updatePay per token every callRetrain run each time✓ reindex changed docs only

Is RAG is the pattern of retrieving relevant external text at query time and putting it in the context window so the model answers from it — the productized form of “context is the new database query.”

Why it exists To ground the model in private, current, authoritative data; cut hallucination; enable citations; and do it cheaper and fresher than fine-tuning — without stuffing everything into a fixed window.

Like (world) An open-book exam. The student (LLM) is fixed, but pulling the right page before answering changes the grade. RAG is the “go fetch the right page” step.

Like (code) A read-through cache in front of a database: on each request you fetch the few relevant records and hand them to the handler. Retrieval is that fetch; the LLM is the handler.

Why so many RAG systems are poor

The naive defaultWhat it costsThe fix (maps to Module 2)
Fixed-size chunkingFragmented, incoherent chunksStructure-aware + contextual chunking
Embedding-only retrievalMisses exact keywords/IDsHybrid: embeddings + BM25 lexical
Top-k, no rerankingRight doc buried below the cutRerank so the best chunk rises to top
No retrieval evaluationYou measure the answer, never the recallEval retrieval hit-rate, not just vibes

Read the middle column. Every failure is upstream of the model: bad chunks, blind retrieval, no rerank, no measurement. Teams grade the answer and never check whether the right document was even retrieved — so they “fix the LLM” and nothing improves.

✗ “RAG gave a wrong answer, so we need a smarter model.”

✓ Usually retrieval missed: the model got nothing or the wrong passage and answered confidently anyway. Garbage in → fluent garbage out. Fix recall before model.

✗ “Embeddings + top-k is a complete RAG system.”

✓ That’s the naive baseline. Without hybrid search and reranking, the relevant doc often sits below the cut (and “lost in the middle” even when included) — so it never reaches the model usefully.

📥 Memory rule: RAG is only as good as its retrieval. Most “the LLM is dumb” bugs are retrieval bugs.

Memory check
  • Name the five stages of the RAG pipeline. → Question → Retrieval → Context assembly → LLM → Answer
  • Why RAG over fine-tuning for current/private data? → fresh per query, can cite the source chunk, cheaper to update (reindex changed docs vs retrain), no stuffing the window
  • Three reasons real RAG systems disappoint? → naive fixed-size chunking, embedding-only retrieval with no rerank, and no evaluation of retrieval quality (they measure the answer, not recall)

Sketch a docs-Q&A or support assistant as RAG, then name the three places it most often breaks in production.

Hit these points: pipeline — chunk the help center / docs, embed + index, then per question retrieve top chunks, assemble into the window with the question, LLM answers with citations → break 1: chunking destroyed the fact (fragmentation / fixed-size cuts) → break 2: retrieval missed — embedding-only with no hybrid/rerank, so the right doc is buried below top-k or lost in the middle → break 3: stale index — the doc changed but wasn’t reindexed, or conflicting passages confuse the model → in all three the model is innocent; the fix is upstream, and you must measure retrieval hit-rate, not just answer quality.

Retrieval practice — test the three modules

Q1. The key difference between Cursor's and Claude Code's codebase strategy is that…

Q2. A known downside of a pre-built embedding index for code is that…

Q3. "Context fragmentation" in chunking refers to…

Q4. The five-stage RAG pipeline, in order, is…

Q5. A RAG bot confidently gives a wrong answer. The most likely root cause is…

Interview — pick your bar

Answer out loud in ~60s, then reveal. Core = recall · Senior = trade-offs & failure modes · Staff = synthesis under ambiguity · System Design = open design round (a different axis, not a harder level).

What are the three discovery steps a tool runs before it assembles a codebase-context window?
Hit these points: file discovery — which files are even relevant, by path/glob/tree → symbol discovery — the definitions and references of the functions/classes in play (an LSP/ctags-style lookup) → dependency traversal — what those symbols import and call, the surrounding graph → then assembly packs the slice within the token budget, preferring signatures over full bodies → the goal throughout: surface the smallest sufficient slice, the way a human navigates an unfamiliar repo.
Contrast Cursor's and Claude Code's strategy for understanding a large repo.
Hit these points: Cursor pre-indexes — at setup it chunks + embeds files and stores vectors; at query time it does semantic retrieval over that index → Claude Code is read-on-demand — no persistent embedding index; it fetches live via tools (grep/glob/read) and follows imports like a human → the trade-off is freshness vs round-trips: pre-index is fast recall but a snapshot that drifts; read-on-demand is always current but pays latency and tokens per hit → same end goal: the right symbols within the budget.
What is chunking, and what makes one chunk "good"?
Hit these points: chunking is the rule for splitting a document into retrievable units — you can't embed or retrieve a whole file, it's too big and too coarse → a good chunk is complete (it answers on its own) and retrievable (its embedding matches the questions it should serve) → one chunk = one retrievable unit, ideally one coherent idea → how you cut decides what you can ever retrieve, so the chunk grain is the retrieval grain.
Name the five stages of the RAG pipeline in order, and say what RAG is.
Hit these points: Question → Retrieval → Context assembly → LLM → Answer → RAG retrieves relevant external chunks at query time and packs them into the window so the model answers from them instead of its frozen weights → that's what lets it cite a source, stay current, and reach your private data → it's the productized form of "context is the new database query" — the LLM only ever sees what Retrieval surfaced.
Your embedding-indexed assistant keeps citing a function deleted last week. What's wrong and how do you fix it?
Hit these points: a pre-built embedding index is a snapshot — if it isn't reindexed on change it serves stale vectors pointing at code that no longer exists → root cause is index/source skew, not the model → fix with incremental reindexing on commit/save plus a freshness check or TTL on the index → pair embeddings with a lexical/grep pass so exact current identifiers are verified against the live tree → or route latency-tolerant queries to read-on-demand so they always hit fresh files.
Walk through chunk-size trade-offs, and explain how zero-overlap fixed-size splitting silently loses facts.
Hit these points: too big = diluted/averaged embedding, imprecise hits, wasted tokens; too small = lost surrounding context and many scattered fragments → fixed-size character splitting cuts mid-function/mid-sentence, so chunks are incoherent → with zero overlap a fact that straddles a boundary ("retry limit is set" | "to 5/min") lives whole in neither chunk, so no chunk is a complete answer and retrieval misses it — context fragmentation → fixes: structure-aware splitting on function/heading bounds, modest overlap so cross-boundary facts survive, and contextual chunking that prepends the section heading.
A RAG bot answers most questions but always whiffs on "what is the rate limit?" though the docs state it. Where do you look first?
Hit these points: suspect context fragmentation — the fact likely straddles a chunk boundary, so neither chunk is a complete retrievable answer → inspect the actual chunks around that sentence, not the model output → check whether the right chunk even appears in top-k for that query — a retrieval check, not an answer check → fixes: add overlap so the fact survives whole, switch to structure-aware splitting so the sentence isn't cut, apply contextual chunking so the chunk carries its heading → the model is innocent; the bug is upstream in chunking/retrieval.
An embedding-only RAG returns the right document for paraphrased questions but misses when users type an exact error code. Why, and what do you add?
Hit these points: embeddings capture semantic similarity, so they shine on paraphrase but can fail to match a rare exact token — an error code or identifier sits in a sparse region where nearest-neighbor recall is weak → that's a recall miss, not a model failure → add lexical retrieval (BM25) so exact strings hit, and run it as a hybrid with embeddings → rerank the merged candidates so the best chunk rises above the cut → verify with a retrieval eval that includes exact-match queries, not just semantic ones — otherwise the gap stays invisible in answer-only vibes.
At scale, how do you decide between a pre-built embedding index and read-on-demand — and when do you combine them?
Hit these points: read-on-demand when freshness and exact-identifier precision dominate and the repo changes constantly — nothing to maintain or go stale, but you pay latency + tokens per round-trip → pre-index when fast semantic recall across a huge corpus matters and queries are fuzzy ("find the code that does X" before you know the name), accepting upfront/reindex cost and staleness risk → the senior move is to combine: embeddings for semantic recall, lexical/live read to verify exact current code, hybrid (embeddings + BM25), reindex incrementally on change → the choice is a staleness-vs-latency trade-off, not a winner.
A team says "our RAG is bad, the model hallucinates — we need a smarter model." Diagnose this as the senior in the room.
Hit these points: reframe — most "the LLM is dumb" bugs are retrieval bugs: garbage in, fluent garbage out → first instrument what was actually retrieved per failing query; teams grade the answer and never the recall → check the usual suspects: fixed-size chunking destroyed the fact, embedding-only retrieval missed exact terms, no reranking so the right doc sits below top-k (or lost in the middle), or a stale index / conflicting passages → fixes map to chunking + retrieval: structure-aware + contextual chunking, hybrid + rerank, reindex on change → gate on a retrieval-quality eval (hit-rate / recall@k), not end-answer vibes → only after retrieval is verified good is model capability the lever worth paying for.
Make the case that retrieval quality matters more than model quality for a code assistant — then steelman the opposite.
For: the model only reasons over what it's given, so a stale index or a fragmented chunk yields a wrong answer at any model size; the model is a shared commodity while retrieval is your design, and retrieval fixes compound across features and cost far less than model upgrades. Steelman against: below a capability floor a weak model can't synthesize even a perfectly retrieved slice — multi-file refactors, long dependency reasoning — so there the model is the binding constraint. Synthesis: fix retrieval first; it's the common cause and the cheapest lever, and you can't even tell whether reasoning is the bottleneck until retrieval is verified good. Pay for model capability once recall@k is high and reasoning is provably the limit.
Design-round framework — drive any RAG / code-context prompt through these, out loud:
  1. Clarify scale: corpus size vs window, query volume, freshness needs, latency & cost budget.
  2. Ingest & chunk: structure-aware splitting + overlap; contextual chunking so each chunk self-describes.
  3. Candidate generation: hybrid — semantic (embeddings) + lexical (BM25) for exact IDs/keywords.
  4. Rerank the top-N for precision; assemble within budget, ordered against lost-in-the-middle.
  5. Freshness: incremental reindex on change, TTL/invalidation, or read volatile/exact data live.
  6. Evaluation: measure retrieval hit-rate / recall@k as the gate, not just answer vibes.
  7. Failure modes & guardrails: retrieval miss, fragmentation, stale index, conflicting passages, no citation.
Design a RAG system for a help-center support bot over a changing knowledge base.
A strong answer covers: ingest the help center, chunk structure-aware on headings/sections with overlap, and apply contextual chunking so each chunk carries its article/section context → embed + index, and add lexical (BM25) so exact product names and error codes match → per question: retrieve hybrid candidates, rerank the top-N, assemble into the window with the question, and have the LLM answer with citations to the source chunk → keep the index fresh — reindex changed articles incrementally, TTL stale ones → name the three break points: chunking destroyed the fact, retrieval missed (no hybrid/rerank, buried below top-k or lost in the middle), stale/conflicting index → close the loop with a retrieval-hit-rate eval, not just end-answer quality.
Design the code-context layer for a coding agent over a 2M-token monorepo with a 200k window.
A strong answer covers: you can surface ~10% of the repo at most, so retrieval is the product, not the window size → index symbols + a dependency/call graph, not just flat text chunks → on a query, gather candidates by exact symbol match (lexical) and semantic similarity, then walk imports/call-sites for structural context → rerank, then pack within budget: signatures and docstrings over full bodies, the files named in the query first, and reserve fixed budget for history + the answer → keep it fresh — incremental re-embed/ctags on change, or read-on-demand for always-fresh precision on exact identifiers → instrument what was retrieved so you can measure hit-rate → name the trade-off explicitly: pre-indexed embeddings (fast, can go stale) vs read-on-demand (fresh, more round-trips).
Sources
Anthropic, "Effective context engineering for AI agents" — anthropic.com/engineering. Context as a finite, curated resource; selection over dumping. Token figures illustrative.
Liu et al., 2023, "Lost in the Middle: How Language Models Use Long Contexts" — arXiv:2307.03172. Why buried / mid-context content is recalled poorly, so top-k order and reranking matter.
Cursor, documentation — cursor.com/docs. Codebase indexing via embeddings; semantic retrieval. Product specifics are version-dependent.
Anthropic, Claude Code documentation — docs.anthropic.com. Read-on-demand context model via live filesystem tools (grep / glob / read).
Anthropic, "Introducing Contextual Retrieval" — anthropic.com/news/contextual-retrieval. Chunk-level context loss; contextual chunks (prepend summary); hybrid embeddings + BM25 with reranking.
Lewis et al., 2020, "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks" — arXiv:2005.11401. The original RAG formulation: retrieve relevant passages, then generate grounded in them.
Was this lesson helpful? Thanks — noted.

These notes reflect my current understanding and are updated as I learn, build, and discover better explanations.