InterviewKata
AI engineering · Module 05a · Level 2: Working with AI

Context engineering for agents

Module 05 showed how to write a good brief. An agent working for an hour builds most of its own context: it searches, reads files, runs commands and piles up tool output. Whether it succeeds depends less on the prompt than on what ends up in its context window, and what stays out. This module covers the techniques that decide that: just-in-time retrieval, code search and import graphs, compaction, notes files and subagents, when to use each, and a demo that finds a bug's real cause with a sixth of the tokens.

A 4-minute conversation: Sam asks, Priya explains
0:00 / 3:34

1. The idea

A detective's desk. Dumping every file in the archive onto it buries the one clue that matters. A good detective keeps a short list of leads, pulls a file only when a lead points to it, writes notes in a notebook so nothing depends on memory, sends a junior to read a thousand pages of phone records and report back one paragraph, and clears the desk before starting the next line of inquiry.

Anthropic describes context as a finite resource with diminishing returns: every token added competes for the model's attention, and accuracy falls as the window fills (Module 02's context rot). Context engineering is the job of keeping the smallest set of high-signal tokens in front of the model at each step.

TechniqueWhat it doesFixes
Instructions up frontA short project file (CLAUDE.md, AGENTS.md) loaded at the start: commands, conventions, what to avoidRediscovering the same facts every session
Just-in-time retrievalKeep references (paths, queries, URLs); load content only when a step needs it, with search tools like grep and globA window stuffed with files that turn out irrelevant
Structure-aware searchFollow imports, call graphs and symbol definitions (language servers, AST outlines) from a starting point such as a failing testKeyword search missing the file that matters
CompactionSummarise the conversation near the limit and continue in a fresh window; the lightest form is clearing old tool resultsLong sessions running out of room or going stale
Notes filesThe agent writes progress, decisions and to-dos to a file outside the window, and rereads itLosing track across compactions and sessions
Subagents for isolationA helper explores in its own window (tens of thousands of tokens) and returns a 1,000 to 2,000-token summarySearch noise and huge logs flooding the main context
Clear toolsFew, unambiguous tools that return compact resultsThe agent choosing the wrong tool, or tools returning pages of noise

2. How it works

One bug fix, with the context assembled step by step.

The right altitude for instructions

Anthropic describes two failure modes for system prompts: brittle, hard-coded if-else rules on one side, vague guidance that assumes shared context on the other. Aim between them: specific enough to steer, general enough to let the model reason. Start minimal, and add an instruction only when you see a failure it would have prevented.

3. Try it yourself

Build the context for one task

The task: "A refund of $19.99 comes back as $19.98. Fix it." Tick what goes into the agent's context. The bar shows tokens, split into what helps and what's noise.

4. Why it matters at work

When to use which, from real systems

SituationTechniqueEvidence
Facts the agent can't learn from the code: deploy steps, secrets to avoid, test commandsA short instructions file loaded up frontClaude Code's guide: tell the agent what it can't see; keep the file short
A large codebase, a specific taskJust-in-time retrieval with search, plus following imports from a failing testClaude Code combines up-front instruction files with on-demand glob and grep (Anthropic, September 2025)
Wide exploration: many files, logs, web pagesSubagents that return short summariesAnthropic's research system: parallel subagents, each condensing its findings for the lead (C11)
One long, coupled taskA single agent with compaction and notes, rather than splitting itCognition: share full context; for long tasks, compress history into key details and decisions (C12)
Work that spans sessions or daysA notes or progress file the agent maintainsAnthropic's example of an agent tracking progress across thousands of steps through notes

Choosing files for a bug fix, in code

A ten-file repository and a rounding bug in billing/money.py. Loading everything costs 15,700 tokens. Keyword search for "refund" still costs 11,900 tokens, mostly a changelog and a UI file, and misses the culprit, because the word "refund" never appears in it. Starting at the failing test and following imports finds it in four files and 2,500 tokens. The program was run in all three languages; each prints the output below it.

# A small repository: file -> (tokens, files it imports, words it contains).
REPO = {
    "api/routes.py": (1200, ["billing/refund.py", "auth/session.py"], {"refund", "login"}),
    "billing/refund.py": (900, ["billing/money.py", "billing/policy.py"], {"refund"}),
    "billing/money.py": (400, [], {"round", "cents"}),
    "billing/policy.py": (500, [], {"refund", "window"}),
    "billing/invoice.py": (1100, ["billing/money.py"], {"invoice"}),
    "auth/session.py": (800, [], {"login"}),
    "auth/oauth.py": (1500, [], {"login"}),
    "ui/checkout.tsx": (2600, [], {"checkout", "refund"}),
    "docs/CHANGELOG.md": (6000, [], {"refund", "invoice", "login"}),
    "tests/test_refund.py": (700, ["billing/refund.py"], {"refund"}),
}
CULPRIT = "billing/money.py"      # bug report: "a refund of $19.99 comes back as $19.98" (a rounding bug)


