portfolio-optimiser/docs/rapport/2026-07-03-sammenligningsrapport.md

20 KiB
Raw Blame History

Sammenligningsrapport — MAF og Claude Agent SDK på identisk delt kjerne (S11)

Hva dette er: Programmets sluttleveranse (målbilde §1): en sammenligning av to uavhengige implementasjoner av samme metode — Microsoft Agent Framework-stacken (portfolio-optimiser) og Claude Agent SDK-søskenet (portfolio-optimiser-claude) — målt mot samme frosne protokoll (sammenligningsprotokollen, S3) på identisk delt kjerne (pinned commons-ref db86e15). Hver tallpåstand i rapporten har en rad i verifiseringsloggen (§9) med klasse reproduce, recompute-from-artifact eller disclosed-non-reproducible. Datert 2026-07-03.

Hovedfunn

  • Begge stacker består ekvivalens-gaten (M1): begge deterministiske validatorer reproduserer golden-suitens besluttede felt på pinned ref, og Claude-sidens ene live-kjøring reproduserer goldens persentiler bit-for-bit (§2). M1 er en gate begge må gjennom, ikke en differensierende metrikk.
  • Begge stacker lukker læringssløyfa offline (M2/M3, plumbing-bevis): MAF-siden via en kjørbar, skriptet simulering (LEARNING LOOP CLOSED, exit 0), søskenet via testsuite-bevis (187 tester grønne, deriblant to-runs- og promoterings-bevisene) (§3).
  • Kun Claude-siden har et live-bevis: én API-kjøring (S10) validerte på første forsøk — 36 791 av 150 000 tokens, 2 av 12 runder, $0.127514. Den FØRSTE live-kjøringen feilet på token-taket (162 250 tokens, $0.331506, exit 3) og rapporteres her med samme prominens, med et datert protokoll-avviksnotat (§4).
  • Klarspråk om bevis-asymmetrien: MAF-siden er aldri kjørt mot en ekte modell. Alt denne rapporten sier om faktisk modellatferd gjelder utelukkende Claude-siden — og én enkelt kjøring er et eksistensbevis: den viser at noe KAN skje under den konfigurasjonen, ikke at det VIL skje. Den bindende erklæringen gjengis ordrett i §6.
  • Kvalitativt (M4 — case-studie-observasjoner, ikke rammeverks-egenskaper): søskenet løser samme metode med ~63 % av MAF-sidens kildevolum (1 696 mot 2 696 linjer; 15 mot 18 moduler) og har mutasjonskontroller MAF-siden mangler; MAF-siden har et kjørbart offline-simuleringsbevis søskenet ikke har. Ingen av observasjonene kan attribueres til rammeverkene (N=1 per rammeverk, samme forfatter — se §5 og §7).

1. Bakgrunn og metode

Målestokkens temporale status (presis): Protokollen ble bundet 2026-07-03 (S3) FØR den delte kjernen ble ekstrahert til portfolio-optimiser-commons (S4) og FØR søskenrepoet fantes (S5). MAF-implementasjonen forelå derimot allerede da protokollen ble skrevet. Pre-bindingen beskytter altså mot at Claude-siden former målestokken etter eget resultat — den beskytter IKKE mot at målestokken er preget av MAF-sidens allerede eksisterende design. Denne residual-asymmetrien er en erkjent svakhet og gjentas i §7.

Interessekonflikt-erklæring: Forfatteren er Anthropic-fokusert AI-rådgiver, og både begge implementasjonene og denne rapporten er skrevet med Claude — modellen fra leverandøren av den ene komparanden.

