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
| Word | What it means |
|---|---|
| Context window | everything the model reads in one call |
| Budget | the most tokens, or word pieces, a window may hold |
| Pinned | a part that is never dropped |
| Tool result | what a tool printed, sent back to the model |
| Memory | notes kept between runs and loaded at the start |
| Drop order | which 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
| Part | Dropped | Why |
|---|---|---|
| Tool results | First, oldest first | The tool can run again |
| Files | Second, unless pinned | The file can be read again |
| Memory | Third | Notes are slower to rebuild |
| History | Last | It holds what the user asked |
| Instructions | Never | Without 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
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
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
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
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
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
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
docker exec -w /home/node/project agent node context1.jsShown 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
| Line | What it shows |
|---|---|
| budget 60, used 59 | The window now fits |
| kept instructions rules 15 | Instructions are never dropped |
| dropped tool_result run 1 13 | The oldest tool result goes first |
| dropped tool_result run 2 4 | Dropping stops once it fits |
| kept file readme 20 | No 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.