Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/C2.17.mp4The model is given the repository and asked to be thorough, so the relevant line falls out of the window.
Slice on the source-sink path, not on distance: the smallest context that still supports a severity decision.
Compare four context strategies against one bug and measure which are decidable and at what size.
Give an agent more context and it gets better, until it gets worse. The cliff is real, it arrives earlier than anyone expects, and past it you are paying more per token for a worse answer.
sast.slice_for(). The slice for get_booking carries get_my_booking as well, because the defect is the difference between them and a slice holding one of the pair cannot show it. slice_ratio() is the Day 2 number: a stage that claims to cut context and hands over 90% of the file is a stage nobody measured.Slicing context on the source-to-sink path rather than on distance, and the case that decides the rule — a defect that is the difference between two functions needs both of them in the window, and the cut is measured rather than claimed.
The model is handed the repository and asked to be thorough, so the relevant line falls out of the context window.
Slice on the source-to-sink path rather than on distance: the smallest context that still supports a severity call.
False positives before and after slicing, against tokens spent. The point is that both improve.
Measure this
| what to measure | denominator or target |
|---|---|
| false positives before and after slicing | two counts |
| tokens spent | count; both should improve together |
accuracy
^
| .-----.
| .' `.
| .' `. <- the cliff
| .' `--....
| .'
+--------------------------------> context tokens
too little enough too much
past the peak you pay more per token for a worse answerCross-cutting, and it applies to every stage that calls a model.
The instinct when a model gets something wrong is to give it more context. That is usually backwards, and the reason is easiest to see if you stop counting tokens and start counting false positives.
To decide whether code is vulnerable, a model needs exactly three things: the sink, the source, and the path between them. Give it less than that and the question is not hard — it is unanswerable, and a model asked an unanswerable question does not say so. It answers from the only thing it has, which is the shape of the line, and a confident verdict derived from the shape of a line is precisely what a false positive is.
Give it more than that and nothing improves: a repository dumped into a prompt produces a review of whatever survived truncation, and you cannot tell which parts those were.
So context engineering is mostly subtraction, with one thing you must never subtract: the enclosing signature, because that is where the source lives. The identical concatenation is critical inside an HTTP handler and irrelevant inside a migration script that takes a constant — and C2.5 has already shown you the same line being a sev 9 in one place and a deletion candidate in another.
C2.3 is the worked case, and it is worth reading as a context result rather than a model result. Given a function, its signature and the caller's authority, the model pass found a real missing-authorisation defect and quoted a line that exists. Given a slice it could not decide, the same model returned CWE-89 at 0.71 confidence quoting a concatenation that is not in the file. Same model, same prompt, different slice.
One SQL injection, four candidate slices. The skill reports for each one whether the defect is decidable in it — sink, source and path all present — how much unrelated code it carries, and what the model does with it.
The ordering to watch: the smallest slice is not the best one, and the largest is not either.
Find the smallest slice in which the defect is decidable, and only then make it smaller.
Cutting below that line does not save money. It buys false positives at a discount, and you pay for them again in triage.
The skill — skills/appsec/context-window-sizing/SKILL.md
name: context-window-sizing
description: >-
Find the smallest slice of a file in which a defect is actually decidable, and
measure what larger windows add in unrelated code. Use when tuning what a
model is shown per finding, when analysis costs too much, or when a model
keeps missing a bug that is on the line you gave it.
allowed-tools: Read, Grep, GlobContext engineering for an analysis pipeline is not "send less". It is finding the slice in which the question can be answered at all, and only then making it smaller. A ±2-line window around the bug is cheap and not decidable: it lacks the signature, so nothing in it says where the value came from.
When designing what a pipeline sends per finding, and whenever cost per finding is the constraint.
1 — Define decidability for the defect class. For injection: the sink, the value's origin, and any sanitiser between them. Write it down before slicing, or you will judge slices by how they look.
2 — Build the candidate slices. The whole file, a fixed window around the line, a wider window, and a path slice — the enclosing function plus the definitions it depends on. Measure each in characters.
3 — Mark each slice decidable or not, against step 1. Cheap and undecidable is the trap: it looks like a saving and it produces confident answers about information that is not there.
4 — Count unrelated content in the decidable ones. Functions, constants and imports the defect does not depend on. This is what a bigger window costs, in tokens and in the model's attention.
5 — Pick the smallest decidable slice, and say what it excluded. The exclusion list is what somebody re-reads when the pipeline misses something.
Input — the fixture committed at the top of scripts/context_window_sizing.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
the bug is on line 22
whole file 839 chars 29 lines
±2 line window 207 chars 5 lines
±6 line window 432 chars 13 lines
strategy sink concat source intent decidable
----------------------------------------------------------------
whole file True True True True True
±2 line window True True False False False
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"decidability": {"requires": ["str"]},
"slices": [{"name": "str", "chars": 0, "decidable": false, "unrelated_units": 0}],
"chosen": {"name": "str", "chars": 0, "unrelated_units": 0},
"excluded": ["str"]
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons C2.17Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick c2-17-context-engineering:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons C2.17 | type /c2-17-context-engineering |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons C2.17 | type /c2-17-context-engineering in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons C2.17 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons C2.17 | type $c2-17-context-engineering, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py C2.17Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at C2.17 --out work/cybertravels
python3 scripts/checkpoint.py --at C2.17 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
The whole file is roughly 840 characters, the ±2 window about 200 and the path slice about 390. The ±2 window is the cheapest and is not decidable — it has the sink and the concatenation but not the signature, so whether the value is user-controlled is unanswerable from it, and a model asked anyway answers from the shape of the line. The ±6 window and the whole file are decidable and carry unrelated functions. The path slice is the smallest decidable context, about 53% smaller than the whole file with zero unrelated functions.
Apply the path-slice rule where the source is three functions away from the sink. That is the case where text windows break down entirely and the call graph the threat model derives (C2.2) earns its keep — a ±N window can never contain a source that is in another file.
Tools used
Skill source: skills/appsec/context-window-sizing