docs: misjonsreview v2 mot bruksscenarioet — målt, ikke bygget (S2, ORDRE 20260902T113744Z-1245330375)

M1–M6 med kommando, tall og nevner per punkt. 1 BLOCKER (plan-review viser
`<Message object at 0x…>` på alle tre HITL-flater; fire asserts grønne på
`plan != ""`), 4 MAJOR (vakuøs offline-generalprøve uten tool_calls-rad;
ingen in-run-dør for ekspert-feedback; bundle-kontekst re-sendt 3×/7× =
73–93 % av prompt-tokens; K2 stopper på håndskrevet validator-input.json),
4 MINOR, 3 NICE. Ordreutkast per MAJOR+. Ingen src/tests rørt; golden
byte-uendret; F15-diffen i treet sett og ikke rørt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-02 17:17:39 +02:00
commit e2d26c50ed

View file

@ -0,0 +1,489 @@
# Misjonsreview v2 — bruksscenarioet målt, ikke bygget (2026-09-02)
**Ordre:** `20260902T113744Z-1245330375-from-.claude` (PM-plan § 4 / S2). **Økt 72.**
**Reviewer:** Fable 5.1 (high, uten advisor) — derfor kommer hvert tall under fra en kommando som
står i § 8, og ingen tall er sitert fra `STATE.md`. **Mandat: MÅL, IKKE BYGG.** Ingen fil under
`src/` eller `tests/` er endret av denne økten (`git status --short` før og etter: identisk — kun
den ucommittede F15-diffen `tests/test_maf_version_guard.py` 28+/15, som ordren ba meg se og ikke
røre, pluss to fremmede utrackede stier).
**Baseline:** [syretesten 25.08](2026-08-25-syretest-vei-ab.md) ·
[katalogkostnaden 26.08](2026-08-26-katalogkostnaden.md) ·
[misjonsreview 25.08](2026-08-25-fable-misjonsreview.md). Golden `demo-transcript.stdout`
**byte-uendret** (`ea8c534773acdbe41ae68f2c55724d69aaf8be4f`, målt `shasum` ved øktstart).
Suiten: **1085 tester samlet** (`pytest --co`), 120 testfiler.
**Bruksscenarioet (operatørens ord 02.09):** «basert på et helt sett av prosjektdokumenter + OKF
bundles + MCP servere og en konkret oppgave skal portfolio-optimiser gjennom dialog og lengre
analyser komme fram til forslag til besparelser i prosjektet, og med tilbakemeldinger komme med
forbedrede forslag. Det er også viktig at portfolio-optimiser er så effektiv som mulig i forhold
til bruken av tokens.»
**Eksponerings-grense (arvet, holdt):** repoet pusher til `open/`. Rapporten bærer tall, stier,
kommandoer og egne probestrenger. Ingen bundle-innhold er gjengitt; fra produsentens golden-bundle
siteres kun frontmatter-NØKLER.
---
## 0. Sammendrag — én linje per målepunkt
| # | Spørsmål | Svar | Kjernetall |
|---|---|---|---|
| M1 | Åpner navigatøren en base etter `444fea7`? | **Fra CLI-døra: nei, og det KAN den ikke** (konstant tekst per rolle kan ikke emittere et verktøykall). **Fra biblioteket: ja, alle fire verktøy, på alle fire baser.** | CLI: 0 verktøykall, 0 approaches, 1 runde × 4 baser. Bibliotek: `list_bundles`/`read_bundle`/`read_file`/`quick_validate` 1/1/1/1 × 4 baser, 1 rutet approach hver |
| M2 | Blir FORSLAGET bedre av `--plan-review revise`, eller bare planen? | **Bare planen — og planen operatøren viser er ULESELIG:** `<agent_framework._types.Message object at 0x…>` på terminalen, i `{run_id}-exploration.json` og i den parkerte `{run_id}-plan-review.json` | Feedback nådde 5 manager-prompts, 0 hypotesiser-prompts; mandat identisk i begge armer; 3 av 4 CLI-artefakter byte-identiske |
| M3 | Hvor lander feedback på et LEVERT forslag? | **I NESTE kjøring** (innboks → `VerdictStore` → hypotese-prompt), bevist med kontroll. **Ingen vei til «forbedret forslag i samme kjøring» for et menneske** — kun validatorens egen avvisning sløyfes tilbake | Run B: ekspertteksten i proposerens prompt; kontroll uten innboks: 0. In-run: 1 refinement, 4 proposer-kall |
| M4 | Tokenprofil per fase | **Bundle-konteksten re-sendes 3× i debatten og 7× i utforskningen = 7393 % av alle prompt-tokens.** Ledgeren offline er 8 tok/kall (skriptet) — ekte usage finnes kun fra 14.08 (15 306) | Tunnel: debatt 40 605 tok (93 % kontekst), utforskning 103 649 tok (85 %); tre største poster navngitt i § 4 |
| M5 | Klar for K2? | **Nei — og det største hullet er ikke `adjudication`:** ingen kode lager `validator-input.json`/`cost-baseline.json` fra et ingestert korpus, og uten dem nekter pipelinen | Produsentens segmenterte v0.2-golden navigeres (3 filer, 0 hopp), `adjudication` leses som streng, 0 konsumenter i `src/` |
| M6 | Vises MCP-kallet i provenance? Egress-linjen? | **Ja, begge — målt mot en EKTE stdio-subprosess** (første gang på kjørestien; B4-testen bruker en stand-in) | `Contacts: prisregister (lookup_unit_price)` · `external_calls=[{prisregister, lookup_unit_price}]` i resultat OG outbox · serverens svar i neste prompt |
**Rangert:** 1 BLOCKER · 4 MAJOR · 4 MINOR · 3 NICE (§ 7). BLOCKER-en er ny og felles for F4 (økt 63)
og U12 (økt 64): begge dørene stiller mennesket et spørsmål det ikke kan lese, og de fire assertene som pinner planteksten (tre testfiler) er
grønne fordi de bare krever `plan != ""`.
---
## 1. M1 — `--explore` etter `444fea7`
**Oppsett.** Fire baser: `shared/examples/{bygg-energi-mikro, veglys-fv-soer, tunnel-hauglia}` +
`~/repos/vegnormal-okf/build/B-n200-2024-gren-1-1-importert` (grenbasen katalogmålingen 26.08 brukte;
10 filer). Kontrakt: `max_rounds=6, max_tokens=100000, max_stall_count=2, max_reset_count=1,
max_plan_revisions=0, enable_plan_review=false`. Instrumentet (§ 8, vedlegg A) teller hvert
verktøykall ved å wrappe `FunctionTool.func` ETTER konstruksjon — verktøylisten agentene får er
uendret — og hvert modellkall per rolle.
**Arm A — operatørdøra** (`run.main` med `--explore --explore-config --scripted-replies`, én konstant
streng per rolle, som er den ENESTE offline-formen CLI-en tilbyr):
| Base | rc | Modellkall (manager/proposer/checker) | Verktøykall | Artefakt: runder / `quick_validations` / `tokens_spent` | Notis |
|---|---:|---|---:|---|---|
| bygg-energi-mikro | 0 | 8 (4/3/1) | **0** | 1 / 0 / 32 | «concluded after 1 round(s); **0 approach(es)**» |
| veglys-fv-soer | 0 | 8 (4/3/1) | **0** | 1 / 0 / 32 | samme |
| tunnel-hauglia | 0 | 8 (4/3/1) | **0** | 1 / 0 / 32 | samme |
| B-n200-2024-gren-1-1 (PROJECT_ID=PROBE) | 1 | 4 (4/0/0) | **0** | 1 / 0 / 32 | utforskningen FULLFØRER, så `run refused: IR projection not found in bundle: 'validator-input.json'`; `m1a-exploration.json` skrevet fra `finally` ✅ |
To ting er målt her, og de er ulike: (i) `444fea7` lukket krasjen — rc er 0/1 med ren nekt, ingen
traceback (4/4); (ii) sløyfa er **vakuøs ved konstruksjon** på denne døra: en `ScriptedChatClient`
returnerer tekst og kan aldri emittere et `function_call`, så navigatøren KAN ikke åpne en base, og
manageren — som svarer én konstant ledger — sier seg ferdig etter én runde. Dette er nøyaktig det
25.08-rapporten forutså og `444fea7`s commit-melding selv sa («fortsatt delvis vakuøs»). Re-målt: sant.
**Artefaktet kan ikke svare på M1s spørsmål.** `{run_id}-exploration.json` har nøklene `completed,
plan_reviews, quick_validations, rounds, run_id, stop, tokens_spent` — navigatørens tre verktøy har
ingen sink og etterlater ingen rad. Bare `quick_validate` spores (`ExplorationTrace.quick_validations`,
`explore.py:277`). «Åpnet navigatøren en base?» er i dag ubesvarbart fra artefaktet, uansett klient.
**Arm B — bibliotekdøra med en klient som EMITTERER verktøykall** (`RecordingClient`, vedlegg A;
navigatør-manus: `list_bundles``read_bundle(id)``read_file(id, "index.md")` → tekst;
hypotesiser-manus: `quick_validate(id, IR-json)``HYPOTHESIS:`-linje; manager: stadienøklet med
tre ledgere navigator→hypothesiser→satisfied):
| Base | Verktøykall (`list_bundles`/`read_bundle`/`read_file`/`quick_validate`) | Modellkall (mgr/nav/hyp) | Runder | Approaches (label, `bundle_id`) | `quick_validate`-dom | Navigatørens prompt-tokens per kall |
|---|---|---|---:|---|---|---|
| bygg | **1/1/1/1** | 12 (6/4/2) | 3 | 1 (`probe-direction-B`, `bygg-energi-mikro`) | validated, **anchored=false**, P10/P50/P90 68 543 / 95 444 / 121 057 | 3 → 144 → 4 031 → 4 783 |
| veglys | **1/1/1/1** | 12 (6/4/2) | 3 | 1 (…, `veglys-fv-soer`) | validated, anchored=true | 3 → 122 → 10 553 → 11 869 |
| tunnel | **1/1/1/1** | 12 (6/4/2) | 3 | 1 (…, `tunnel-hauglia`) | validated, anchored=true | 3 → 148 → 12 767 → 14 429 |
| B-n200-gren-1-1 | **1/1/1/1** | 12 (6/4/2) | 3 | 1 (…, `B-n200-2024-gren-1-1-importert`) | validated, anchored=false, **P10 = P50 = P90 = 300** | 3 → 137 → 2 476 → 3 050 |
Katalogresultatet (`index_excerpt`) står i navigatørens NESTE prompt i 4/4 baser (målt på
innholdet, ikke på `.text`). Sømmen bærer altså et verktøykall ende til ende — også mot en
regelverksbase uten prosjekt. **Det er første gang dette er målt på utforskningsstien;** 25.08 og
økt 56 kunne bare kalle verktøykroppene direkte.
**MINOR-funn på veien:** `quick_validate` sier `validated` med P10 = P50 = P90 når kandidaten mangler
`assumptions` — alle 512 MC-samples identiske, den stokastiske falsifisereren er inert, og dommen
flagger det ikke. Det er samme defektklasse CLAUDE.md beskriver for `response_format`-skjemaet, nå på
det rådgivende verktøyet (kandidaten var min egen probe uten bånd, men en levende hypotesiser vil
også utelate bånd).
## 2. M2 — `--plan-review revise`: planen eller forslaget?
**Bibliotek-armen med DISKRIMINATOR.** Skriptet manager svarer én plan uten feedback og en ANNEN plan
når feedback-nøkkelen `NIGHT-SETBACK-7731` står i prompten; hypotesiseren svarer `night-setback` når
nøkkelen står i DENS prompt, ellers `led-retrofit`. Da er mandatet forskjellig hvis og bare hvis
feedbacken nådde hypotesiseren.
| Arm | Reviews (index → beslutning) | Manager-kall | Feedback-nøkkel i prompts (rolle: antall) | Hypotesiserens prompt | Mandat |
|---|---|---:|---|---|---|
| approve | 0 → approve | 5 | — | 19 tegn (`shape one direction`) + 375 tegn instruksjoner | `[led-retrofit]` |
| revise, approve | 0 → revise (feedback ordrett), **1 → approve** | 7 | **manager: 5** (replan ×2, ledger ×2, final) · **hypothesiser: 0** | 19 tegn | `[led-retrofit]` |
Manageren replanla (2 ekstra kall, indeks 1 finnes — F4s diskriminator holder). Men **planteksten
kringkastes aldri til deltakerne**: hypotesiseren får KUN managerens `instruction_or_question`
(19 tegn), så den eneste veien fra feedback til forslag går gjennom en LEVENDE managers neste
instruksjon. Offline kan det hoppet ikke måles — det er en ærlighetsgrense, ikke en defekt. Det som
ER målt: med skriptet manager er mandatet identisk, altså forslaget identisk.
**CLI-armen (ekte stdin, ekte artefakter, `--outbox-dir`):** begge rc 0.
| Artefakt | approve vs revise |
|---|---|
| `m2-runconfig.json` | identisk |
| `m2-own-proposal-proposal.json` | identisk |
| `m2-own-proposal-outcome.json` | identisk |
| `m2-exploration.json` | **forskjellig** — kun `plan_reviews` |
**BLOCKER-1 — planen mennesket skal godkjenne er en objekt-repr.** Terminalen skrev:
```
PLAN REVIEW #1
--- the plan the exploration would run ---
<agent_framework._types.Message object at 0x10ff788c0>
--- progress so far ---
None
Answer "approve" to sign it off, or "revise <what to change>":
```
Samme streng står i `m2-exploration.json``plan_reviews[*].plan`, og i den PARKERTE
`park1-plan-review.json``plan` (U12-døra, målt med `--checkpoint-dir`): eksperten som skal svare
dager senere får `'<agent_framework._types.Message object at 0x10725fd40>'`.
**Rotårsak, lest i kilden:** `MagenticPlanReviewRequest.plan` er typet `Message`
(`_magentic.py:846`); `explore.py:1395/1404/1418/1478` gjør `str(review.plan)`, og `Message` har
ingen `__str__` (målt: `type(m).__str__ is object.__str__``True`; `m.text` gir planteksten).
Hjelperen som gjør det riktig finnes alt i samme fil: `_plan_text` (`explore.py:966`), brukt for
span-eventene. **Hvorfor gaten er grønn (fire asserts, tre filer):** `test_plan_review_cli_door_loadbearing.py:161`
asserterer `plan != ""`, `:180` asserterer `reviews[0]["plan"][:40] in out` — repr-strengen står
jo i stdout; `test_async_plan_review_loadbearing.py:408` asserterer truthiness;
`test_explore_loadbearing.py:564` `!= ""`. Repoets vakuøs-gate-klasse, **tolvte gang**, og denne
gangen på begge HITL-dørene samtidig. En planreview ingen kan lese er en review ingen kan svare på;
F4s og U12s målbilde «be om svar, bruke svarene» er dermed ikke nåbart for et menneske i dag.
## 3. M3 — feedback på et LEVERT forslag
Kommandoene er `run_project`-kall med instrumentet (vedlegg A); alle fire skritt på
`bygg-energi-mikro`.
| Skritt | Kommando/handling | Målt |
|---|---|---|
| 1. Kjøring A uten ekspert | `run_project(..., outbox_dir=…, run_id="m3a")`, ingen `verdict_input` | `result.verdict is None` ✅ · `verdict_key = ee44c58e0c0d258a` · `m3a-outcome.json.verdict_id = ee44c58e0c0d258a` · proposer 3 kall, checker 1 |
| 2. Eksperten svarer | `capture_verdict(_features_of(outcome.proposal), "rejected", "EKSPERT-MARKOR-7731: …")``write_verdict(inbox, v)` | `v.id == verdict_key` ✅ → fila `ee44c58e0c0d258a.json` |
| 3. Kjøring B, FERSK store | `run_project(..., verdict_dir=inbox, store=VerdictStore([]))` | markøren står i **proposerens** prompt (3 kall) · checker: nei |
| 3b. Kontroll | samme uten `verdict_dir` | markør i **0** prompts |
| 4. In-run maskin-feedback | proposer overdriver (270 000) til «REJECTED by the deterministic validator» står i prompten | `refinements = ["claimed saving 270000 exceeds P90 feasible 121057"]`, proposer **4** kall, utfall `ValidatedProposal` |
**Hvor den lander i dag:** `run.py:644-647` (`load_verdicts_from_dir``store.add`) FØR
Steg-1-folden → `generate._build_messages(context=…)` i NESTE kjøring. Outboxen bærer nøkkelen
(`RunResult.verdict_key`, F2) så en ukommentert kjøring er dømbar — bevist over.
**Finnes en vei til «forbedret forslag i SAMME kjøring»?** For maskinen: ja — validatorens
`Rejection.reason``_build_messages(prior_rejection=…)` (`generate.py:320-325`), bundet av
`max_attempts=3` + `meter.tick_round`. For et MENNESKE: **nei.** `grep -n "prior_" generate.py` gir
kun `prior_rejection`; checker-kritikken sløyfes ikke tilbake (uttalt grense siden Steg 5); eneste
menneske-søm i sløyfa er `plan_reviewer`, som fyrer FØR hypotesene finnes; hosting
`_response_payload` (`hosting.py:195-203`) legger ut `verdict_id` og stopper. Bruksscenarioets «med
tilbakemeldinger komme med forbedrede forslag» er derfor en to-kjørings-sløyfe med dager mellom,
aldri en dialog om det forslaget som ligger på bordet. Minste søm og vokter: MAJOR-2 (§ 7).
## 4. M4 — tokenprofil per fase
**Instrument, med ærlighetsgrense først.** `BudgetMiddleware` leser `usage_details.total_token_count`
(`budget.py:246`), og den skriptede klienten rapporterer **8 tokens per svar** (`tokens_per_reply=8`)
— ledger-summene under (5696) er derfor KALLTELLINGER × 8, ikke forbruk. Eneste ekte usage repoet
har er den levende kjøringen 14.08: **15 306 tokens** (`docs/2026-08-14-…:221`), tolv runder med
parse-feil, ingen utforskning. Profilen under er derfor **o200k_base-tokens av alt modellen VILLE
fått sendt** (tekst + `function_call` + `function_result`), målt per kall med instrumentet fra § 8 —
samme instrument som reproduserte commons' fasittall 25.08 (3 861 / 12 595 / 10 406 for de tre
eksempelbundlene; `bundle_context` re-målt i dag til nøyaktig de tallene).
**Debatt + generering (CLI-armen, M1 A):**
| Base | Sum prompt-tok | debatt(proposer) ×2 | debatt(checker) ×1 | utforskning(manager) ×4 | generering ×1 | Kontekst re-sendt |
|---|---:|---:|---:|---:|---:|---|
| bygg (ctx 3 861) | 14 283 | 7 868 (55 %) | 3 986 (28 %) | 2 243 (16 %) | 186 (1 %) | 3× = **81 %** |
| veglys (ctx 10 406) | 34 001 | 20 984 (62 %) | 10 556 (31 %) | 2 243 (7 %) | 218 (1 %) | 3× = **92 %** |
| tunnel (ctx 12 595) | 40 605 | 25 376 (62 %) | 12 759 (31 %) | 2 243 (6 %) | 227 (1 %) | 3× = **93 %** |
**Utforskning med verktøykall (bibliotek-armen, M1 B):**
| Base | Sum prompt-tok | manager-ledger ×3 | hypothesiser ×2 | navigator ×4 | manager-final ×1 | plan+facts ×2 | Kontekst re-sendt |
|---|---:|---:|---:|---:|---:|---:|---|
| bygg | 36 919 | 12 064 (33 %) | 9 786 (27 %) | 8 961 (24 %) | 5 292 (14 %) | 816 (2 %) | 7× = **73 %** |
| veglys | 86 013 | 26 262 (31 %) | 23 984 (28 %) | 22 547 (26 %) | 12 404 (14 %) | 816 (1 %) | 7× = **85 %** |
| tunnel | 103 649 | 31 394 (30 %) | 29 116 (28 %) | 27 347 (26 %) | 14 976 (14 %) | 816 (1 %) | 7× = **85 %** |
| B-n200-gren (ctx 2 307) | 24 761 | 8 532 (34 %) | 6 254 (25 %) | 5 666 (23 %) | 3 493 (14 %) | 816 (3 %) | 7× = **65 %** |
«Kontekst re-sendt» = antall prompts som inneholder en 160-tegns skive fra midten av
`okf.bundle_context(base)` × kontekstens tokens, delt på summen. Én `read_bundle` gjør hele
konteksten til et `function_result`, og det resultatet rir med i **hver** senere prompt: navigatørens
egne (2), begge hypotesiserens (2, fordi de deler samtalehistorikk), og alle managerens
ledger-/final-prompts (3). Hele utforskningen koster **8,2 × kontekststørrelsen** (tunnel:
103 649 / 12 595), debatten **3,2 ×**.
**De tre største postene, navngitt:**
1. **Debattens tre turer bærer hele `bundle_context` hver** — 8193 % av en CLI-kjørings
prompt-tokens (proposer ×2 + checker ×1). Kilde: `workflow.py` GroupChat, konteksten i
deltakernes melding, ikke i `instructions` (målt `instructions_chars` 61/307).
2. **Managerens progress-ledger-prompts bærer hele samtalen inkl. verktøyresultater** — 3 × 5 766
(bygg) / 3 × 15 450 (tunnel) tokens, 3034 % av utforskningen. MAF-Magentic-atferd
(`_magentic.py`), ikke vår kode.
3. **Hypotesiseren arver navigatørens verktøyresultater** — 2 × 4 795 / 2 × 14 441 tokens, 2528 %.
**Hva caching og kontekstbudsjett ville kjøpt — regnet fra prompt-prefiksene, ikke antatt:**
| Par | Felles prefiks (tok) | Av | Lesning |
|---|---:|---|---|
| debatt proposer #1 vs #2 (bygg / tunnel) | **3 877 / 12 612** | 3 877 / 12 612 | hele første tur er et re-sendt prefiks (100 %) |
| debatt proposer #1 vs checker | 3 877 / 12 612 | 3 986 / 12 759 | 9799 % delt |
| manager ledger #1 vs #2 | 215 | 751 / 5 547 (bygg) | før første verktøyresultat deles nesten ingenting |
| manager ledger #2 vs #3 (bygg / tunnel) | **5 010 / 14 656** | 5 766 / 15 450 | 8795 % delt |
| hypothesiser #1 vs #2 | 4 795 / 14 441 | 9 786 / 29 116 | 4950 % |
Anslag, per base tunnel: **leverandør-prefiks-caching** (uendret kode, bare byte-stabile prefikser,
som allerede er tilfellet) kunne treffe ≈ 2 av 3 kontekstkopier i debatten (≈ 25 200 av 40 605 =
62 %) og ≈ 2 av 3 ledger-kopier i utforskningen (≈ 29 300 av 103 649 = 28 %); hypotesiserens
andre kall ≈ 14 400 (14 %). Samlet ≈ **40 % av utforskningen og ≈ 60 % av debatten** er
prefiks-cachebart i dag. **Kontekstbudsjett** virker på multiplikatorens grunntall: et tak på
`read_bundle` av samme form som katalogtaket (`_CATALOGUE_EXCERPT_CHARS`) — f.eks. konseptliste +
`read_file` per konsept i stedet for hele `bundle_context` — kutter alle 7 kopiene samtidig; ved
tunnel er hver kopi 12 595 tokens. **Ikke målt her:** hva en levende manager faktisk kaller og hvor
mange runder den bruker; tallene over er én navigatør-tur + én hypotesiser-tur.
## 5. M5 — klar for K2?
**Leseprøve mot produsentens nyeste form** (`~/repos/llm-ingestion-okf/examples/
ingest-golden-segmented-okf-v0-2/expected-bundle`, den eneste committede bundelen med
`adjudication`-nøkkelen; produsent-HEAD `62b6192`, tag `v0.5.0a2`+104). Konsumentens funksjoner,
kalt direkte:
| Funksjon | Resultat | Lesning |
|---|---|---|
| `okf.navigate_bundle` | 3 kontekstfiler, 0 verdicts, **0 hopp**; nestede `krav/index.md` og `krav/1-1/index.md` fulgt | Navigasjonen tåler den segmenterte formen ✅ |
| `okf.parse_frontmatter(konsept)` | nøkler `adjudication, bundle_id, generated, ingested_at, parent, segment_id, source_file, source_offset, source_sha256, title, type`; `adjudication → 'proposed'`, `source_offset → '[94, 176]'` (streng) | Nøkkelen er LESBAR som streng; ingen tolker den |
| `okf.parse_frontmatter(index.md)` | `{okf_version: 0.2, bundle_id: b-golden-segmented-okf-v0-2}` | Rot-`bundle_id` er der; konsumenten bruker basenavnet (`explore.py:666`, `run.py:1512`) |
| `okf.bundle_context` | 132 tokens; `adjudication: proposed` forekommer **1** gang — som index-LABEL-prosa | Tilstanden lekker inn som tekst, ikke som tilstand |
| `explore.list_bundles` | `{"id": "expected-bundle", …, "cost_baseline": false, "documents": 3}` | Katalogen fungerer ✅ |
| `okf.load_ir_projection` | **`FileNotFoundError: IR projection not found in bundle: 'validator-input.json'`** | **Pipelinen nekter** (samme nekt som M1 A på grenbasen) |
| `verdicts.seed_store_from_bundle` | 0 | — |
| `grep -rn adjudication src/ tests/` | **0 treff** (kjent-positiv `bundle_id`: 71) | Ingen konsument |
| `grep -rn concept_id src/ tests/` | **0 treff** | Ingen konsept-identitet i konsumenten |
**Hva som mangler for «hele veien», konkret:**
| # | Gap | Fil/funksjon i dag | Eier / status |
|---|---|---|---|
| 1 | **IR-projeksjon + kostbaseline fra et ingestert korpus.** `validator-input.json` er håndskrevet per prosjekt (README:146 «one file the ingest layer does not write for you»; `docs/kunnskapsbase-for-en-kjoring.md:84`). K2 er anbudsdokumenter (PDF/DOCX/XLSX) — ingen kode leser kostlinjer ut av dem | `run._project_from_bundle` (`run.py:437`) → `okf.load_ir_projection` (`okf.py:433`) fail-fast; `okf.load_optional_cost_baseline` (`:411`) | **Ingen eier, ingen ordre.** Det tyngste enkelthullet — uten det stopper syretesten der M1 A stoppet på grenbasen |
| 2 | **`adjudication`-konsument** (PM B2: fravær = `unknown`, aldri `absent`) | ingen; nærmeste form er B4 Steg 7 `evidence_for(path, key="verified")` med `EvidenceState` | **Bestilt** i S3 amendment B (Steg 7b), K4c-vokter `tests/test_falsification_verdict_loadbearing.py`. Ikke bygget |
| 3 | **(`bundle_id`, `concept_id`)-identitet** | `explore._bundle_index` + `run.py:1512` (basename, to kopier); `mandate.Approach.bundle_id` default `""`; `verdicts._mint_id` (`:86`) korpus-blind; `VerdictStore.add` (`:305`) first-write-wins stille | **Bestilt** S3 amendment D / B4 Steg 910 (`reconcile_bundle_id`, `collisions`). `concept_id` finnes ikke som begrep i konsumenten — S3 B1 gir den, men ingen kode i dag nøkler en dom på et konsept |
| 4 | **Flerkilde-`sources` + K5-terskel** | `parse_frontmatter` holder lister verbatim som streng | B4 Steg 27 / S3 amendments A + C |
| 5 | **Ny profil `SEGMENTED_OKF_V0_2`** | installert `llm_ingestion_okf` **v0.3.2** har moduler `connectors, errors, manifest, materialize, render`**ingen `profiles.py`**; profilen finnes kun i produsent-HEAD | Gjelder KUN Door A (`ingest.materialize`) i dette repoet; LESING trenger ingen pin. STATE (økt 70): «IKKE bump `llm-ingestion-okf`-pinnen» — står ikke i veien for K2-lesing |
| 6 | **Office → tekst** | — | Produsent (S1), utenfor dette repoet |
**Konklusjon M5:** når S1 leverer et K2-bundle, kan portfolio-optimiser **navigere det og
katalogisere det** i dag, men **ikke kjøre pipelinen** — og hindringen er ikke `adjudication`, det er
at ingen lager prosjektets tallfiler. Det er et scenario-hull ingen av de tre planene (PM, B4, S3)
navngir med eier.
## 6. M6 — MCP under kjøring, mot en ekte stdio-server
**Server:** `/tmp/claude-s2/mcp_stub.py``FastMCP("prisregister")`, ett verktøy
`lookup_unit_price(code) -> "unit price for {code}: 1.0 NOK/kWh (STUB-SERVER-7731)"`. Konfig:
`{"servers":[{"name":"prisregister","transport":"stdio","command":<.venv python>,"args":[stub],
"allowed_tools":["lookup_unit_price"],"timeout_seconds":30}]}`.
| Arm | Kommando | Egress-linje (stdout) | `provenance.external_calls` | Serverens svar i neste prompt |
|---|---|---|---|---|
| A — CLI, tekst-agenter | `python -m portfolio_optimiser.run BYGG-KONTOR-NORD … --mcp-config mcp.json --scripted-replies … --outbox-dir … --run-id m6a` (subprosess) | **`Contacts: prisregister (lookup_unit_price)`** ✅, rc 0 | `m6a-proposal.json`: **`[]`** — agentene kalte aldri (kan ikke, tekst) | — |
| B — bibliotek, proposer som KALLER | `run_project(..., mcp_servers=load_mcp_config(cfg), outbox_dir=…, run_id="m6b")` med `RecordingClient`-manus `[call lookup_unit_price(code=ENERGI-TOTAL-EL), IR-json]` | (ikke CLI) | **`[{"server": "prisregister", "tool": "lookup_unit_price"}]`** i `result.provenance` OG i `m6b-proposal.json` ✅ | **ja** (`STUB-SERVER-7731` i proposerens 2. prompt); serverloggen viser `CallToolRequest` |
Dette er første måling av kjørestien (`run_project``build_mcp_tools``MCPStdioTool`
`AsyncExitStack``ToolCallRecorder`) mot en **ekte subprosess**; `test_b4_mcp_call_trace_loadbearing.py`
bruker en in-process stand-in med vilje, og `test_ingest_golden_mcp.py` måler INGEST-transporten, ikke
denne. Attribusjonen (`server="prisregister"`) kom fra vår egen konfig-indeks, som designet.
**To MINOR-funn i samme kjøring:** (a) genereringskallet (`generate.py:460`
`chat_client.get_response(messages, options={"response_format": …})`) går utenom `Agent` og har
**ingen verktøy** — et `function_call`-svar der blir en parse-feil, så prisregisteret kan brukes i
debatten men ikke i steget som skriver forslags-JSON-en (indirekte via debatt-outputen som kontekst);
(b) `m6b-parse-failures.json` fanget det svaret som `"text": ""` — «verbatim»-fangsten mister et
svar som var et funksjonskall, altså nøyaktig beviset den finnes for.
**Instrument-lærdom, uttalt:** første forsøk på arm A krasjet med `io.UnsupportedOperation: fileno`
fordi jeg fanget stderr in-process (`redirect_stderr`) og `stdio_client` gir barnet `sys.stderr`. Det
er min instrumentfeil, ikke repoets — men det betyr at en `capsys`-basert test av denne stien ville
feilet på samme måte; subprosessen er målingen (P4-presedensen, igjen).
---
## 7. Funn, rangert
### BLOCKER
**BLOCKER-1 — Planreviewen viser et menneske `<Message object at 0x…>` i stedet for planen, på
alle tre flater.** `explore.py:1395/1404/1418/1478` `str(review.plan)` på en MAF `Message` uten
`__str__`. Terminal (F4), `{run_id}-exploration.json.plan_reviews[*].plan`, og den parkerte
`{run_id}-plan-review.json.plan` (U12). Fire asserts i tre testfiler grønne på `!= ""`/truthiness/`[:40] in out`.
Konsekvens: begge HITL-dørene er uøvbare for et menneske; en ekspert som svarer `approve` på en
repr signerer blindt. Beviset ligger i § 2 og i `/tmp/claude-s2/out/m2/result.json` +
`m2-park/outbox/park1-plan-review.json` (efemert; kommandoene i § 8 gjenskaper det).
**Ordreutkast B1 (Opus 5 / high, én økt, Iron Law):**
> Oppgave: planteksten som vises og lagres i plan-reviewen skal være `Message.text`, aldri
> `str(Message)`. Bruk den eksisterende `_plan_text` (`explore.py:966`) på alle fire stedene; ingen
> ny hjelper. **Test først, RØD:** i `tests/test_plan_review_cli_door_loadbearing.py` skjerpes T2 til
> å asserte at den skriptede managerens plantekst (en unik streng i `_REPLIES["manager"]`-planen —
> scenariet må gi manageren et stadienøklet svar slik `simulation._exploration_manager_reply` gjør,
> ellers finnes ingen plantekst å finne) står i stdout OG i artefaktets `plan`, og at strengen
> `"Message object at"` IKKE står noe sted; i `tests/test_async_plan_review_loadbearing.py` samme
> assert på `pending_plan_reviews(...)[0].plan` og på `{run_id}-plan-review.json`. Mutasjon: revert
> til `str(review.plan)` → begge røde; kontroll: hele suiten grønn, golden byte-uendret. **Ikke
> utløs:** ingen endring i Magentic-wiring, `checkpoint_storage`, `_ALLOWED_CHECKPOINT_TYPES` eller
> F15-diffen; ingen push.
### MAJOR
**MAJOR-1 — Offline-generalprøven av utforskningen er vakuøs ved konstruksjon, og artefaktet kan
ikke se det.** (M1) `--scripted-replies` tar én konstant streng per rolle → 0 verktøykall, 0
approaches, 1 runde på 4/4 baser; `{run_id}-exploration.json` har ingen rad for navigatørens
verktøy. En operatør som «beviser så mye som mulig gratis før det dyre trinnet» kan i dag ikke bevise
at navigatøren åpner noe, og etter en betalt kjøring kan ingen lese om den gjorde det.
> **Ordreutkast M1 (Opus 5 / high, to halvdeler i én økt):** (a) `ExplorationTrace.tool_calls`
> (navn + `bundle_id`-argument, aldri resultatet) fylt av en `FunctionMiddleware`
> utforskningsagentene — speil av `mcp_tools.ToolCallRecorder`, én kopi av mønsteret — og skrevet av
> `trace_payload` ved siden av `quick_validations`; (b) `_load_scripted_replies` godtar for
> utforskningsrollene en LISTE av trinn der et trinn kan være `{"call": "<tool>", "args": {…}}`, og
> `scripted_factory` bygger da en klient som emitterer `function_call` (formen målt i denne rapporten,
> vedlegg A). **Test først:** `tests/test_scripted_explore_door_loadbearing.py` — CLI-kjøring med
> manus `list_bundles → read_bundle → tekst` gir `tool_calls == [list_bundles, read_bundle]` i
> artefaktet (RØD uten (a) og uten (b) hver for seg); kontroll: konstant-streng-manus gir `[]`, og
> golden byte-uendret. **Ikke utløs:** ingen endring i verktøykroppene eller katalogtaket; ikke
> `run.py`s debatt-roller; ingen push.
**MAJOR-2 — Ingen dør for menneskelig feedback på det forslaget som ligger på bordet.** (M3)
Feedback lander i neste kjøring (bevist), maskinens egen avvisning sløyfes tilbake (bevist), men
bruksscenarioets «med tilbakemeldinger komme med forbedrede forslag» er en to-kjørings-sløyfe.
> **Ordreutkast M2 (Opus 5 / high, Voyage-brief anbefalt — berører `run.py`, `generate.py`,
> `outbox.py`, CLI):** `run_project(..., proposal_reviewer: ProposalReviewer | None)` — kalles ETTER
> validatorens utfall med (forslag, dom, provenance) og svarer `approve` eller `revise(feedback)`;
> `revise` mater `generate._build_messages(prior_feedback=…)` (parallell til `prior_rejection`, kun
> teksten, aldri forrige JSON) inn i ett nytt bundet forsøk under EKSISTERENDE `max_attempts` +
> `meter.tick_round` — ingen ny løkke; hvert svar registreres verbatim på `RunResult.expert_revisions`
> og i outboxen; CLI-dør `--proposal-review` speiler `--plan-review` (EOF = feil, aldri godkjenning);
> hosting NEKTER (synkron). **Test først:** `tests/test_proposal_review_loop_loadbearing.py` — en
> `revise` gir et ANDRE genereringskall hvis prompt bærer feedbacken ordrett og et annet utfall enn
> en alltid-godkjenn-reviewer (diskriminatoren, F4-formen); EOF raiser; taket holder (revise ×N
> stopper på `max_attempts`); kontroll uten reviewer: byte-identisk kjøring og golden uendret.
> **Ikke utløs:** ingen endring i dom-myntingen (F2), Steg 7-innboksen, `capture_verdict` eller
> checker-gaten.
**MAJOR-3 — Bundle-konteksten re-sendes 3× i debatten og 7× i utforskningen; 7393 % av alle
prompt-tokens.** (M4) Tre poster navngitt i § 4; ≈ 40 % (utforskning) / ≈ 60 % (debatt) er
prefiks-cachebart som koden står, resten krever et kontekstbudsjett på `read_bundle`.
> **Ordreutkast M3 (Fable 5.1 eller Opus 5 / high — MÅL FØRST, så én søm):** (1) mål med instrumentet
> i vedlegg A hva `read_bundle` koster som `function_result` per base og hvor mange prompts det rir
> med i; (2) bygg `read_bundle` om til samme form som katalogen: konseptliste (`name`, `type`,
> `title`, lengde) med `read_file` som neste trinn, tak i TESTEN ikke i koden
> (`test_catalogue_cost_loadbearing.py`-formen); (3) gate: `tests/test_read_bundle_cost_loadbearing.py`
> — `read_bundle` over tunnel-basen < 1 500 o200k-tokens, kontroll at én konseptfil alene sprenger
> taket, og at `read_file` fortsatt gir hele fila. **Ikke utløs:** `okf.bundle_context` selv (den
> bærer debattens kontekst og goldenene), nav-goldenene, `run.py`. Debattens 3× er GroupChat-formen
> og en egen beslutning (kontekst i `instructions` vs melding endrer ikke tokens, bare cache-treff).
**MAJOR-4 — K2 kan ikke kjøres «hele veien» fordi ingen lager prosjektets tallfiler, og ingen plan
eier det.** (M5) `validator-input.json` + `cost-baseline.json` er håndskrevne; `_project_from_bundle`
nekter uten dem (målt to ganger i dag). `adjudication`/identitet ER bestilt (S3), dette er det ikke.
> **Ordreutkast M4 (Fable 5.1 / high — MÅL, IKKE BYGG, til PM):** inventar av K2 (86 filer): hvilke
> bærer kostlinjer (XLSX/tabeller i PDF), hvilken form (kode, mengde, enhetspris), og om
> `ir.SavingsProposal`/`CostBaseline` kan fylles fra dem uten skjønn. Utfall: EN eier-beslutning —
> produsent-profil som emitterer `cost-baseline.json` (llm-ingestion-okf), eller et konsument-steg
> `okf.derive_cost_baseline(bundle)` med vokter `tests/test_cost_baseline_derivation_loadbearing.py`.
> **Ikke utløs:** ingen kode før eieren er valgt; ingen endring i `ir.py`.
### MINOR
**MINOR-1** — `quick_validate` sier `validated` med P10 = P50 = P90 når kandidaten mangler
`assumptions` (M1 B, grenbasen: 300/300/300); den inerte MC-en flagges ikke i dommen.
**MINOR-2** — parse-feil-fangsten registrerer et `function_call`-svar som `"text": ""` (M6 B).
**MINOR-3** — MCP-verktøy er nåbare i debatten, ikke i genereringskallet (`generate.py:460`, ingen
`tools`) — by design, men ikke uttalt noe sted.
**MINOR-4** — Planteksten kringkastes aldri til deltakerne (hypotesiserens prompt = 19 tegn
instruksjon); effekten av en planrevisjon på forslaget hviler 100 % på managerens neste
`instruction_or_question`. Ærlighetsgrense som bør stå i `--plan-review`-hjelpeteksten.
### NICE (positivt målt)
**NICE-1** — Verktøysømmen bærer ende til ende, også mot en regelverksbase uten prosjekt (M1 B, 4/4).
**NICE-2** — Innboks-sløyfa virker med kontroll og nøkkel-samsvar (M3), og `finally`-artefaktene
skrives også når pipelinen nekter (M1 A grenbasen, 1 fil).
**NICE-3** — Egress-deklarasjon og B4-recorder holder mot en ekte stdio-subprosess (M6).
---
## 8. Verifiseringslogg
Instrumentet ligger i `/tmp/claude-s2/` (efemert, utenfor repoet — ordren forbød produksjonskode,
og et måleinstrument i `tests/` ville vært en beslutning om å beholde det). Vedlegg A gjengir kjernen
så tallene kan reproduseres.
| # | Påstand | Kommando → resultat |
|---|---|---|
| 1 | Treet urørt; F15-diff sett | `git status --short`` M tests/test_maf_version_guard.py`, `?? docs/presentasjon-…html`, `?? scratchpad/` (før og etter); `git stash list \| wc -l`**0** (STATE sa «stash») |
| 2 | Golden | `PYTHONIOENCODING=utf-8 uv run python -m portfolio_optimiser.simulation \| shasum``ea8c5347…` |
| 3 | Nevner tester | `uv run pytest --co -q \| tail -1` → 1085; `ls tests/*.py \| wc -l` → 120 |
| 4 | M1 A/B × 4 baser | `uv run python /tmp/claude-s2/m1.py <base> <PID\|PROBE> /tmp/claude-s2/out/m1-<n>``result.json` (tall i § 1) |
| 5 | Artefaktnøkler | `python -c 'import json;print(sorted(json.load(open("…/m1a-exploration.json"))))'` → 7 nøkler, ingen `tool_calls` |
| 6 | M2 bibliotek + CLI | `uv run python /tmp/claude-s2/m2.py shared/examples/bygg-energi-mikro BYGG-KONTOR-NORD /tmp/claude-s2/out/m2`; park: `python -m portfolio_optimiser.run … --checkpoint-dir … --run-id park1``park1-plan-review.json.plan` = repr |
| 7 | `Message` uten `__str__` | `uv run python -c "from agent_framework import Message; m=Message(role='assistant',contents=['PLAN: x']); print(str(m)[:40], m.text, type(m).__str__ is object.__str__)"``<agent_framework._types.Message object … PLAN: x True` |
| 8 | Alle `str(review.plan)` | `grep -n "review\.plan\|_plan_text(" src/portfolio_optimiser/explore.py``:966` (hjelper), `:1395/:1404/:1418/:1478` (`str(...)`) |
| 9 | Vakuøse asserts | `sed -n 161p;180p tests/test_plan_review_cli_door_loadbearing.py`; `sed -n 408p tests/test_async_plan_review_loadbearing.py`; `sed -n 564p tests/test_explore_loadbearing.py` |
| 10 | M3 | `uv run python /tmp/claude-s2/m3.py shared/examples/bygg-energi-mikro BYGG-KONTOR-NORD /tmp/claude-s2/out/m3` → § 3 |
| 11 | M4 profil | `uv run --with tiktoken python /tmp/claude-s2/m4.py <calls-*.json>` + re-sendt-skript (§ 4); `bundle_context`-tokens re-målt = commons' fasit |
| 12 | Ekte usage finnes kun 14.08 | `grep -n token_usage docs/2026-08-14-fase1b-forste-levende-kjoring.md``:221 token_usage: 15 306` |
| 13 | M5 leseprøve | `uv run python - <<EOF` mot produsent-golden (§ 5); `grep -rn "adjudication\|concept_id" src/ tests/ --include='*.py'` → 0/0; `grep -rn bundle_id src/ --include='*.py' \| wc -l` → 71 |
| 14 | Pin vs produsent | `uv pip list \| grep llm-ingestion` → 0.3.2; `ls .venv/…/llm_ingestion_okf/` → ingen `profiles.py`; `cd ~/repos/llm-ingestion-okf && git describe --tags``v0.5.0a2-104-g62b6192` |
| 15 | S3 dekker adjudication + identitet | `grep -n "adjudic\|concept_id" ~/.claude/coord/portfolio-optimiser/orders/20260902T113744Z-124778510-*.md` → amendment B (Steg 7b), D/B1 |
| 16 | M6 | `uv run python /tmp/claude-s2/m6.py shared/examples/bygg-energi-mikro BYGG-KONTOR-NORD /tmp/claude-s2/out/m6` → § 6 (serverlogg `CallToolRequest` på stderr) |
| 17 | Genereringen har ingen verktøy | `grep -n "get_response" -A1 src/portfolio_optimiser/generate.py``:460` uten `tools` |
| 18 | Doc-gaten godtar rapporten | `uv run pytest -q tests/test_doc_constant_sync_loadbearing.py tests/test_public_surface_claims_loadbearing.py` → se commit |
**Ikke verifisert i denne økten:** at en LEVENDE manager/navigatør faktisk kaller verktøyene (samme
grense som før — men sømmen som skal bære kallet er nå bevist); at leverandørens caching faktisk
treffer prefiksene (anslaget er regnet av prompt-bytes, ikke målt mot en fakturering); K2-korpusets
faktiske kostlinje-form (MAJOR-4s ordre måler det).
---
## Vedlegg A — instrumentets kjerne (reproduksjon)
```python
# RecordingClient: en ScriptedChatClient som også kan EMITTERE verktøykall (ikke produksjonskode)
from agent_framework import ChatResponse, Content, Message, UsageDetails
from portfolio_optimiser.simulation import ScriptedChatClient
class RecordingClient(ScriptedChatClient):
def __init__(self, role, *, script=(), default_reply="ok"):
super().__init__(role=role, default_reply=default_reply, tokens_per_reply=8)
self._script = list(script) # str | ("call", tool_name, {args})
def _inner_get_response(self, *, messages, options, stream=False, **kw):
item = self._script.pop(0) if self._script else self._default
if isinstance(item, tuple):
_, name, args = item
contents = [Content("function_call", call_id=f"c{self.call_count}", name=name, arguments=args)]
else:
contents = [item]
async def _coro():
return ChatResponse(messages=[Message(role="assistant", contents=contents)],
response_id="synthetic", usage_details=UsageDetails(total_token_count=8))
return _coro()
# Verktøyteller: wrap FunctionTool.func ETTER konstruksjon — agentenes verktøyliste er uendret
import functools
from portfolio_optimiser import explore as ex
TOOLS = []
def _count(tools):
for t in tools:
orig, name = t.func, t.name
@functools.wraps(orig)
def w(*a, __o=orig, __n=name, **kw):
TOOLS.append((__n, {k: str(v)[:80] for k, v in kw.items()})); return __o(*a, **kw)
t.func = w
return tools
_nav, _qv = ex.navigator_tools, ex.quick_validate_tool
ex.navigator_tools = lambda d: _count(list(_nav(d)))
ex.quick_validate_tool = lambda d, **kw: _count([_qv(d, **kw)])[0]
# Prompt-størrelse: tekst + function_call + function_result (IKKE bare .text — første versjon
# målte 16 tegn for en prompt som bar hele bundle-konteksten som function_result)
```
Prompt-tokens: `tiktoken.get_encoding("o200k_base")` over den blob-en, kjørt med
`uv run --with tiktoken` (`tiktoken` er fortsatt ikke en prosjekt-avhengighet).