portfolio-optimiser/docs/plan/2026-08-06-demo-uke-plan.md
Kjell Tore Guttormsen d6f3359fae feat(step5): the falsification that informed the next hypothesis now leaves the loop
generate_via_llm consumed each validator Rejection internally (`last`), fed it into the
next attempt's prompt, and dropped it. So Step 5 was real but unobservable: a caller could
see THAT a proposal validated, never that it validated on attempt 2 after the deterministic
validator falsified attempt 1. It was the one step of the eight with no output to show.

The seam is a typed return value -- GenerationResult(outcome, refinements) -- rather than an
out-parameter or a callback: a returned value cannot be silently lost by a caller that forgets
to pass a collector, and mypy forces every call site to acknowledge it.

refinements carries ONLY rejections that were actually fed back. When the attempt budget runs
out the final rejection IS outcome; counting it here would be double-counting, and the bounded
control test goes red on the collect-everything implementation that gets this wrong.

The loop's bound is untouched: max_attempts and meter.tick_round stand, and `last` still drives
the prompt alone, so prompt growth is unchanged. run.py accumulates across _evaluate calls, so
_evaluate_mandate is untouched; RunResult.refinements defaults (the coverage precedent) and is
concatenated across approaches rather than keyed per approach -- stated as an honesty limit.

The simulation now shows it: the scripted proposer overclaims 250000, which the validator
falsifies against P90 = 90000, and the corrected 30000 validates. Only the overclaim is
scripted -- the rejection is computed. scripted_factory takes a per-role reply selector so this
needs no second scripted client body.

README records the two accuracy changes only (Step 5 is now inspectable; the simulation trace
shows the correction). The level-2 publishing claim stays deferred until after the demo (O4).

Load-bearing MEASURED against the full suite with a control, four mutations all red:
detach the returned history (4 tests) - collect-everything (control only) - detach the run
wiring (2 tests) - revert the simulation's proposer to a constant (the demo-protection test).
Control: 759 passed / 4 skipped; ruff, format and mypy clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CcWFcREUi6YPjEpN3ACDP
2026-08-06 15:12:06 +02:00