def everything():
    return list(REPO)


def keyword(word):
    return [f for f, (_, _, words) in REPO.items() if word in words]


def follow_imports(start):
    """Start from the failing test and follow imports, the way an agent uses search and an import graph."""
    seen, todo = [], [start]
    while todo:
        f = todo.pop(0)
        if f not in seen:
            seen.append(f)
            todo.extend(REPO[f][1])
    return seen


for name, files in [("load everything", everything()), ('keyword search "refund"', keyword("refund")),
                    ("failing test + imports", follow_imports("tests/test_refund.py"))]:
    tokens = sum(REPO[f][0] for f in files)
    found = "includes" if CULPRIT in files else "misses"
    print(f"{name:24} {len(files):2} files {tokens:6,} tokens, {found} the culprit")
// A small repository: file -> [tokens, files it imports, words it contains].
const REPO = new Map<string, [number, string[], string[]]>([
  ["api/routes.py", [1200, ["billing/refund.py", "auth/session.py"], ["refund", "login"]]],
  ["billing/refund.py", [900, ["billing/money.py", "billing/policy.py"], ["refund"]]],
  ["billing/money.py", [400, [], ["round", "cents"]]],
  ["billing/policy.py", [500, [], ["refund", "window"]]],
  ["billing/invoice.py", [1100, ["billing/money.py"], ["invoice"]]],
  ["auth/session.py", [800, [], ["login"]]],
  ["auth/oauth.py", [1500, [], ["login"]]],
  ["ui/checkout.tsx", [2600, [], ["checkout", "refund"]]],
  ["docs/CHANGELOG.md", [6000, [], ["refund", "invoice", "login"]]],
  ["tests/test_refund.py", [700, ["billing/refund.py"], ["refund"]]],
]);
const CULPRIT = "billing/money.py"; // bug report: "a refund of $19.99 comes back as $19.98" (a rounding bug)

const everything = () => [...REPO.keys()];
const keyword = (word: string) => [...REPO].filter(([, [, , words]]) => words.includes(word)).map(([f]) => f);

// Start from the failing test and follow imports, the way an agent uses search and an import graph.
function followImports(start: string): string[] {
  const seen: string[] = [], todo = [start];
  while (todo.length) {
    const f = todo.shift()!;
    if (!seen.includes(f)) {
      seen.push(f);
      todo.push(...REPO.get(f)![1]);
    }
  }
  return seen;
}

const runs: [string, string[]][] = [["load everything", everything()], ['keyword search "refund"', keyword("refund")],
  ["failing test + imports", followImports("tests/test_refund.py")]];
for (const [name, files] of runs) {
  const tokens = files.reduce((s, f) => s + REPO.get(f)![0], 0);
  const found = files.includes(CULPRIT) ? "includes" : "misses";
  console.log(`${name.padEnd(24)} ${String(files.length).padStart(2)} files ${tokens.toLocaleString("en-US").padStart(6)} tokens, ${found} the culprit`);
}
import java.util.*;

public class Main {
    record FileInfo(int tokens, List<String> imports, Set<String> words) {}

    // A small repository: file -> tokens, files it imports, words it contains.
    static final Map<String, FileInfo> REPO = new LinkedHashMap<>();
    static {
        REPO.put("api/routes.py", new FileInfo(1200, List.of("billing/refund.py", "auth/session.py"), Set.of("refund", "login")));
        REPO.put("billing/refund.py", new FileInfo(900, List.of("billing/money.py", "billing/policy.py"), Set.of("refund")));
        REPO.put("billing/money.py", new FileInfo(400, List.of(), Set.of("round", "cents")));
        REPO.put("billing/policy.py", new FileInfo(500, List.of(), Set.of("refund", "window")));
        REPO.put("billing/invoice.py", new FileInfo(1100, List.of("billing/money.py"), Set.of("invoice")));
        REPO.put("auth/session.py", new FileInfo(800, List.of(), Set.of("login")));
        REPO.put("auth/oauth.py", new FileInfo(1500, List.of(), Set.of("login")));
        REPO.put("ui/checkout.tsx", new FileInfo(2600, List.of(), Set.of("checkout", "refund")));
        REPO.put("docs/CHANGELOG.md", new FileInfo(6000, List.of(), Set.of("refund", "invoice", "login")));
        REPO.put("tests/test_refund.py", new FileInfo(700, List.of("billing/refund.py"), Set.of("refund")));
    }
    static final String CULPRIT = "billing/money.py"; // bug report: "a refund of $19.99 comes back as $19.98" (a rounding bug)

    static List<String> keyword(String word) {
        List<String> out = new ArrayList<>();
        REPO.forEach((f, info) -> { if (info.words().contains(word)) out.add(f); });
        return out;
    }