Identisk kjerne, bevist: Begge repo konsumerer den delte kjernen (13 filer: OKF-bundle, golden.json/validator-input.json, expert-reviewer-persona, method-spec.md) som git-subtree av commons-ref db86e15. Tre-hashen fc58b0b956e5f9871b29810f17284250e794abd5 er verifisert identisk FEM veier: HEAD:shared i begge repo, db86e15^{tree} i begge repo, og — temporalt — 0f8af04:shared i søskenet (shared-treet slik det sto i commiten som persisterte S10-artefaktene). Den femte sjekken beviser at kjernen ikke endret seg mellom live-kjøringen og denne sammenligningen; protokollens §1-krav om re-pin+re-kjør ved kjerneendring er dermed ikke utløst. diff -rq mellom de to shared/-katalogene er tom, og PORTFOLIO_SHARED_ROOT var tom i kjøringsmiljøet (ingen runtime-ompeking av MAF-sidens kjerne-resolver).

Miljø: MAF-repo på 695c7590… (Python 3.12.13, agent-framework-core); søsken på 0f8af04e… (Python 3.14.0, claude-agent-sdk). Python-versjonsskewen er uproblematisk for M1: begge validatorer bruker kun random.Random(20260624).uniform(...), og golden-testene er grønne på hver side. Alle kjøringer i S11 er offline (null API-forbruk); søskenrepoet ble behandlet strengt read-only, bevist med identisk git status --porcelain før og etter (§9, rad 13).

2. M1 — validator-samsvar mot golden (ekvivalens-gate)

M1 rammes som ekvivalens-gate, ikke som differensierende metrikk, av to grunner:

  1. Golden er ikke nøytral mellom sidene. golden.json ble generert av referanseprosedyren i method-spec §7.2 (navngitt der som «Reference procedure (what generated the golden)»: nominal_feasible = 0.30 × Σ quantity·unit_cost, Mersenne-Twister seed 20260624, 512 samples, 10-kvantiler) — en prosedyre som stammer fra MAF-linjen. MAF-sidens golden-samsvar er dermed i praksis selv-samsvar; kun Claude-sidens samsvar er en uavhengig reproduksjon.
  2. Begge validatorer er ren Python (MAF validator.py:41, søsken validator.py:25 — samme seed) — gaten måler metodetroskap, ikke rammeverkene.

Resultat på pinned ref, scopet til det ENE benchmark-bundelet bygg-energi-mikro:

  • MAF: tests/test_bygg_energi_mikro.py::test_validator_output_matches_golden grønn (i suiten: 157 passed, 4 skipped — skippene er de env-gatede live-testene tests/spikes/test_a_groupchat.py, tests/test_foundry_profile_live.py, tests/test_local_profile_live.py, tests/test_portfolio_live.py).
  • Søsken: tests/test_bygg_energi_mikro.py::test_decided_figures_match_golden grønn (i suiten: 187 passed, 0 failed).
  • Live-reproduksjon: søskenets committede runs/s10/run_result.json reproduserer goldens persentiler bit-for-bit — p10 = 68543.08886748762, p50 = 95443.98966314227, p90 = 121057.08845805985 (kommando i §9.1).

MAF-simuleringens tall er IKKE golden-tall og siteres aldri som M1-kilde: det skriptede forslaget har ingen assumptions-bånd, så Monte Carlo-trekket degenererer (alle samples like; p90 = nominal = 90 000). Det er et bevisst designvalg i et plumbing-bevis, ikke et avvik.

3. M2/M3 — offline-kategori (begge sider)

Offline-tallene er designparametre valgt av forfatteren pluss boolske håndhevelses-utfall — aldri målinger av modellatferd (skriptede stand-ins produserer dem). De sammenlignes kun med samme kategori på motparten; live-tall står i egen tabell i §4 og blandes aldri inn her.

Formforskjell (erklært): MAF-sidens offline-bevis er en kjørbar modul (uv run python -m portfolio_optimiser.simulation — to kjøringer adskilt av en promotering, over ekte MAF-agenter med skriptet chat-klient). Søskenets offline-bevis ER testsuiten (test_step7_async_loop_loadbearing.py + test_step8_promotion_loadbearing.py m.fl.) — det finnes ingen kjørbar sim-modul der, og testsuiten emitterer følgelig ingen runde-/token-tall å tabellere.

