Lesson 3: Just-in-Time Retrieval: Letting the Agent Fetch Its Own Context
Learning goals:
- Use the attention budget to price the hidden cost of preloading, and decide whether a given piece of material belongs in the initial context
- Describe how just-in-time retrieval works: lightweight identifiers, metadata as signal, relevant context discovered progressively through exploration
- Draw the hybrid strategy for one concrete agent — what gets preloaded, and what stays behind as an identifier to be fetched at run time
Prerequisites: You've read Lessons 1 and 2, and you have the harness loop from course 7 of this series, "Agent Harness Fundamentals: Loops and Control," within reach | Prev: Lesson 2 << | Next: Lesson 4 >>
First, Resist the Urge to Cram It All In
Say you're building a Q&A agent for a codebase: 200 source files in the repo, and users ask things like "where is this function defined" or "what breaks if I change this config." The obvious move is to read all 200 files and paste them into the initial context — context windows are big now, it'll fit.
It fits. That doesn't mean it belongs. Lesson 1 covered why: models parse large volumes of context by drawing on an "attention budget," and "Every new token introduced depletes this budget by some amount."1 The arithmetic only gets worse as you go: "as the number of tokens in the context window increases, the model's ability to accurately recall information from that context decreases."1 The saving grace is that this is a slope, not a ledge — "some models exhibit more gentle degradation than others, this characteristic emerges across all models," and together these factors "create a performance gradient rather than a hard cliff."1 Which brings back Lesson 1's conclusion, worth repeating here: "context, therefore, must be treated as a finite resource with diminishing marginal returns."1
Back to those 200 files. A user asks one specific question, and maybe two or three files are genuinely relevant; the hundred-odd thousand tokens carried by the other 197 aren't harmless scenery. They compete for attention with the content that matters, out of the same budget. Worse, the agent runs in a loop, and "An agent running in a loop generates more and more data that could be relevant for the next turn of inference."1 If the initial context is already seven-eighths full, the loop hits the wall after a handful of turns.
So the question becomes: when do you put material directly in front of the model, and when do you just tell it where the material lives and let it go get it? That's this whole lesson.
The Two Strategies, Side by Side
Start by stating each one plainly.
Preloading: before inference begins, everything that might be needed goes into the initial context. The model sees all of it on turn one and never has to retrieve anything.
Just-in-time retrieval: the initial context holds no source material, only lightweight identifiers — the approach is to "maintain lightweight identifiers (file paths, stored queries, web links, etc.)"1 and let the agent load content through tools at run time, as needed.
Think about how you actually work: you haven't memorized the codebase. What you carry around is "auth logic lives in the auth directory," "config parsing is probably in config.js" — an index that points at content, and you open the file when you need the detail. Just-in-time retrieval hands that working style to the agent.
But look at the last cell of that table again: retrieval isn't free. Every just-in-time fetch is a full tool-call round trip — the model issues the call, the harness runs it, the result comes back, the model reasons again. In course 7 of this series, "Agent Harness Fundamentals: Loops and Control," you fitted your harness with two valves, max turns and a budget cap; retrieval spends exactly what those two valves govern. So "always just-in-time" isn't the answer either. It's a bill to be calculated, and the trade-off framework later in this lesson does the math.
How Just-in-Time Retrieval Actually Works
Three things make it run: a set of identifiers (so the model knows what exists and roughly where), a few retrieval tools, and a loop that permits multiple turns of exploration. Put together, this "allows agents to incrementally discover relevant context through exploration."1
Here's the part that gets underrated: the identifiers' metadata is itself signal. File names and directory structure both advertise what content is for and how relevant it's likely to be.1 You don't have to open tests/refund.test.js to know what's inside; a legacy/ directory untouched for two years probably isn't the place to start. Anthropic's write-up of its multi-agent research system puts the underlying move sharply: "The essence of search is compression: distilling insights from a vast corpus."2 Every step of just-in-time retrieval — listing a directory, searching a keyword, picking a file — performs that compression, narrowing "a broad swath of maybe-relevant" down to "the small piece I actually have to read."
Down to code. Give that codebase Q&A agent three tools; all three implementations are short:
Then write the tool definitions to Lesson 2's standard — "self-contained," "extremely clear with respect to their intended use," with "minimal overlap in functionality":1
The system prompt carries only the stable parts: the job, the citation requirement, the behavioral conventions for retrieval. Note that not one line of file content appears in it:
Wire this up to a small e-commerce repo, ask "where does the order refund amount get calculated?", and a typical run looks like this. (The repo contents are illustrative; the shape of each call and the format of each return are fixed by the implementations above. The turn-3 file body is too long to print, so a one-line parenthetical stands in for it.)
Watch what happened between turns 2 and 3. Grep returned three matching lines, and the model didn't read both files. From the line contents it worked out that the definition sits in refund.js while service.js is merely the importer and caller, so it opened exactly one file. That's metadata doing the model's first pass of filtering for it: "the metadata of these references provides a mechanism to efficiently refine behavior."1 Across the whole trajectory, what entered the context was one directory listing, three lines of grep output, and one 60-line file — not 200 files.
Two more details reward a second look. The first is the truncation baked into two of the implementations: grep returns at most 50 lines, read_file at most 400. Lesson 2 made the point that tools should be "returning information that is token efficient"1 — retrieval tools are the context's suppliers, and the loop only survives if the suppliers cap their shipments. The second is the failure case: if grep keeps coming up empty, the model may search over and over with different keywords. That is precisely the scenario the idle-spin detection and budget valve from the harness course exist to catch — exploration is good, unbounded exploration isn't.
Hybrid Is the Normal Case
You might expect the conclusion to be "just-in-time retrieval wins." It isn't. Real systems rarely sit at either pole; the common shape is a hybrid — "retrieving some data up front for speed, and pursuing further autonomous exploration at its discretion."1
Claude Code, which you use every day, is a live example: "CLAUDE.md files are naively dropped into context up front, while primitives like glob and grep" support just-in-time exploration at run time.1 The official docs describe it plainly — "CLAUDE.md is a special file that Claude reads at the start of every conversation."3 Because it loads every single time, the docs advise keeping only broadly applicable material in it and asking of every single line: "Would removing this cause Claude to make mistakes?"3 If the answer is no, that line should go. The docs put the consequence bluntly: "Bloated CLAUDE.md files cause Claude to ignore your actual instructions!"3
Skills take a third route — "Claude loads them on demand without bloating every conversation."3 Line those three up and you get a tiered picture of a hybrid strategy:
- CLAUDE.md: stable, binding on every turn → preloaded at the start;
- Skills: packaged specialist capability, used only for specific tasks → loaded on demand;
- The codebase itself: enormous, and only a sliver is needed each time → explored just-in-time via glob and grep.
When you design your own agent, you're drawing a version of that same picture: which material sits in "the CLAUDE.md slot," and which sits in "the codebase slot."
A Trade-Off Framework You Can Apply Right Away
For each candidate piece of material, ask two questions:
- Is it stable? Does the content stay put over time, independent of any particular question?
- Is it used on every turn — or nearly every turn?
Two yeses → preload. Typical cases: coding standards, core business constraints, the agent's rules of conduct, the top-level directory structure. Material like this is usually small too — if something billed as "needed every turn" turns out to be enormous, start by doubting that it really is needed every turn.
Any no → leave an identifier and retrieve just-in-time. Typical cases: a module's full source (needed only for questions about that module), historical tickets (consulted only when chasing a specific failure), a long design doc (opened only when aligning on an approach).
Then put the cost of retrieval on the scale and check the result once more: each fetch adds a round trip, adds latency, spends budget. So don't stubbornly push small, frequently used material into retrieval — trading 600 words of preload space for an extra list_files turn in every session is a losing deal. Going the other way, preloading a 2,000-line script that probably won't come up is pure attention-budget burn.1
The Claude Code docs put the stakes in one line: "The context window is the most important resource to manage."3 Preloading and just-in-time retrieval aren't rival doctrines. They're the two hands you manage that resource with.
This Lesson Skips RAG, On Purpose
Say "retrieval" and many people jump straight to vector stores, embeddings, RAG pipelines. This lesson deliberately doesn't touch any of it — the course README draws the boundary, and "just-in-time retrieval" here means something plainer: an agent holding filesystem and search tools, pulling content on demand.
That isn't pedagogical laziness. File paths come with hierarchy and naming semantics for free, grep results are precise and explainable, and the pair is already enough to sustain a full loop of incrementally discovering relevant context through exploration.1 Anthropic's guidance on building agents offers a matching sense of proportion: "you should consider adding complexity only when it demonstrably improves outcomes."4 Note the verb — consider. That's a posture of weighing, not a prohibition. For a codebase Q&A agent, walking the simplest path (filesystem plus grep) until you can measure where it falls short fits that posture better than standing up vector retrieval on day one.
One thread to leave hanging: however disciplined your just-in-time retrieval is, an agent grinding through a long task keeps accumulating tool results turn after turn,1 and the context window creeps toward its ceiling regardless. At that point, being good at "taking less" stops being enough — you also need to be good at throwing things out and writing things down. That's Lesson 4.
Recap
- "It fits" is not a reason to preload: every new token depletes the attention budget, and as token count grows the model's accurate recall from context declines — a performance gradient rather than a hard cliff; context has to be managed as a finite resource with diminishing marginal returns1
- How just-in-time retrieval works: the context keeps only lightweight identifiers (file paths, stored queries, web links), and tools load content on demand at run time; the identifiers' metadata — file names, directory structure — signals relevance by itself, which lets agents incrementally discover relevant context through exploration1
- Retrieval isn't free: each fetch is a tool-call round trip, spending latency plus the turns and budget governed by the control valves from the harness course
- Hybrid is the normal case: retrieve some data up front for speed, and let the model pursue further autonomous exploration at its discretion1. Claude Code is the ready-made reference — CLAUDE.md dropped in whole at the start, skills loaded on demand, the codebase explored on the spot via glob and grep1 3
- The framework is two questions: stable? needed every turn? Two yeses means preload, otherwise leave an identifier; don't force small-and-frequent material into retrieval, and don't force large-and-rare material into preload
- "Just-in-time retrieval" in this lesson means on-demand pulls through filesystem and search tools, with no vector store involved; before you bring in heavier retrieval machinery, keep that sense of proportion in mind — consider adding complexity only when it demonstrably improves outcomes4
>> Lesson 4: Compaction and Notes: Context Management for Long Tasks