    // Start from the failing test and follow imports, the way an agent uses search and an import graph.
    static List<String> followImports(String start) {
        List<String> seen = new ArrayList<>();
        Deque<String> todo = new ArrayDeque<>(List.of(start));
        while (!todo.isEmpty()) {
            String f = todo.poll();
            if (!seen.contains(f)) { seen.add(f); todo.addAll(REPO.get(f).imports()); }
        }
        return seen;
    }

    public static void main(String[] args) {
        Map<String, List<String>> runs = new LinkedHashMap<>();
        runs.put("load everything", new ArrayList<>(REPO.keySet()));
        runs.put("keyword search \"refund\"", keyword("refund"));
        runs.put("failing test + imports", followImports("tests/test_refund.py"));
        runs.forEach((name, files) -> {
            int tokens = files.stream().mapToInt(f -> REPO.get(f).tokens()).sum();
            String found = files.contains(CULPRIT) ? "includes" : "misses";
            System.out.println(String.format(Locale.US, "%-24s %2d files %,6d tokens, %s the culprit", name, files.size(), tokens, found));
        });
    }
}
load everything          10 files 15,700 tokens, includes the culprit
keyword search "refund"   6 files 11,900 tokens, misses the culprit
failing test + imports    4 files  2,500 tokens, includes the culprit

5. In your five tools

The names differ; every major coding agent has each piece. Checked 4 November 2026.

TechniqueWhere you find it
Instructions up frontCLAUDE.md, AGENTS.md (read by Codex, Cursor, Copilot and others), .github/copilot-instructions.md; Module 09
Just-in-time retrievalBuilt-in search and read tools in every agent; codebase indexes for semantic search in Cursor and Copilot
Structure-aware searchLanguage-server tools and MCP servers for symbols and references; ask the agent to start from a failing test
CompactionAutomatic compaction, plus /compact with a focus and /clear between tasks in Claude Code; similar commands elsewhere
Notes filesAsk for a PROGRESS.md or task list the agent updates; memory features (Module 09)
SubagentsEvery tool's subagents (Module 12): give them the search, keep their summaries

6. Remember it

Memory hook: "Leads, not the archive"Keep a short list of leads and pull files when they point somewhere; write notes, send juniors for the long reading, clear the desk between cases.
How this hook works: what “Leads, not the archive” means

The story → the concept

DetectiveAgent
The case file pinned to the wallInstructions file loaded up front
Pulling a file when a lead points to itJust-in-time retrieval
Following who called whomImport and call graphs
The notebookA notes or progress file
A junior summarising the phone recordsA subagent returning a short summary
Clearing the deskCompaction and /clear

The trap it saves you from

"The model has a million-token window, so give it everything." More context isn't better context; the clue gets buried.

7. Common mistakes

Loading the whole repositoryBig windows still lose accuracy as they fill. Retrieve what the step needs.
Keyword search onlyThe cause often lives in a file that doesn't mention the symptom. Follow imports and calls from a concrete starting point.
A bloated instructions fileHundreds of lines of rules dilute the ones that matter. Keep only what the agent can't learn from the code.
Never clearingA new task in an old session drags the last task's context along. Clear or compact between tasks.
Subagents that return everythingA helper that pastes its whole exploration back defeats the point. Ask for a short summary with file paths.
Tools that dump pagesA tool returning 20,000 tokens of JSON fills the window in two calls. Return compact, filtered results.

8. Hands-on lab (roadmap week 5)

  1. Measure: run a real bug fix and note how many tokens the session used and which files the agent read.
  2. Anchor: repeat, starting from a failing test and asking the agent to follow imports. Compare files read and tokens.
  3. Isolate: send a subagent to analyse a large log and return ten lines. Compare with reading it in the main session.
  4. Notes: on a multi-hour task, have the agent keep PROGRESS.md; compact twice; check it picks up where it left off.
  5. Trim: cut your instructions file to what the agent can't learn from the code. Did anything get worse?

9. Self-check quiz

What is just-in-time retrieval?

The agent keeps paths and queries, and reads files as its leads point to them.

Why did keyword search miss billing/money.py in the demo?

The rounding helper doesn't say "refund". Following imports from the failing test reaches it.

What should a subagent return to the main agent, per Anthropic's guidance?

It may use tens of thousands of tokens exploring; the main context gets only the distilled result.

You finish one task and start an unrelated one in the same session. What first?

The previous task's files and output are now noise competing for attention.

What belongs in the instructions file loaded at the start?

Commands, conventions and rules it can't infer; the code itself it can read when needed.

10. Go deeper

ResourceTimeWhy
Effective context engineering for AI agents (Anthropic, September 2025)20 minThe techniques in this module, from the people who built Claude Code.
Best practices for Claude Code20 minInstruction files, compaction with a focus, subagents for long output.
How we built our multi-agent research system (Anthropic, June 2025)20 minSubagents as context isolation at scale.
Don't build multi-agents (Cognition, June 2025)10 minSharing context, and compression for long tasks.
Module 02: The context window30 minContext rot and the basics of compaction.