docs: read_bundle's context cost measured with a denominator, before any change

S2c / MAJOR-3, order 20260902T151931Z-250257273. The order's rule is MEASURE
FIRST, so the numbers land as their own commit before the seam is touched.

Instrument validated against a known positive before use: it reproduces
commons' own published bundle_context fasit exactly (3 861 / 10 406 / 12 595).
Prompts are measured as text + function_call + function_result -- .text alone
undercounts a prompt whose whole payload is a tool result.

Measured, per CLI --explore run: one read_bundle result rides in FIVE
exploration prompts (navigator 1, manager 3, hypothesiser 1), which is 54-59
percent of every prompt-token in the run. The debate's three copies come from
run.py's okf.bundle_context and are a separate decision; they are in the table
as denominator and as the after-control, never as the target.

One premise felled before building on it: the tunnel base's root index body is
4 763 chars alone, nearly the whole 1 500-token ceiling, so read_bundle carries
the concept listing and not the index body -- which the catalogue already
excerpts and read_file still returns whole.
This commit is contained in:
Kjell Tore Guttormsen 2026-09-02 23:52:21 +02:00
commit ce7f687717

View file

@ -0,0 +1,97 @@
# S2c / MAJOR-3 — `read_bundle`s kontekstkostnad: målingen
**Ordre:** `20260902T151931Z-250257273-from-.claude`. Bindende ordretekst: ordreutkast M3 i
[`docs/2026-09-02-misjonsreview-v2.md`](2026-09-02-misjonsreview-v2.md) § 7.
**Regelen ordren gir: MÅL FØRST, så ÉN søm.** Denne fila er tallene med nevner — skrevet og
committet FØR noe ble endret, og utvidet med ETTER-tabellen i sømmens egen commit.
## 0. Instrumentet, og hvorfor det er troverdig
Efemert, i `/tmp/claude-s2c/` — utenfor repoet, som forrige gang (`misjonsreview-v2` § 8): et
måleinstrument i `tests/` ville vært en beslutning om å beholde det, og ordren ba om en måling,
ikke en ny produksjonsflate. Tokens telles med `tiktoken`s `o200k_base`, kjørt med
`uv run --with tiktoken` (`tiktoken` er fortsatt IKKE en prosjekt-avhengighet).
**Kjent-positiv kontroll FØR noe ble målt** (Verifiseringsloven ansikt 4 — en spørring skal bevises
å KUNNE finne): instrumentet ble kjørt mot de tre eksempelbundlene og reproduserte commons' egne
publiserte fasittall for `okf.bundle_context` **eksakt** — 3 861 / 10 406 / 12 595. Et instrument
som ikke treffer en kjent fasit måler seg selv.
**Prompten måles som tekst + `function_call` + `function_result`, ALDRI `.text` alene.** Den
skriptede klientens egen `sink` samler kun `.text`, og en prompt hvis hele nyttelast er et
verktøyresultat måles da til noen få tegn. Den fella er dokumentert i `misjonsreview-v2` vedlegg A
og er unngått her ved en egen wrapper på `ScriptedChatClient._inner_get_response`.
**Kjøringen er ekte, ikke rekonstruert:** hver rad er én `run.main([... --explore ...])` gjennom
CLI-døra, med navigatøren skriptet som `list_bundles → read_bundle → tekst` (trinn-manuset fra økt
67, MAJOR-1 b). At navigatøren faktisk ÅPNET basen er lest av kjøringens eget artefakt
(`{run_id}-exploration.json`s `tool_calls`), ikke antatt.
**Ærlighetsgrense, uttalt:** kopi-telleren er en 160-tegns skive fra MIDTEN av `read_bundle`s
nyttelast (samme sonde som `misjonsreview-v2` § 4). Skiva finnes i dag i BEGGE de to strengene
kjøringen sender — utforskningens `function_result` OG debattens `okf.bundle_context` — fordi de er
den samme strengen. Kolonnene under skiller dem derfor på ROLLE, som er det eneste som skiller
dem: `manager`/`navigator`/`hypothesiser` er utforskningen (sømmen denne ordren rører),
`proposer`/`checker` er debatten (som ordren forbyr å røre).
## 1. FØR — hva `read_bundle` koster, og hvor mange prompts det rir med i
Per base, én CLI-kjøring med `--explore`:
| Base | `read_bundle`-nyttelast | Prompts totalt | Prompt-tokens totalt | Utforsknings-kopier | Tokens i dem | Debatt-kopier | Tokens i dem |
|---|---:|---:|---:|---:|---:|---:|---:|
| `bygg-energi-mikro` | 12 005 tegn / **3 861 tok** | 15 | 35 773 | **5** | 22 013 | 3 | 11 738 |
| `veglys-fv-soer` | 32 201 tegn / **10 406 tok** | 19 | 88 881 | **5** | 54 623 | 3 | 31 376 |
| `tunnel-hauglia` | 39 583 tegn / **12 595 tok** | 19 | 106 519 | **5** | 65 698 | 3 | 37 943 |
**De fem utforsknings-kopiene, navngitt** (tunnel-tall): navigatørens egen prompt etter kallet
(12 770) · managerens tre progress-ledger-/final-prompts (13 527 / 13 549 / 13 075) ·
hypotesiserens ene tur (12 777). Ett `read_bundle`-kall gjør hele basen til et `function_result`,
og det resultatet rir med i **hver senere prompt i samme samtale** — deltakerne deler
samtalehistorikk, så ingen av dem betaler for å be om det.
**Selve nyttelasten, som andel av alt:** 5 kopier × nyttelast = 19 305 / 52 030 / **62 975** tokens,
altså **54 % / 59 % / 59 %** av ALLE prompt-tokens i kjøringen. Debattens tre kopier (11 738 /
31 376 / 37 943 = 33 % / 35 % / 36 %) kommer fra `run.py`s `okf.bundle_context` og er en EGEN
beslutning (PM) — de er med i tabellen som nevner og som ETTER-kontroll, ikke som mål.
**Nevneren for «hvor mange prompts»:** 15 / 19 / 19 prompts i alt, hvorav 8 bærer konteksten.
Tallet 5 gjelder DETTE manuset (én navigatør-tur, én hypotesiser-tur, tre manager-prompts).
`misjonsreview-v2` § 4 målte 7× på bibliotek-armen med et annet manus (to hypotesiser-turer, fire
navigatør-prompts); begge er sanne om sitt eget manus, og ingen av dem er en påstand om hva en
LEVENDE manager gjør. Multiplikatoren er en egenskap ved samtalehistorikken, ikke ved manuset:
den er antall prompts etter kallet, og den er ≥ 1 uansett.
## 2. Hva basene faktisk inneholder — premisset for formen
| Base | Filer navigert | Konseptfiler | `type: verdict` | Rot-`index.md` (body) |
|---|---:|---:|---:|---:|
| `bygg-energi-mikro` | 6 | 4 | 1 | 1 884 tegn |
| `veglys-fv-soer` | 7 | 5 | 1 | 3 646 tegn |
| `tunnel-hauglia` | 7 | 5 | 1 | 4 763 tegn |
**Ett premiss felt før noe ble bygget på det:** «basens indeks er selve navigasjonsprosaen, så den
hører hjemme i `read_bundle`». Tunnelbasens rot-indeks er alene **4 763 tegn ≈ 1 400 o200k-tokens**
— altså nesten hele ordrens tak på 1 500 for HELE kallet. Å legge den inn ville brukt opp budsjettet
på et felt katalogen alt gir et bundet utdrag av (`_CATALOGUE_EXCERPT_CHARS`), og som fortsatt er
nøyaktig ett `read_file(id, "index.md")` unna. `read_bundle` bærer derfor konseptlista og ikke
indeksbodyen.
## 3. Hva prefiks-caching ville kjøpt — notert, IKKE bygget
Ordren ber om notatet, ikke mekanismen. Regnet av prompt-prefiksene i `misjonsreview-v2` § 4
(ikke av en fakturering — leverandørens faktiske cache-treff er UMÅLT): ≈ 60 % av debattens og
≈ 40 % av utforskningens prompt-tokens er prefiks-cachebart som koden står i dag, uten én
kodeendring, fordi prefiksene allerede er byte-stabile. Caching virker på KOPIENE; et
kontekstbudsjett virker på GRUNNTALLET de multipliserer. De to utelukker ikke hverandre, og
denne ordren gjør bare det siste.
## 4. Verifiseringslogg (FØR-halvdelen)
| # | Påstand | Kommando → resultat |
|---|---|---|
| 1 | Instrumentet treffer en kjent fasit | `uv run --with tiktoken python /tmp/claude-s2c/measure.py <repo>` → 3 861 / 10 406 / 12 595 = commons' publiserte tall |
| 2 | Kjøringen ÅPNET basen (ikke bare listet den) | `measure-exploration.json`s `tool_calls``list_bundles`, `read_bundle(<base>)` |
| 3 | Prompt-størrelse inkluderer verktøyresultatet | wrapper på `_inner_get_response` serialiserer `contents` (`function_call`/`function_result`), ikke `.text` |
| 4 | Nevner | 15 / 19 / 19 prompts per kjøring; 8 bærer konteksten i alle tre |
| 5 | Indeksbodyen alene sprenger nesten hele taket | `len(navigate_bundle(tunnel).index_summary)` → 4 763 tegn ≈ 1 400 tok |