Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Context engineering for LLM agents

After this lesson: I can choose what goes into an agent's context window under a token budget.

60 min lesson7 commandsAI coding agent lab: use opencode to explain and fix code, and check its work

Choose what the model sees

Context engineering is deciding what goes into the model's window at each step of an agent run. The window is finite, so every part of it is a choice.

An agent that keeps every file and every tool result can fill its window in a few steps. Then the instructions that matter are buried under output nobody needs.

By the end of this lesson

Note

I can choose what goes into an agent's context window under a token budget.

Key words

WordWhat it means
Context windoweverything the model reads in one call
Budgetthe most tokens, or word pieces, a window may hold
Pinneda part that is never dropped
Tool resultwhat a tool printed, sent back to the model
Memorynotes kept between runs and loaded at the start
Drop orderwhich parts go first when the window is full

The cells students fill in are left blank in this preview.

A window is made of parts

Each call sends parts in a fixed order. Instructions say who the agent is and what it may do. Memory holds lasting notes. Files are what it is working on. Tool results are what its tools printed. History is the talk so far.

Module 67 explains how memory is stored. Here the question is only how much of it fits.

What to drop first

Give the whole window a budget and drop parts until it fits. Drop the cheapest to rebuild first. An old tool result can be asked for again. A file can be read again. Memory is slower to rebuild.

Never drop the instructions. Pin the file being edited. Stop dropping as soon as the window fits, because every extra drop loses something.

A drop order for a coding agent

PartDroppedWhy
Tool resultsFirst, oldest firstThe tool can run again
FilesSecond, unless pinnedThe file can be read again
MemoryThirdNotes are slower to rebuild
HistoryLastIt holds what the user asked
InstructionsNeverWithout them the agent has no rules

The cells students fill in are left blank in this preview.

A common mistake

Warning

Many people trim from the front, dropping the oldest messages whatever they hold. That removes the instructions first. Drop by what a part is, not by how old it is.

Write the model helper, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// The token counts of a reply, or null when the reply carries none.' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the model helper, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"Recorded sample: no model answered (\" + error.message + \").\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the window builder, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const BUDGET = 60;' '// A token here is one word. A real tokenizer counts differently.' 'function size(text) { return text.split(\" \").length; }' 'const parts = [' '  { kind: \"instructions\", name: \"rules\", text: \"You are a coding helper. Edit only files you were shown. Say what you changed.\" },' '  { kind: \"memory\", name: \"notes\", text: \"The project uses tabs. Tests live in tests. Run them before a commit.\" },' '  { kind: \"file\", name: \"app.py\", pinned: true, text: \"def total(items): return sum(items) # sums the prices of a basket\" },' '];' > context1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the window builder, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'parts.push({ kind: \"tool_result\", name: \"run 1\", text: \"3 passed 1 failed test_total expected 12 got 10 in tests slash test_app\" });' 'parts.push({ kind: \"tool_result\", name: \"run 2\", text: \"4 passed 0 failed\" });' 'parts.push({ kind: \"file\", name: \"readme\", text: \"A tiny shop script. It prices baskets and prints a receipt for each customer with tax added at the end.\" });' 'const DROP = [\"tool_result\", \"file\", \"memory\"];' 'function total(list) {' '  let sum = 0;' '  for (const p of list) sum = sum + size(p.text);' '  return sum;' '}' >> context1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the window builder, part 3

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function assemble(all, budget) {' '  const kept = all.slice();' '  const dropped = [];' '  for (const kind of DROP) {' '    for (const p of all) {' '      if (total(kept) <= budget) break;' '      if (p.kind === kind && p.pinned === undefined) {' '        kept.splice(kept.indexOf(p), 1);' '        dropped.push(p.name);' '      }' '    }' '  }' '  return { kept: kept, dropped: dropped, used: total(kept) };' '}' >> context1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the window builder, part 4

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const w = assemble(parts, BUDGET);' '  console.log(\"budget \" + BUDGET + \", used \" + w.used);' '  for (const p of parts) {' '    console.log((w.kept.includes(p) ? \"kept    \" : \"dropped \") + p.kind + \" \" + p.name + \" \" + size(p.text));' '  }' '  const body = w.kept.slice(1).map(function (p) { return p.name + \": \" + p.text; }).join(\" | \");' '  const question = body + \" | Which file do you edit first? One sentence.\";' '  const messages = [{ role: \"system\", content: w.kept[0].text }, { role: \"user\", content: question }];' '  const reply = await ask(messages, \"I edit app.py first, because the failing test is about total.\", 40);' '  console.log(\"model: \" + reply.text);' '}' 'main();' >> context1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run and see what was dropped

Command
docker exec -w /home/node/project agent node context1.js

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Words stand in for tokens

A real window is counted in tokens by the model's tokenizer. Here one word is one token, so every student prints the same sizes. The idea is the same: add up the parts and compare with a limit.

The Run makes one short call with what was kept. With no model it prints a Recorded sample.

One possible output

LineWhat it shows
budget 60, used 59The window now fits
kept instructions rules 15Instructions are never dropped
dropped tool_result run 1 13The oldest tool result goes first
dropped tool_result run 2 4Dropping stops once it fits
kept file readme 20No file was dropped, so the readme stays
model: ...One sentence. The words differ each run

The cells students fill in are left blank in this preview.

About this lab and its source: AI coding agent lab: use opencode to explain and fix code, and check its work

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.