Designparameter / håndhevelses-utfall (offline) MAF (skriptet sim) Søsken (testsuite-speil)
Syntetisk tokentelling per svar (designparameter) 8 (simulation.py:80) — (ingen sim-modul)
Debatt-tak (designparameter) max_rounds=3 testene setter egne tak
Tak håndhevet? JA — GroupChatOrchestrator kuttet debatten ved taket i begge runs (tracen viser «reached max_rounds=3; forcing completion» ×2) JA — budsjett-/loop-tester grønne i suiten
Læringssløyfe lukket (fil-båret, promote → re-seed)? JA — markøren realiseringsgrad=0.79 fraværende i Run A-prompten, til stede i Run B-prompten; LEARNING LOOP CLOSED, exit 0 JA — to-runs-beviset og promoterings-gaten grønne (del av de 187)
Refinement-sti (forrige avslagsgrunn inn i neste forsøk) detach-bevist? JA — tests/test_step5_refine_loadbearing.py JA — tests/test_step5_refine_loadbearing.py

Simuleringens egen banner-tekst er del av ærlighetskontrakten og gjelder også denne rapporten: den beviser plumbing, deterministisk ryggrad og at dataflyten lukkes — IKKE at en levende LLM ville produsert forslaget eller dommen.

4. S10 — live-kategori (KUN Claude-siden)

Alt i denne seksjonen er live-observasjoner og gjelder utelukkende Claude-siden (jf. §6). Det ble gjort TO live-kjøringer; begge rapporteres med samme prominens.