136 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Demo-uke — alle åtte steg live torsdag 13. august 2026
> **Grunnlag:** `2026-08-06-intensjons-qa.md` (påstander A1C8, funn G1G6, beslutninger O1O4).
> Rammene A/B/C fra briefen står. Denne planen legger til dagsrekkefølge, den ene kodejobben,
> og et go/no-go-punkt som gjør uka robust mot at innholdet ikke kommer.
## 0. Låst — ikke gjenåpne
| | Beslutning |
|---|---|
| **A** | «Live» = sanntids-gjennomgang, skriptet under panseret. Preflight/Foundry er UTE av kritisk sti. |
| **B** | MAF-siden alene. Søsken-repoet er parkert. |
| **C** | Det realistiske eksemplet bygges i commons, i en egen tab. |
| **O1** | Ferdigbygd eksempel. Fabrikken (D-G/T0 `okf-toolkit`) er eksplisitt utsatt. |
| **O2** | Steg 5 bygges og vises live. |
| **O3** | Bestilt commons 2026-08-06 med frist 11. august; reserve planlagt. |
| **O4** | README oppdateres ETTER demoen (14.15. august), ikke før. |
Bestillingen er levert: `20260806T112037Z-2648131757-from-portfolio-optimiser.md`.
## 1. Hva demoen ÆRLIG er — formuleringen er avtalt på forhånd
D-I setter publiserings-påstanden til **nivå 2**: realistisk case, modellerte tall, aldri salgsspråk
over beleggsnivået. Tre ting sies høyt i demoen, ikke i en fotnote:
1. **Agent-svarene er skriptet.** Dette beviser dataflyten, den deterministiske ryggraden og at
læringssløyfa lukkes — ikke at en levende modell ville produsert nettopp dette forslaget.
(Banneret sier det allerede i dag; det skal stå, ikke pyntes bort.)
2. **Innholdet er håndkuratert, ikke fabrikkert.** Den besluttede demo-stien (D-H pkt. 4) går via en
bundle-fabrikk som ikke er bygget. Vi viser «last ned → kjør», men et menneske lagde innholdet.
3. **Tallene er modellerte, ikke målte.** Ingen pilot har validert dem i drift.
Dette er ikke en unnskyldning som svekker demoen — det er selve grunnregelen repoet er bygget på
(A5: koden får ikke påstå mer enn den gjør). En demo som overselger bryter med det den demonstrerer.
## 2. De åtte stegene → hva som faktisk vises
`RunResult` bærer allerede `retrieved`, `debate_output`, `checker_verdict`, `outcome`
(`ValidatedProposal | Rejection`), `verdict` og `coverage`. Presentasjonen er ~30 `print`-linjer
(`simulation.py:295326`). Derfor er sju av åtte steg **presentasjonsarbeid**, og ett er ekte bygg:
| Steg (`method-spec` §3) | Vises som | Status i dag | Arbeid |
|---|---|---|---|
| 1 — Forstå konteksten | navigerte filer + ExpeL-folden (`retrieved`) | markør-linja antyder det | `print` |
| 2 — Hypotese | forslaget med parametere | data finnes, printes ikke | `print` |
| 3 — Debatt (maker-checker) | begge deltakere + `checker_verdict` | checker printes | `print` |
| 4 — Valider / falsifiser | validator-linja med P90 | **printes** ✔ | — |
| 5 — Forbedre, informert og bundet | avvisning → korrigert forslag | **bygget 7. aug** ✔ | — |
| 6 — Forkast eller foreslå | typet `outcome` | data finnes, printes ikke | `print` |
| 7 — Svar på tilbakemelding | persona-dom + fil-innboksen | dommen printes | `print` |
| 8 — Promoter godkjent kunnskap | promotert fil + index-lenke | **printes** ✔ | — |
**Steg 5 er den eneste ekte kodejobben.** `generate_via_llm` forbruker den mellomliggende
avvisningen internt (`last`) og returnerer bare sluttresultatet — og i dagens demo-kjøring
validerer forslaget på FØRSTE forsøk, så forbedringsløkka trigges aldri. Det kreves to ting:
en søm som slipper avvisnings-historikken ut, og et nytt skriptet forløp der forslaget først
blir avvist og deretter korrigert.
## 3. Dagsplan
**Fredag 7. august — steg 5-sømmen (den ene kodejobben). ✔ GJORT.**
Slipp avvisnings-historikken ut av `generate_via_llm` uten å endre løkkas tak (`max_attempts` +
`meter.tick_round` står urørt — «forbedre til god nok» uten tak er forbudt). Nytt skriptet
avvis-så-korriger-forløp. Ny load-bearing-test: RØD når sømmen kobles fra. Ligger først i uka
med vilje — det er den eneste jobben som kan overraske, og den har fem dagers slakk bak seg.
> **Utfall:** den åpne beslutningen ble **egen returtype** — `generate_via_llm` returnerer
> `GenerationResult(outcome, refinements)`, og `RunResult.refinements` bærer den ut av kjøringen.
> En returverdi kan ikke bli stille tapt slik en out-parameter kan, og mypy tvinger hvert kallsted
> til å ta stilling. `refinements` bærer KUN avvisninger som faktisk ble matet tilbake (den siste
> avvisningen ved uttømt budsjett ER `outcome`). Simuleringens proposer overklager 250 000 NOK, som
> den deterministiske validatoren felt mot P90 = 90 000; det korrigerte forslaget på 30 000
> validerer. Fire mutasjoner målt røde mot hele suiten, med kontroll.
**Lørdag 8. søndag 9. august — presentasjonslaget.**
De fem `print`-tilleggene over, formet som én lesbar gjennomgang med steg-nummer i margen.
Ingen ny logikk. Målet er at en tilhører kan følge hvert steg uten at du forklarer hva de ser på.
**Mandag 10. august — gjør manuset innholds-drevet.**
`ScriptedChatClient` tar en `reply_selector` over `(prompt_blob, role)` — den ser altså prompten
og KAN nøkle svaret på kandidaten i stedet for å ha ett hardkodet svar. Bygg det **nå, før
innholdet kommer**: da er et nytt prosjekt en data-oppføring, ikke et nytt manus skrevet for hånd
under tidspress. Dette er ukas viktigste risikoreduksjon.
**Tirsdag 11. august — GO/NO-GO på innholdet.**
Er commons-leveransen hentbar? `git subtree pull --prefix=shared commons main --squash`, så
kjør. **JA:** pek `simulate_learning_loop` på den nye bundelen, utvid selector-dataene, mål
rendret kontekst-størrelse per bundle. **NEI:** lås mikro-eksemplet som demo-innhold og si det i
ærlighets-avsnittet. Beslutningen tas tirsdag, ikke onsdag kveld.
**Onsdag 12. august — generalprøve, så fryse.**
Kjør hele gjennomgangen to ganger. Identisk output begge ganger (determinisme er et poeng, ikke en
detalj). Full suite grønn. Etter generalprøven: ingen endringer i kjørestien.
**Torsdag 13. august — demo.**
**Fredag 14. lørdag 15. august — README (O4).** Nivå-2-påstanden løftes ETTER at beviset finnes.
## 4. Risiko
| Risiko | Utslag | Tiltak |
|---|---|---|
| Commons rekker ikke 11. august | Tynt innhold | Reserve låst tirsdag; bestillingen sier eksplisitt at det er en reserve, ikke en krise |
| Nytt innhold krever nytt manus | 2 dager håndarbeid under press | Innholds-drevet `reply_selector` bygges mandag, FØR innholdet kommer |
| Prompten sprenges av stor bundle | Token-tak slår inn midt i demoen | Bestillingen er størrelses-kappet på målt grunnlag (~25 000 tegn/bundle); mål på nytt tirsdag |
| Steg 5-sømmen tar lengre tid | Ett steg mangler | Ligger fredag; fallback er testbevis-varianten (vurdert og valgt bort, men den finnes) |
| `docs/presentasjon-*.html` eies av annen sesjon | Konflikt | Røres aldri; alltid eksplisitt filliste ved `git add` |
## 5. Verifisering
Konkrete kriterier, ikke «sjekk at det virker»:
1. `uv run python -m portfolio_optimiser.simulation` → exit 0, og outputen har **én merket linje per
steg 18**. Verifiseres med `... | grep -cE "^ *Steg [1-8]"` → 8.
2. Steg 5 er synlig som **to** forslag: ett avvist med grunn, ett korrigert som validerer.
Verifiseres ved at outputen inneholder både en `REJECTED`- og en `VALIDATED`-linje for samme kandidat.
3. Ny load-bearing-test for steg 5-sømmen blir **RØD** når sømmen kobles fra. Måles mot HELE suiten,
med kontroll, restaurert fra scratchpad-kopi + `shasum -c`.
4. `uv run pytest -q` grønn (baseline i dag: 759 kollektert, 755 passed / 4 skipped).
5. `uv run ruff check .` + `uv run mypy src` rene.
6. Generalprøve onsdag: to kjøringer, **byte-identisk** output (`diff <(kjøring1) <(kjøring2)` tom).
7. Ved commons-leveranse: rendret kontekst per bundle måles med `okf.bundle_context` og skal ligge
under ~25 000 tegn. Over det → bruk færre bundles, ikke større prompt.
8. `shared/examples/bygg-energi-mikro/` og `nav-golden-*` er **uendret** etter subtree-pull
(`git diff --stat` på de stiene → tomt). Goldenene er load-bearing.
## 6. Nøkkelantakelser som skal testes, ikke antas
- **«Sju av åtte steg er ren presentasjon.»** Testes fredag/lørdag: hvis et av de fem `print`-tilleggene
viser seg å kreve ny logikk, er det samme klasse funn som C7 og skal meldes med en gang.
- **«`reply_selector` kan nøkle på kandidaten.»** Signaturen er `(prompt_blob, role) -> str`, så
prompten er tilgjengelig — men at kandidaten er entydig identifiserbar i blobben er ikke verifisert.
Testes mandag, på mikro-eksemplet, før innholdet kommer.
- **«Ny bundle plugges inn som parameter.»** `simulate_learning_loop(bundle_dir, work)` tar katalogen
som argument (`simulation.py:183`, kalt `:293`) — verifisert i dag. Det som IKKE er verifisert, er at
en bundle med flere kandidater kjører gjennom uendret; det er S3.2-stien, og den testes tirsdag.