Context Engineering and the Tool Belt
Read21 min · Plan → Implement → Verify → Evolve · Updated 2026-09-13
You end this lesson with: A context budget for your repository and a tool belt with everything unearned removed.
Chapters
- The Window Is a Budget, Not a Bucket01
- Just-in-Time Retrieval Beats Eager Stuffing02
- Write a File Map the Agent Reads Instead of Globbing03
- Progressive Disclosure of Instructions04
- The Tool Belt: Why More Tools Degrades Selection05
- Measuring Context Usage: A Budget Table for a Real Repo06
Practice project: Keep the Agent Working Across Session Restarts
Lecture 17. Context Engineering and the Tool Belt
You ask the agent to fix a failing test in a file you already know by name. Instead of opening it, the agent reads the whole src/ tree, then the whole tests/ tree, then three unrelated config files "for context." By the time it reaches the actual bug, half the window is gone. Its final message is a three-paragraph summary of what it found, not a patch. You paste the file path again, more directly this time, and it fixes the bug in one turn.
Nothing about the model changed between those two attempts. What changed is how much of the window the agent had left to think with, and how much of what it did have was signal versus noise. That is the whole subject of this lecture: the context window is not a bucket you fill until it's full. It's a budget, and every token you spend on material the task doesn't need is a token the agent can't spend reasoning about the material it does.
The Window Is a Budget, Not a Bucket
Three things eat a context window before the agent has done any useful work:
- Tool schemas. Every tool and MCP server you expose costs tokens up front: its name, description, and full JSON parameter schema get loaded into every single turn, whether the agent uses that tool or not. A repo wired to five MCP servers with a dozen tools each can spend several thousand tokens on tool definitions before the system prompt or your actual request is even read.
- File dumps. Reading a whole directory tree, a whole file when you needed ten lines, or a whole log when you needed the last failure: each of these trades a small amount of your effort (being precise about what you ask for) for a large, recurring cost the agent pays on every subsequent turn, because the content stays in the transcript.
- Transcript growth. Every tool call and its result stays in context for the rest of the session by default. A verbose tool (a build log, a full
git diff, an uncompressed search result) doesn't cost you once. It costs you on every following turn until the conversation ends or gets compacted.
None of these are "wasted" in the sense of being useless: a file dump might contain the one line that matters. The problem is that the agent still has to hold the other 999 lines in its attention to find that one, and so do you, every time you re-read the transcript to understand what it did.
Treat the window like a budget: a fixed size, a set of line items, and a decision about what earns a place before the session starts, not an unlimited bucket you top up whenever something seems relevant.
Just-in-Time Retrieval Beats Eager Stuffing
Eager stuffing is the instinct to be "helpful" by giving the agent everything that might be relevant up front: the whole README, the whole schema file, three related modules "just in case." It feels safe. It is the single most common way sessions run out of usable context before the task is done.
Just-in-time (JIT) retrieval inverts this: give the agent a map of what exists and let it pull the specific slice it needs, when it needs it. The agent that greps for the failing function and reads twenty lines around it ends the task with far more budget left than the one that read four files to find the same twenty lines.
| Eager stuffing | Just-in-time retrieval |
|---|---|
| Paste the whole file into the prompt "for context" | Point at the file path; let the agent Read the slice it needs |
| Attach the full API reference before any question is asked | Keep a short index; the agent looks up the one endpoint it's using |
| Re-explain project structure in every prompt | Maintain a file map (see next section) the agent reads once |
| Include full logs from every prior run | Include only the last failure, or a link to fetch more on demand |
Anthropic's own guidance on this frames it plainly: retrieve just in time by maintaining lightweight identifiers (file paths, stored queries, links) and letting the agent load data into context dynamically, rather than pre-processing everything into the prompt. The trade-off is real (JIT retrieval costs an extra tool call the eager version doesn't), but that tool call is cheap compared to the tokens it saves on every subsequent turn.
Source: Anthropic: Effective Context Engineering for AI Agents
Write a File Map the Agent Reads Instead of Globbing
The cheapest JIT mechanism is also the easiest to skip: a short file that tells the agent where things live, so it doesn't have to glob and grep its way to a mental model of your repo every session.
Without a map, a fresh session on a mid-sized repo spends its first several tool calls on pure orientation (listing directories, opening package.json, grepping for where routes are defined) before it has read a single line related to your actual request. With a map, that orientation cost drops to one file read.
A good file map is not a full repo tree. A full tree is itself eager stuffing: every path costs tokens whether or not the agent needs it. A good map lists the handful of entry points and conventions a newcomer actually needs, each with a one-line reason to go there:
# FILE-MAP.md
## Where things live
- `src/routes/` — TanStack Router file-based routes. One file per URL segment.
- `src/lib/*-fns.ts` — server functions (`createServerFn`). This is where
DB and filesystem access happens; routes and components never touch them directly.
- `src/lib/*.server.ts` — server-only modules. Never imported by client code;
the bundler enforces this, so don't work around it.
- `src/components/` — shared UI. If a component is used by only one route,
it lives beside that route instead.
- `tests/` — mirrors `src/` one-for-one. `tests/lib/course-fns.test.ts` tests
`src/lib/course-fns.ts`.
## Do not read unless the task requires it
- `content/manifest.json` — generated. Read the specific lecture's `index.md`
instead of this file; it's large and mostly irrelevant to any single task.
- `dist/`, `.output/` — build artifacts. Never hand-edit, rarely worth reading.
The second section matters as much as the first: telling the agent what not to open is as valuable as telling it where to go, because it forecloses the eager-stuffing instinct before it starts.
Progressive Disclosure of Instructions
The same budget logic applies to instructions, not just files. A single giant instructions document loaded in full, every session, regardless of what the task touches, is eager stuffing applied to your own rules.
Progressive disclosure splits instructions into layers that load only as needed:
- Always-loaded layer, a short index: project conventions that apply to every task, plus a table of pointers to deeper material. This should be small enough that loading it on every turn is a rounding error.
- Triggered layer: deeper reference material that loads only when the task matches its trigger (a specific stack, a specific kind of change, a specific failure mode).
- On-demand layer: bundled files (scripts, templates, longer references) that the triggered layer points at but doesn't inline, fetched only if the task actually needs them.
This is the same structure Anthropic describes for Agent Skills: a short always-loaded name and description, a body that loads when the skill is triggered, and bundled resources loaded only on demand from within that body, three levels, each one an order of magnitude larger than the last, so the agent never pays for depth it doesn't use.
# skills/db-migrations/SKILL.md frontmatter — always loaded (~2 lines cost)
---
name: db-migrations
description: Use when adding or changing a Drizzle schema or writing a migration.
---
<!-- Body below the frontmatter — loaded only when the description matches the task -->
# DB Migrations
Run `bunx drizzle-kit generate` after editing `schema.ts`, never hand-write
migration SQL. See `references/rollback-checklist.md` for the rollback
procedure — only read that file if this migration touches a table with
existing production rows.
The references/rollback-checklist.md file in that last line is the third layer — it exists on disk, costs nothing until the agent decides the condition applies, and is never loaded for a routine additive migration.
The Tool Belt: Why More Tools Degrades Selection
Every tool you expose is a branch the model must reason past before acting, not just a capability it might use. Two tools that do almost the same thing (a project's own file search and an MCP server's file search, say) don't add optionality. They add a decision the model has to get right on every single call: which one applies here.
This shows up in a specific, recognizable way. As the tool count grows, watch for:
- The agent picking a technically-adjacent tool over the obviously correct one, because both descriptions plausibly match.
- The agent calling a tool with the wrong parameters because it's blending two similar schemas.
- The agent doing something manually (writing a shell command) that a purpose-built tool already does better, because the tool got lost in the list.
Anthropic's guidance on tool design converges on the same fix a context budget calls for: build fewer, more consolidated tools that map to complete workflows, write descriptions unambiguous enough that a tool's purpose doesn't overlap another's, and remove tools that exist "because they might be useful" rather than because a real task needs them.
Tool-belt audit checklist: run this against every MCP server and tool group enabled in a project, not just once at setup:
- Has this tool been called in the last N sessions? If never, disable it and re-enable only when a task needs it.
- Does this tool's description overlap another enabled tool's description? If yes, merge, disable one, or rewrite both descriptions to be mutually exclusive.
- Does this tool return raw, unfiltered data (a full API response, a full log) when a filtered version would do? If yes, either configure it to filter, or wrap it.
- Is this tool here because a task needed it once, or because "it might come in handy"? The second is a reason to remove it, not keep it.
- Could this tool's whole job be done by a tool already enabled? If yes, one of them is dead weight.
Measuring Context Usage: A Budget Table for a Real Repo
You can't manage what you don't measure. Before trimming anything, build a table: what's loaded on every session, what it costs, and whether it earns its place. Here's one for a mid-sized TanStack Start repo with a handful of MCP servers enabled:
| Source | Loaded when | Approx. cost | Earns its place? |
|---|---|---|---|
| Project instructions file (always-on rules) | Every session | ~800 tokens | Yes: short, applies to every task |
| MCP server tool schemas (5 servers, ~10 tools each) | Every session | ~4,000-6,000 tokens | Partial: audit; several are unused |
File map (FILE-MAP.md) | Every session | ~300 tokens | Yes: replaces repeated exploration |
Full content/manifest.json read | Only if agent globs instead of using the map | ~2,000+ tokens | No: should never be read whole for a single-lecture task |
One lecture's index.md, read on demand | Only when that lecture is the task | ~1,500 tokens | Yes: precisely scoped |
Full git diff after a large refactor | After large diffs, stays in transcript | Grows with diff size, compounds each turn | Situational: summarize instead of leaving raw in transcript when it's not the current focus |
The pattern to look for: line items that load on every session but earn their place on only some tasks. Those are the ones to move from "always loaded" to "just in time." The failure signature of an over-stuffed window is specific and recognizable once you've seen the budget table: the agent starts producing longer prose summaries and fewer direct actions, because it's spending more of its remaining attention re-deriving what's already in the window than deciding what to do next. When you see that shift (more talk, less action, on a task that used to be a quick fix) check the budget before you check the model.
Key Takeaways
- The context window is a budget with a fixed size, not a bucket you top up when something seems relevant.
- Tool schemas, file dumps, and transcript growth are the three things that quietly consume the budget before the agent does useful work.
- Just-in-time retrieval (pointers and lookups instead of eager pasting) trades one cheap tool call for a large, recurring token saving.
- A short file map that tells the agent where to go, and what not to open, replaces repeated exploration every session.
- Progressive disclosure applies the same logic to instructions: a small always-loaded layer, a triggered layer, and on-demand resources.
- Every tool exposed is a decision the model must reason past, not just a capability. More tools without consolidation degrades tool selection, it doesn't improve it.
- The failure signature of an over-stuffed window is an agent that talks more and acts less. It's spending attention re-deriving context instead of deciding what to do next.
What You Have Now
A context budget for your repository and a tool belt with everything unearned removed.
Save the budget table as harness/context-budget.md; the lab's checker looks for that file at that path.
Example Files
FILE-MAP.md
A short repo map the agent reads instead of globbing. Drop this at the repo root or alongside your instructions file.
# FILE-MAP.md
## Where things live
- `src/routes/` — file-based routes. One file per URL segment.
- `src/lib/*-fns.ts` — server functions; DB/filesystem access lives here only.
- `src/lib/*.server.ts` — server-only modules, never bundled to the client.
- `src/components/` — shared UI; single-route components live beside their route.
- `tests/` — mirrors `src/` one-for-one.
## Do not read unless the task requires it
- `content/manifest.json` — generated; read the specific content file instead.
- `dist/`, `.output/` — build artifacts, never hand-edited.
tool-belt-audit.md
Run this checklist against every enabled MCP server and tool group at least once a month, or any time tool selection starts feeling unreliable.
# Tool-Belt Audit
For each enabled tool or MCP server:
- [ ] Called in the last N sessions? If never, disable until a real task needs it.
- [ ] Description overlaps another enabled tool? Merge, disable one, or
rewrite both to be mutually exclusive.
- [ ] Returns raw/unfiltered data where a filtered result would do? Wrap it
or configure filtering.
- [ ] Enabled "because it might be useful" rather than for a specific task?
That's a reason to remove it.
- [ ] Redundant with another already-enabled tool? Remove the weaker one.
Re-run after every new MCP server or plugin is added — the audit is only
useful as a recurring check, not a one-time setup step.
context-budget-report.ts
A small, real script that walks a set of paths and estimates their context cost, so the budget table above isn't guesswork. Token estimate uses the common ~4-characters-per-token heuristic, rough, but good enough to rank line items against each other.
/**
* context-budget-report.ts
*
* Estimates the token cost of the files you always load into a session
* (instructions, file maps, always-on config) so you can rank them and
* decide what earns "always loaded" status.
*
* Run: bunx tsx context-budget-report.ts <path1> <path2> ...
*/
import { readFileSync, statSync } from "node:fs";
interface BudgetLine {
path: string;
chars: number;
approxTokens: number;
}
function estimateTokens(chars: number): number {
// ~4 characters per token is a standard rough estimate for English text
// and typical code; treat this as a ranking heuristic, not an exact count.
return Math.ceil(chars / 4);
}
function buildReport(paths: string[]): BudgetLine[] {
return paths
.filter((p) => {
try {
return statSync(p).isFile();
} catch {
return false;
}
})
.map((path) => {
const content = readFileSync(path, "utf8");
const chars = content.length;
return { path, chars, approxTokens: estimateTokens(chars) };
})
.sort((a, b) => b.approxTokens - a.approxTokens);
}
function printReport(lines: BudgetLine[]): void {
const total = lines.reduce((sum, l) => sum + l.approxTokens, 0);
console.log("path".padEnd(40), "chars".padStart(8), "~tokens".padStart(10));
for (const line of lines) {
console.log(
line.path.padEnd(40),
String(line.chars).padStart(8),
String(line.approxTokens).padStart(10),
);
}
console.log("-".repeat(60));
console.log(`Total always-loaded budget: ~${total} tokens`);
}
const targets = process.argv.slice(2);
if (targets.length === 0) {
console.error("Usage: bunx tsx context-budget-report.ts <path1> <path2> ...");
process.exit(1);
}
printReport(buildReport(targets));
Exercises
- Build your budget table. List every file, instruction document, and MCP server your agent loads on a fresh session in your own repository. Run
context-budget-report.tsagainst the always-loaded files and fill in the approximate token cost. Mark each line "earns its place" or "move to just-in-time." - Write your file map. Create a
FILE-MAP.mdfor a repo you work in regularly, following the two-section pattern above (where things live, what not to open). Start a session with only the map instead of your usual exploration prompt and compare how many tool calls it takes the agent to reach the first correct edit. - Run the tool-belt audit. Go through every MCP server and tool group currently enabled in your setup and apply the checklist. Disable at least one tool you can't justify with a real recent task, and note whether tool selection on your next session feels more reliable.
Check your understanding
Reading is free — your progress isn't being saved
Nothing you did on this page is recorded. Paid students get saved progress and verified completions, recorded quiz answers, the spaced-repetition review queue, labs, and a certificate for each course they finish.
30-day money-back guarantee. One-time payment — lifetime access and updates.