Størrelse (live) Kjøring 1 (feilet, 2026-07-03) Kjøring 2 (fasit, 2026-07-03)
Utfall Strukturert stopp: token-tak nådd validated + checker approve
Tokens 162 250 (taket 150 000 HOLDT — kjøringen ble stanset) 36 791 av 150 000
Runder ikke bevart (ingen artefakter) 2 av 12
Genererings-forsøk ikke bevart 1
Kost $0.331506 $0.127514
Exit-kode 3 0
Modell ikke bevart i artefakt claude-haiku-4-5-20251001
Kilde søskenets STATE.md-logg (prosa) — disclosed-non-reproducible runs/s10/*.json, committet i 0f8af04 — recompute-from-artifact

Konfigurasjon (protokollens §4.3-krav): defaultene i søskenets src/portfolio_optimiser_claude/run_s10.py — bundle bygg-energi-mikro, max-rounds 12, max-tokens 150 000, max-budget-usd-per-call 0.25, max-debate-rounds 3, max-attempts 3, top-k 3.

Datert protokoll-avviksnotat (2026-07-03): Protokollens §4.5 lyder: «Utfall re-rulles ikke: feiler S10-kjøringen på innhold (avvist forslag, checker-REJECT, tak nådd), er DET resultatet som rapporteres. Én re-kjøring er tillatt kun ved teknisk transportfeil (nettverk/auth), og re-kjøringen loggføres eksplisitt.» Kjøring 1 endte på «tak nådd» — etter ordlyden et innholdsutfall, og re-kjørings-unntaket dekker kun transportfeil. Re-kjøringen var likevel operatør-godkjent etter et post-mortem som attribuerte feilen til harness-/konfig-defekter, ikke modellinnhold: konfig-lekkasje kapret checker-agenten, og en påtvunget preamble gjorde ren JSON-output umulig (kilde: søskenets STATE-logg). Denne kategoriseringen («harness-feil, ikke innholdsutfall») ble gjort ETTER utfallet og står ikke i protokollteksten. Rapporten behandler derfor re-kjøringen som et erklært protokollavvik — det absorberes ikke stille — og rapporterer kjøring 1 førsteklasses, jf. tabellen over.

Konfig-diff mellom feilet og vellykket kjøring (publisert): git -C /Users/ktg/repos/portfolio-optimiser-claude diff 0238507..7637c6f --stat viser 7 filer endret, 253 innsettinger, 25 slettinger (artifacts.py, loop.py, run_s10.py, sdk_client.py

  • tre testfiler), samlet i fiks-commiten 7637c6f «fix(run): S10 del 2 — post-mortem: stopp-artefakt, SDK-isolasjon, raw-JSON-direktiv». Endringene er harness-fikser (SDK-isolasjon via setting_sources=[], raw-JSON-direktiv, persistering av stopp-artefakter) — ikke justering av modell, prompt-innhold eller tak for å tvinge frem et bedre utfall. Takene var identiske i begge kjøringer. Leseren kan selv vurdere diffen; kommandoen står i §9.

Asymmetrisk artefakt-retensjon (erklært): Den vellykkede kjøringen etterlot fire committede JSON-artefakter; den feilede etterlot KUN prosa i STATE-loggen (persistering av stopp-artefakter ble bygget først i post-mortem-fiksen). Tallene for kjøring 1 er derfor ikke-reproduserbare og merket deretter. Fremadrettet retensjonsregel: enhver live-kjøring skal persistere sine artefakter uansett utfall (søskenets persist_stop_artifacts håndhever nå dette).

Hva kjøring 2 beviser (eksistensbevis): under §4.3-konfigurasjonen produserte modellen ett forslag som passerte den deterministiske validatoren og checker-gaten på første forsøk, innenfor takene. For et forretningspublikum: én kjøring viser at dette KAN skje — den sier ingenting om hvor ofte det vil skje.

5. M4 — kvalitative case-studie-observasjoner

Alt i denne seksjonen er observasjoner av ÉN implementasjon per rammeverk, skrevet av samme forfatter med samme AI-assistent, der spec-en i tillegg ble ekstrahert FRA MAF-implementasjonen (S2/S3). Prechelt (2000) fant at variansen mellom programmerere overstiger variansen mellom språk; med N=1 per rammeverk kan ingen forskjell under attribueres rammeverket. MAF-sidens spec-troskap er dessuten nær-tautologisk (spec-en ble skrevet fra den), og søskenet hadde annenmanns-fordel (implementert mot en ferdig spec med fasit). Observasjonene står som case-studie-funn, ikke rammeverks-egenskaper.

Kildevolum (ferske tall, kommandoer i §9):

Observasjon MAF Søsken
LOC i src/ (wc -l, alle .py) 2 696 1 696
Antall moduler (.py-filer i src/) 18 15
Største moduler verdicts.py 517, run.py 453, simulation.py 294 loop.py 328, experience.py 171, artifacts.py 152

En del av differansen er formforskjellen fra §3: MAF-sidens kjørbare sim-modul (294 linjer) finnes ikke i søskenet, der beviset bor i testsuiten. Differansen forsvinner ikke om den trekkes fra.

Spec-troskap (mot method-spec §12-tabellen): begge suiter er grønne på pinned ref, inkludert golden- og kontrakts-testene som håndhever §12-feltene. MAF-siden har i tillegg en egen spec-integritetstest (tests/test_method_spec_loadbearing.py); søskenet har ingen tilsvarende fil — dets spec-troskap er bevist via golden-/kontrakts-/persona-testene.

Load-bearing-dekning (detach-bevis; ærlig asymmetri begge veier):

  • MAF: 9 load-bearing-testfiler (checker-gate, method-spec, persona, shared-root, simulering, steg 1/5/7/8).
  • Søsken: 7 load-bearing-testfiler (checker-gate, persona, steg 1/5/7/8) + test_sdk_isolation.py.
  • Søskenet har det MAF mangler: fire mutasjonskontroller i golden-testen (test_changed_quantity_diverges, test_changed_assumption_band_diverges, test_expected_actual_is_rate_times_modelled, test_learning_surface_is_outside_validator_reach) og et persistert attempts-felt i run-artefakten.
  • MAF har det søskenet mangler: kjørbar offline-simulering med eget detach-bevis (test_simulation_loadbearing.py) og spec-integritetstesten.

Utvikleropplevelse (én-rater-observasjon, ingen rubrikk): Begge stacker lot metoden implementeres med type-hints, mypy (søskenet i strict-modus via pyproject) og samme testdisiplin. Dette er én forfatters erfaring fra to sekvensielle implementasjoner og har ingen utsagnskraft utover det.

6. Liveness-asymmetri-erklæringen (ordrett fra protokollens §3)

Liveness-asymmetri-erklæring: De to stackene har ulik bevis-liveness, valgt av kostnadshensyn (D6). MAF-siden kjøres ALDRI mot en ekte modell; dens bevis er et skriptet offline-bevis av plumbing, deterministisk ryggrad og lukket læringssløyfe. Claude-siden kjører ÉN minimal ekte API-kjøring (S10) som programmets eneste genuine modell-atferdsbevis. Sammenligningen påstår derfor ALDRI noe om modellatferd på MAF-siden; enhver live-observasjon gjelder kun Claude-siden og merkes slik der den siteres.

7. Begrensninger (threats to validity)

  1. N=1 implementasjon per rammeverk, samme forfatter og samme AI-assistent — ingen observert forskjell kan attribueres rammeverkene (Prechelt-poenget i §5).
  2. Ett benchmark-bundle (bygg-energi-mikro) — M1-ekvivalensen er kun bevist for dette bundelet.
  3. Interessekonflikt — forfatter og verktøy er knyttet til leverandøren av den ene komparanden (§1).
  4. Residual temporal asymmetri — protokollen predaterer søskenet, men ikke MAF-implementasjonen (§1); målestokken kan være preget av MAF-designet.
  5. Én live-kjøring, én re-kjøring etter erklært protokollavvik — live-kategorien er et eksistensbevis med et deklarert §4.5-avvik (§4); den feilede kjøringens tall er ikke-reproduserbare (kun STATE-prosa).
  6. Bevis-liveness er asymmetrisk by design (§6) — offline- og live-kategoriene er holdt i separate tabeller, og ingen live-påstand finnes om MAF-siden.

8. Konklusjon

Begge implementasjonene består ekvivalens-gaten på identisk delt kjerne og lukker læringssløyfa i offline-kategorien; metoden er altså realiserbar på begge rammeverk med deterministisk validator som blokkerende gate. Claude-siden har i tillegg programmets eneste live-bevis: én kjøring som validerte på første forsøk innenfor takene — et eksistensbevis, etter én feilet kjøring som rapporteres med samme vekt. De kvalitative forskjellene (mindre kildevolum og mutasjonskontroller hos søskenet; kjørbart simuleringsbevis og spec-integritetstest hos MAF-siden) er case-studie-observasjoner fra én forfatter og overføres ikke til påstander om rammeverkene. Skriptede stand-ins er merket som stand-ins overalt hvor de forekommer.

9. Verifiseringslogg

Klasser: reproduce = kommandoen kan kjøres på nytt og skal gi samme svar; recompute-from-artifact = tallet avledes av committede artefakter (live-tall re-kjøres aldri, jf. §4.2/§4.5); disclosed-non-reproducible = kilden er erklært og ikke-reproduserbar.

Kommandoer kjøres fra MAF-repoets rot (/Users/ktg/repos/portfolio-optimiser); søskenet er /Users/ktg/repos/portfolio-optimiser-claude.

# Påstand Kommando / kilde Klasse
1 Tre-hash fc58b0b956e5f9871b29810f17284250e794abd5 identisk ×5 (inkl. temporal S10-sjekk) git rev-parse 'HEAD:shared' 'db86e15^{tree}' i begge repo + git -C <søsken> rev-parse '0f8af04:shared' reproduce
2 Delt kjerne = 13 filer, katalogene identiske diff -rq <MAF>/shared <søsken>/shared (tom) + find shared -type f | wc -l reproduce
3 PORTFOLIO_SHARED_ROOT tom i kjøringsmiljøet echo "[$PORTFOLIO_SHARED_ROOT]"[] reproduce
4 Python-versjoner 3.12.13 / 3.14.0 uv run --frozen python -V i hvert repo reproduce
5 MAF-suite: 157 passed, 4 skipped (navngitt i §2) uv run --frozen pytest -rs reproduce
6 MAF ruff/format/mypy grønne (18 kildefiler) uv run ruff check . + uv run ruff format --check . + uv run mypy src reproduce
7 Søsken-suite: 187 passed (inkl. steg 7/8-loadbearing) PYTHONDONTWRITEBYTECODE=1 uv run --frozen --directory <søsken> pytest -p no:cacheprovider -rs reproduce
8 Søsken ruff/mypy grønne (mypy strict via pyproject) uv run --frozen --directory <søsken> ruff check . + … mypy src reproduce
9 MAF-sim: løkke lukket, exit 0, markør False→True uv run --frozen python -m portfolio_optimiser.simulation; echo EXIT=$? reproduce
10 S10-fasit: 36 791/150 000 tokens, 2/12 runder, attempts 1, $0.127514, claude-haiku-4-5-20251001 cat <søsken>/runs/s10/{usage,run_result,provenance}.json recompute-from-artifact
11 S10-persentiler == golden bit-for-bit python-kommando i §9.1 reproduce
12 runs/s10/ har nøyaktig én commit (0f8af04) git -C <søsken> log --format="%H %cI %s" -- runs/s10/ reproduce
13 Read-only-bevis: søskenets git status --porcelain identisk før/etter S11-kjøringene BEFORE-/AFTER-fangst i evidence; git -C <søsken> status --porcelain (rent) reproduce
14 Konfig-diff feilet↔vellykket: 7 filer, +253/25 git -C <søsken> diff 0238507..7637c6f --stat reproduce
15 Kjøring 1: 162 250 tokens, exit 3, $0.331506, ingen artefakter søskenets STATE.md, logglinje «S10, live-kjøring 1» disclosed-non-reproducible
16 LOC/moduler: 2 696/18 (MAF), 1 696/15 (søsken) find src -name '*.py' -exec wc -l {} + og find src -name '*.py' | wc -l i hvert repo reproduce
17 §3-erklæringen gjengitt ordrett i §6 python-kommando i §9.2 reproduce
18 Ingen statistiske ord i rapporten grep -inE 'typis[k]|gjennomsnittli[g]|stabil[t]' docs/rapport/2026-07-03-sammenligningsrapport.md → 0 treff (exit 1) reproduce

9.1 M1 bit-for-bit-kommandoen (rad 11)

python3 - <<'EOF'
import json, pathlib
g = json.loads(pathlib.Path("shared/examples/bygg-energi-mikro/golden.json").read_text())["validator"]
r = json.loads(pathlib.Path("/Users/ktg/repos/portfolio-optimiser-claude/runs/s10/run_result.json").read_text())["outcome"]
for k in ("p10", "p50", "p90"):
    assert g[k] == r[k], (k, g[k], r[k])
print("OK: golden == run_result (bit-for-bit p10/p50/p90)")
EOF

9.2 Verbatim-kommandoen (rad 17, whitespace-normalisert sammenligning)

python3 - <<'EOF'
import re, pathlib
proto = pathlib.Path("docs/plan/2026-07-03-sammenligningsprotokoll.md").read_text()
block = re.search(r"> \*\*Liveness-asymmetri-erklæring:\*\*.*?(?=\n\n## )", proto, re.S).group(0)
decl = "\n".join(l.lstrip("> ").rstrip() for l in block.splitlines())
report = pathlib.Path("docs/rapport/2026-07-03-sammenligningsrapport.md").read_text()
assert decl.replace("\n", " ") in " ".join(report.split()), "asymmetry declaration NOT verbatim"
print("OK: declaration verbatim")
EOF