Course · Term 2 of 30
Context Window
Every lesson in this course builds one system: a stablecoin reserve attestation registry that discovers issuers, reads the PDFs they publish, and refuses to publish a field it could not read. What it is and where this lesson sits.
The constraint every later term is a workaround for. Sub-agents exist because one window is not enough. Caching exists because refilling one costs money. Progressive disclosure exists because filling it with things you did not need is how you run out. Teach the limit and half the curriculum stops looking like a list of tricks and starts looking like one problem seen from five angles.
The failure it fixes
The window is finite, so at some size your input stops fitting. That part is obvious and nobody gets hurt by it, because a hard error is a good error.
The failure that hurts is the one where it nearly fits. Something upstream trims to make room, the request succeeds, the model answers, and the answer is fluent and specific and about a document that was missing its last third. In this domain that third is often the composition table, because attestation PDFs put the summary first and the breakdown last. You publish a reserve figure with a page citation, and the citation is now a lie: the page exists, the model never saw it.
Nothing in the response tells you this happened. Truncation upstream and a confident answer downstream look exactly like a correct run. The only defence is to make the trimming step report what it removed, and to decide deliberately whether that report is a warning or a stop.
Break it first
Pack the real issuer universe into a budget that cannot hold it, and use a packer that returns only what fit:
$ npx tsx scripts/terms/demo-02.ts
domain: local-docs (a folder of text documents)
30 items, 997 tokens to send them all
budget 747 ( 75%) kept 22/30 dropped 8 lost: units/14-skills,units/17-background-agents,...
budget 498 ( 50%) kept 15/30 dropped 15 lost: units/04-projects,units/06-cli,...
budget 249 ( 25%) kept 7/30 dropped 23 lost: units/13-git-worktrees,front-matter/20-overlap-matrix,...
budget 99 ( 10%) kept 2/30 dropped 28 lost: front-matter/60-verdicts,units/02-context-window,...
The curve is 30 → 22 → 15 → 7 → 2, and it was 30 → 22 → 15 → 7 → 3 when
this ran against stablecoin issuers instead. That near-identity is worth
naming rather than glossing: with records of roughly uniform size, the number
kept is close to the budget fraction, so this curve is mostly arithmetic. It
is a weaker figure than it first appears. What makes it worth printing is not
the shape but the lost: column, which is the thing a run without it never
tells you.
Now imagine those lost: lines were not printed. You would have a run that
reported "30 issuers discovered" at the top and classified seven of them, with
nothing in between saying so. That is the shape of the bug, and it is invisible
by construction.
The mechanism
src/context.ts makes one decision, and it is not "does it fit". It is: when it
does not fit, what leaves, and does the caller find out.
pack() fills a budget by priority and returns two lists. included is what
fit, in input order, because packing decides inclusion and must not silently
reorder a document. dropped is everything else, each entry carrying a reason.
There are two reasons and the distinction matters. over-budget means the chunk
lost a competition and a bigger budget would have kept it. single-chunk-too-large
means it exceeds the whole budget and no amount of trimming elsewhere will ever
help. Collapsing those into one reason sends the caller looking for something to
delete when nothing would work.
Ties in priority break by input order, so packing is deterministic. A packer
whose output depends on Array.prototype.sort stability across engines is a
packer that will one day produce two different contexts from one input.
packOrThrow() is the default for extraction work. A reserve figure read from a
silently trimmed document is worse than no figure, because absence is honest and
a wrong number with a citation is not.
Build it
Priority here is circulating supply: if something must go, lose the small issuer. That is a policy choice, not a property of the packer, and it is recorded in the artifact rather than buried in a sort comparator where nobody will find it. Someone will disagree with it, and they should be able to find it in order to disagree.
The receipt
artifacts/02-context.json carries the full drop curve, the identities of every
dropped issuer at each budget, and the refusal message packOrThrow produces at
fifty per cent.
Ten tests. Two failed on first run, and the tests were wrong rather than the
code: they hard-coded budgets from my mental model of the estimator, and
word12 costs two tokens rather than one. Budgets are now derived from measured
cost. A test whose setup encodes a guess about the code under test fails for
reasons that have nothing to do with the behaviour it is checking, and it fails
confusingly.
Where it overlaps
Line A is context economics, and this is the constraint the whole line escalates from. Line B is instruction packaging: the prompt occupies part of every window, which is exactly the tension interstitial I is about. Line C is delegation topology, and it starts here, because the first reason to delegate is that one window is not enough. Term 11, next in build order, measures what that delegation costs.
Sharp edges
- The estimator is not the tokenizer. Every budget in this file is measured in estimated tokens, biased twenty per cent high per term 3. Packing is therefore conservative: it drops chunks that would have fit.
- Priority is not importance. Circulating supply is a proxy chosen for this demo. The largest issuer is not automatically the one whose attestation matters most, and a registry that only ever reads the top seven has a coverage problem it cannot see.
packOrThrowis right for extraction and wrong for summary. Refusing to answer at all is correct when a number will be published with a citation, and obstructive when a human just wants an overview.- A chunk that fits alone may never fit together.
single-chunk-too-largeis computed against the whole budget, not the remaining budget, so a chunk can be reportedover-budgeton one run and included on another with the same budget and different neighbours. That is correct, and it surprises people.
A personal teaching project, in development. The registry it builds is a teaching artifact and is not an assurance opinion about any issuer. Not affiliated with or endorsed by any employer, or by any vendor whose tools it describes. Where a unit depends on a specific flag or path, it names the version it was verified against. All units.