portfolio-optimiser · v1.1.0 · teknisk gjennomgang

Agentene foreslår. En beregning avgjør. Fagfolk dømmer.

Et åpent Python-rammeverk på Microsoft Agent Framework som leter etter kostnadsbesparelser inni hvert prosjekt i en portefølje: arkitekturen, tilbakemeldingssløyfene og harnessen, fra formålet ned til hver enkelt søm.

For AI-arkitekter og utviklere · inntil 60 minutter · alle påstander har kildelinje til repoet (git.fromaitochitta.com/open/portfolio-optimiser, HEAD 50c9763, 2026-09-18)

01Agenda

Fra formål til søm: sju deler, hver med figurer.

Del 1 · 6 minFormåletProblemet, hovedidéen, hva det ikke er
Del 2 · 8 minArkitekturen overordnetLagene, én kjøring i åtte steg, repo-kartet, backend-profilene
Del 3 · 14 minElementeneKunnskapsbasen, ingest, debatten, validatoren, ekspert-sløyfen, artefaktene
Del 4 · 10 minArbeidsflyten og sløyfeneSekunder, minutter, dager, måneder: hvem lukker hva
Del 5 · 8 minMAF-harnessenHva MAF er, byggeklossene, hva som er valgt og valgt bort
Del 6 · 5 minKvalitetsmetoden og målt statusLoad-bearing tester, invariantene, hva som er vist og ikke vist
Del 7 · 4 minEtter v1.1Destinasjonen, arbeidsstrømmene, hva som ikke skal bygges

Det du skal sitte igjen med: helheten (hvorfor systemet er bygget som en portrekke med mennesker i begge ender) og detaljen (hvilken modul, hvilket tak, hvilken fil som krysser hvilken grense). Hver slide har en kildelinje du kan slå opp.

02Lesehjelp

Tre farger, én betydning hver.

Figurene i dette decket bruker samme visuelle språk hele veien, så du kan lese en ny figur uten å lære den på nytt.

  • Blått er det som er valgt og i bruk på default-stien.
  • Rødt er en port som nekter, blokkerer eller stopper.
  • Grønt er et utfall som passerte, eller et resultat i verden.
  • Stiplet er opt-in: finnes, men er av inntil noen slår det på.
  • Grått er ikke i bruk, eller bare kontekst.

Tall står alltid med nevner. «Ikke verifisert» betyr nettopp det. Kildelinjen nederst peker på fil og linje i repoet.

Valgt, default-sti f.eks. GroupChat-debatten Port som nekter f.eks. Rejection, BudgetExceeded Passert / resultat f.eks. ValidatedProposal, ledger Opt-in f.eks. --explore, --mcp-config Ikke i bruk / kontekst f.eks. WorkflowBuilder-grafen dataflyt eller kall valgfri eller senere kobling

Del 1 · Formålet

Formålet

Hva rammeverket faktisk løser, hvem som har hvilken rolle i sløyfen, og hva det med vilje ikke gjør.

03Del 1 · Formålet

Problemet er ikke å finne besparelser, men å vurdere dem, prosjekt for prosjekt.

Portefølje av uavhengige prosjekter FV42-GSV-E1 Fv. 42 gang- og sykkelveg, etappe 1 RV13-RAS-TP Rv. 13 rassikring tunnelportal BRU-LAKS-REHAB Bru over Lakselva, rehabilitering SKOLE-VVS-OPPGR Skole, VVS-oppgradering Reallokering MELLOM prosjekter utenfor scope — porteføljestyring ligger over metoden FV42-GSV-E1 — kostnadslinjene INNI prosjektet kode beskrivelse mengde enhetspris 01.1 Rigg og drift 1 rs 850 000 02.3 Masseutskifting bløt grunn 4 200 m3 420 03.1 Forsterkningslag knust grus 1 800 m3 310 05.2 Asfalt Ab11 4 300 m2 215 07.4 Kantstein granitt 2 400 m 690 09.1 Veglysmaster LED 34 stk 28 500 Σ 6 721 500 NOK over 6 linjer Et tiltak foreslås MOT disse linjene, og må navngi kodene sine her. Syntetisk referanseprosjekt: AI-forfattede plassholdere, ikke ekte estimater.
Besparelsen bor inni prosjektet. Rammeverket leter etter tiltak mot kostnadslinjene i ett prosjekt om gangen, og flytter aldri budsjett mellom prosjekter.

Kilde: README.md:9-13 · README.md:265-279 · src/portfolio_optimiser/data/reference_projects.json

04Del 1 · Formålet

Tre roller: to språkmodeller som kan diskutere, og en beregning som ikke lar seg overtale.

Fagekspert bestiller med et mandat ett prosjekt + retninger Debatt proposer foreslår ETT typet kandidat-tiltak checker angriper resonnementet og avslutter med en dom Deterministisk validator solver + Monte Carlo obligatorisk og blokkerende aldri en valgfri plugin Den avgjør TALLENE. Den kan bare blokkere. validated rejected unsupported Fagekspert dømmer utfallet, dager senere læringssløyfen — den lukkes først i neste kjøring (Del 4)
Samme fagperson står i begge ender: én bestiller kjøringen med et mandat, én dømmer utfallet etterpå. Imellom ligger én beregning som ikke lar seg overtale.

Kilde: src/portfolio_optimiser/workflow.py:26-45 · src/portfolio_optimiser/validator.py:648-748 · shared/method-spec.md:54

05Del 1 · Formålet

Fem ting rammeverket med vilje ikke er.

Non-goals er ikke mangler. De er grenser noen har bestemt, og de forklarer hvorfor kjernen er så liten.

Det er ikkeDet er
Et compliance-produktTekniske forutsetninger: lokal drift, provenance på hvert forslag, ingen stille egress. Behandlingsformål, DPIA og ROS eies av den som tar det i bruk.
En porteføljereallokatorBesparelser inni hvert prosjekt. Å flytte budsjett mellom prosjekter, og å rangere prosjekter mot hverandre, ligger over metoden.
Autonom beslutningstakingValidatoren kan bare blokkere. Å godkjenne et tiltak er fagekspertens valg, og rammeverket iverksetter ingenting på agentenes ord.
En nøkkelferdig vertikalGenerisk kjerne med navngitte extension points — datakilder, kostnadsmodeller, personaer. De siste 10 % av et domene er deployerens.
En modell-benchmarkEnde-til-ende-beviset kjører offline mot en scripted stand-in. Det viser at sløyfen lukkes, ikke hvor godt en gitt LLM foreslår eller dømmer.

Status, uten pynt. Én live-kjøring mot et ekte endepunkt er gjennomført (14.08.2026): første forsøk døde på rundetaket, gjenkjøringen endte i et korrekt rejected. Ingen kjøring har ennå gitt et validert forslag mot en live modell, og ingen ekte ekspertdom har kommet inn i treet ennå.

Kilde: README.md:265-303 · shared/method-spec.md:26-33

06Del 1 · Formålet

Designfilosofien: generisk kjerne, tydelige sømmer, mennesker på toppen.

Tradisjonell wiki Skrevet for mennesker prosa, kapitler, navigasjon for øyet Søk og lesing for mennesker indeksert for den som browser Maskintilgang skrudd på etterpå et API over noe som ikke var ment for det Denne Menneske-affordanser som LAG PÅ TOPPEN inbox-fil, promotion gate, rapporter Wikien er skrevet FOR modellen agentens arbeidsminne og læringssubstrat Ingenting inn uten en godkjenning fail-closed promotion gate
Rekkefølgen er snudd: den primære leseren av hver fil er modellen, ikke en person som browser. Menneskene er fortsatt avgjørende — de er bare et lag over, ikke under.
90 %-prinsippet

Bygg den generiske kjernen og navngi sømmene. De siste 10 % av et domene er deployerens.

Fork-and-own

Én vedlikeholder, ingen SLA, MIT. Issues er signaler; pull requests tas ikke imot.

Rent teknisk rammeverk

Behandlingsformål, DPIA og ROS eier den som tar det i bruk. Vi bygger ikke compliance-funksjoner.

Kilde: README.md:14-19 · README.md:335-349 · docs/extending.md:294-321

Del 2 · Arkitekturen overordnet

Arkitekturen overordnet

Lagene, de åtte stegene, kartet over koden, den delte kjernen, de to backend-profilene og de fire kjøremodusene.

07Del 2 · Arkitekturen overordnet

Fem lag, og porten sitter mellom agentene og alt som blir varig.

1 · Kunnskap — OKF-bundle («LLM-wiki») markdown + YAML-frontmatter, index.md, kryss-lenker Det eneste laget som er varig. 2 · Agenter — maker–checker i en GroupChat proposer foreslår, checker angriper, hardt rundetak Skriver ingenting selv. 3 · Deterministisk validator solver + Monte Carlo, obligatorisk og blokkerende Port 1 · tallene kan bare blokkere, aldri godkjenne 4 · HITL — en inbox-mappe eksperten skriver en JSON-fil, systemet leser mappa Dager eller uker senere. Ingen levende sesjon. 5 · Læring — promotion gate en godkjent dom løftes inn i wikien som en konseptfil Port 2 · hva som blir varig fail-closed — bare godkjent kunnskap krysser lukkes i NESTE kjøring
Inndelingen i fem lag er framstillerens lesning. Spesifikasjonen selv navngir tre: context layer, output layer og promotion gate — og sier at separasjonen mellom dem er load-bearing.

Kilde: shared/method-spec.md:36-50 · src/portfolio_optimiser/verdicts.py:699-773

08Del 2 · Arkitekturen overordnet

Én kjøring, åtte steg, og sløyfen lukkes først i neste kjøring.

Inni én kjøring — steg 1 til 6 1 · Understand naviger bundlen, fold inn tidligere ekspertdommer 2 · Hypothesise ett typet kandidat- tiltak, streng IR 3 · Debate maker–checker, hardt rundetak 4 · Validate to falsifiserere: tallene og resonnementet 5 · Refine nytt forsøk informert av nektgrunnen 6 · Propose validert forslag eller typet nekt På tvers av kjøringer, adskilt i tid — steg 7 og 8 7 · Feedback dager senere: en JSON-fil i en mappe 8 · Promote godkjent dom løftes inn i wikien neste kjøring leser den nye konseptfila
Steg 1 til 6 skjer inni én kjøring. Steg 7 og 8 er en annen tidsskala helt: eksperten svarer dager senere, og en senere kjøring plukker det opp. Tilstandsmaskinen med vaktene tas i Del 4.

Kilde: README.md:351-376 · shared/method-spec.md:56-58

09Del 2 · Arkitekturen overordnet

Repo-kartet har seks lag, men bare én av grensene er håndhevet av koden.

Blå = importerer agent_framework · 13 av 39 moduler Kjøreflater og ops 7 moduler run simulation hosting preflight hitl costsim evals/v1_gate Orkestrering 7 moduler, alle MAF-bundne workflow explore generate budget mcp_tools tracing backends Validering 2 moduler validator stress Porten som avgjør verdien har ingen MAF-import i det hele tatt. Læring og HITL 7 moduler verdicts proposal_review outbox notify persona ledger value_report Kunnskap og ingest 10 moduler datasource tools okf retrieval semretrieval prepass frozen_bundles ingest ingest_mcp shared_root Kontrakter og IR 6 moduler provenance ir dimension contracts mandate reference_domain
Målt 18.09.2026: 41 .py-filer og 22 019 linjer under src/. To av filene er __init__.py, så 39 er moduler, og 13 av dem importerer agent_framework. Lagdelingen er framstillerens lesning; modulene ligger flatt, og bare evals/ er en pakke.

Den ene grensen koden håndhever. tests/test_okf.py holder en liste, _MAF_FREE_MODULES, med ni moduler — okf, dimension, mandate, outbox, costsim, hitl, notify, semretrieval, proposal_review — og en AST-sjekk går rødt hvis noen av dem importerer agent_framework eller mcp. AST, ikke tekstsøk, så en docstring som omtaler rammeverket ikke utløser den. ingest og ingest_mcp har egne vakter, og hitl og notify har i tillegg en transitiv importgraf-vakt. Det er dette som lar en søsken-implementasjon på Claude Agent SDK gjenbruke lagene nederst uendret.

Kilde: tests/test_okf.py:22-32 · tests/test_okf.py:476-500 · tests/test_hitl_loadbearing.py:273 · find src -name "*.py" | xargs wc -l

10Del 2 · Arkitekturen overordnet

Den delte kjernen er en git subtree, og begge stackene implementerer fra spec-en alene.

portfolio-optimiser-commons source of truth — ingen kjørbar kode method-spec.md 8-stegs-sløyfa ingest-spec.md manifest + gate CONCEPT.md forretningskonseptet examples/ 3 bundles + fasit skills/ expert-reviewer Fasit-suiten er den ENESTE ground truth. Personaen ligger som en framework-nøytral Agent Skill (SKILL.md etter agentskills.io-mønsteret), og hver stack leser den samme JSON-en med sin egen loader. git subtree pull --squash portfolio-optimiser (MAF) shared/ som pull-only subtree Wheelen bærer treet som pakket data under portfolio_optimiser/_shared/, så en installert dist virker uten checkout. portfolio-optimiser-claude (Claude Agent SDK) samme bundles, samme fasit Bygget fra spesifikasjonen alene, uten å reversere referansekoden. PARKERT — ikke utviklet parallelt. ALDRI git subtree push fra en konsument Målt 03.07.2026: re-splitten lekket HELE konsumentens historikk inn i commons. Synk er pull-only, og alle endringer lander der først.
Oppslaget av shared/ skjer ved kall: PORTFOLIO_SHARED_ROOT først, så arbeidstreets shared/ når den finnes, ellers den pakkede kopien i distribusjonen.

Kilde: shared/README.md · src/portfolio_optimiser/shared_root.py:20-38 · pyproject.toml:111-112

11Del 2 · Arkitekturen overordnet

To backend-profiler: local på loopback uten egress, azure mot Foundry.

profil + rolle local | azure proposer · checker resolve_model() 1 · ukjent profil → ValueError 2 · map[profil][rolle] 3 · ellers map[profil][default] 4 · REPLACE-WITH-* → ValueError PORTFOLIO_MODEL_MAP slår den pakkede get_backend(profile) ChatBackend-protokoll returnerer én av to LocalBackend — utviklingsdefault OpenAIChatCompletionClient 127.0.0.1:11434/v1 — loopback, aldri en fjernvert Chat Completions, ikke Responses: usage uten None AzureFoundryBackend FoundryChatClient credential er PÅKREVD — ingen lazy default AzureCliCredential på en utviklermaskin ManagedIdentityCredential i hostet container Det som faktisk ligger i data/model_map.json local: default / proposer / checker = qwen3:4b azure: default / proposer / checker = REPLACE-WITH-FOUNDRY-DEPLOYMENT Azure-profilen feiler altså fail-fast ut av boksen, helt til operatøren bytter dem ut.
Endepunktet leses ved kall, og vårt eget navn vinner: PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT først, så det plattform-injiserte FOUNDRY_PROJECT_ENDPOINT. Presedensen gjelder verdier, ikke erklæringer, så et eksportert-men-tomt navn faller gjennom i stedet for å skygge for et ekte.

Kostnadsdisiplin. Utvikling skjer på local, som er gratis. Foundry brukes bare til målrettet, minimal verifisering. Credential-figuren og hele leverandørsømmen tas i Del 5.

Kilde: src/portfolio_optimiser/backends.py:87-91 · backends.py:105-122 · backends.py:125-172 · src/portfolio_optimiser/data/model_map.json

12Del 2 · Arkitekturen overordnet

Kjørestien har fire moduser, og de er en dokumentert partisjon.

CLI-en har 36 distinkte flagg. Ett kall kan ikke utøve dem alle, så modusene er skrevet ned som en partisjon, og kombinasjoner som ville vært tvetydige nektes i stedet for å bli tolket.

ModusPåkrevdHva den gjør
EnkeltprosjektPROJECT_ID --docs-dirÉn kjøring mot ett prosjekt. Valgfritt --bundle-dir, --verdict-dir, --outbox-dir (krever --run-id), --explore, --proposal-review, --scripted-replies, --live-dry-run.
Multi-base--across-bundle gjentatt, --mandate, --run-id, --outbox-dirÉn bestilling, flere kunnskapsbaser, sekvensielt. Én verdict-store tres gjennom alle, så en dom mot base k når base k+1.
Portefølje--portfolioReferanseprosjektene etter hverandre. Valgfritt --goals og --ledger; stopper tidlig når målet er nådd.
Verdirapport--report --ledgerLeser bare. Null modellkall. Ruller opp realiserte besparelser per prosjekt, med provenance per post.

Nektet, aldri stilltiende ignorert. --live-dry-run og --scripted-replies er begge offline-moduser og nektes sammen. --json uten --report nektes. --report tåler bare --ledger og --json ved siden av seg. --across-bundle nekter --bundle-dir, --portfolio, --explore og --prepass-payload, fordi hver av dem løser opp én base.

Kilde: src/portfolio_optimiser/run.py:2826-3145 (37 add_argument, hvorav én er posisjonsargumentet project_id) · run.py:3185 · run.py:3265-3271 · run.py:3434 · run.py:4051

Del 3 · Elementene

Elementene i arkitekturen

Nå går vi gjennom sømmene én for én: kunnskapsbasen, navigasjonen, ingest-laget, kontraktene, debatten, validatoren, ekspert-sløyfen og artefaktene en kjøring etterlater seg.

13Del 3 · Elementene

Kunnskapsbasen er en LLM-wiki (Karpathys idé) i OKF-format (Googles spec).

index.md reservert inngangspunkt tiltak-led.md type: measure metode-mv.md type: methodology grunnlag.md type: evidence dom-2026-03.md type: verdict kryss-lenke --- type: measure title: LED-retrofit 2026 ingest_manifest: kilder.json --- type er det ENESTE påkrevde feltet. Egendefinerte felt BEVARES. Verdict-laget er skjermet Ekskluderingen er en TYPE-SJEKK på hver fil idet den nås, på hvert nivå — aldri en egenskap ved lenkegrafen. Tidligere dommer når hypotesen bare gjennom den portede ExpeL-folden, aldri ved å bli lest som bundle-kunnskap.
Det er nettopp fordi egendefinerte felt må bevares at prosjektets egne lag — ekspertdommer og ingest-provenance — kan bo i frontmatteren uten å bryte formatet.

Hvorfor et åpent format. OKF er leverandørnøytralt, så de samme bundlene konsumeres uendret av begge referanseimplementasjonene. Kunnskapen overlever den enkelte agent-stacken.

Kilde: README.md:305-333 · shared/method-spec.md:38-44 · src/portfolio_optimiser/okf.py:44-50

14Del 3 · Elementene

Navigasjon, ikke RAG: index først, lenker deretter, og verdict-laget er skjermet i verktøyet.

list_bundles() hvilke baser finnes — O(baser), aldri O(korpus) read_bundle(bundle_id) åpne ÉN base og list toppnivået read_dir(bundle_id, path, …) én katalog, paginert: 10 av gangen, maks 50 read_file(bundle_id, path) ett dokument helt — og fire porter foran Dybde-først over lenker En katalog blir ALDRI enumerert. Ledende / betyr bundle-rot. Det er ESCAPE, ikke dybde, som er forbudt. Ufulgte lenker havner på Bundle.skipped med grunn: missing eller outside-bundle. Aldri stille. Fire porter på read_file 1 · Verdict-laget — nektes ubetinget, på dokumentets EGEN frontmatter 2 · Sti utenfor bundlen — safe_resolve, PathSecurityError 3 · Katalog, ikke dokument — DirectoryPathRefused 4 · Utenfor dimensjonen — DimensionScopeRefused Nektene er RETURVERDIER, ikke exceptions. Grunnen reiser tilbake til modellen. Bytene gjør det aldri.
En femte funksjon, declare_requirement, legges på kun når kravloggen er koblet på. En logg som ikke ser hva som ble åpnet, ville godtatt hver eneste erklæring.
Form, målt på K2-korpuset (630 konseptdokumenter)o200k-tokens i én kjørings prompterEndring
Hele bundlen utlevert, i tre kopier1 947 342 (99,1 % av promptene)utgangspunkt
Peker + de fire verktøyene, like-for-like753−99,96 %
Peker + en debatt som faktisk går stigen8 942−99,5 %

Kilde: src/portfolio_optimiser/explore.py:1199-1612 · src/portfolio_optimiser/okf.py:914-975 · docs/invarianter.md:1719-1734

15Del 3 · Elementene

Ingest-laget bygger bundlen FØR kjøringen, gjennom en content gate.

manifest JSON-Schema- validert, fail-fast polymorf kildetype connectors file / CSV sql http MCP er en HttpGet-formet adapter i http-familien materialize renderer konsept- filer og index.md null modellkall materialize_gated content gate llm-ingestion-guard skanner HVERT generert konsept FØR noe når bundlen OKF- bundle 8-stegs-sløyfa leser den Nektet kjøring skriver INGENTING Ingen halvferdig bundle, ingen delvis skrevet index — nekten er et utfall, ikke et avbrudd midtveis. Nettverk er et løfte per kjøring allow_network er et kwarg til materialize, default False, aldri et manifest-felt: manifestet kan ikke gi seg selv nett. En http-kilde nektes fail-fast uten eksplisitt opt-in.
Ingest kjører før sløyfen og gjør null modellkall. De gylne uttrekkene er bit-identiske på begge stacker, fordi begge implementerer fra den samme ingest-spec-en.

Status og et målt dokumentavvik. Laget er kjørt kun mot committede fixtures; ingen bundle er ennå materialisert fra en live kilde. Pinnene i pyproject.toml er llm-ingestion-okf v0.8.5 (linje 71) og llm-ingestion-guard v1.4.0 (linje 80), mens docs/extending.md:86 og ingest.py-docstringen fortsatt sier v0.3.2 og v0.3.4. Dokumentene er utdaterte, ikke koden.

Kilde: src/portfolio_optimiser/ingest.py:1-33 · src/portfolio_optimiser/ingest_mcp.py:99 · pyproject.toml:71,80 · README.md:288-296

16Del 3 · Elementene

Kontraktene er Pydantic, og de feiler før det første modellkallet.

Validert ved oppstart contracts.py — fire kontrakter DataSourceContract · ModelMapContract TerminationContract · FeedbackContract mandate.Mandate objective + approaches[] med id, label, bundle_id og en bindende requirement dimension.Dimension allowed_measure_types er påkrevd budget.Budget max_tokens og max_rounds, begge over 0 nekter konstruksjon ellers — aldri ubegrenset explore.ExplorationContract seks felt, alle påkrevd, ingen default Gjennom kjøringen ir.SavingsProposal project_id · measure affected_items[] — minst én claimed_saving_nok over 0 assumptions: kode → (low, high) ir.AffectedItem code · quantity, minst 0 unit_cost over 0 · total summen begrenser claimed ir.CostBaseline project_id · items: kode → linje prosjektets faktiske kostnadslinjer — det validatoren forankrer mot På hvert utfall provenance.ProvenanceStamp citations — minst én, alltid model · role · token_usage validator_decision: validated | rejected cost_baseline_anchored: bool, uten default — begge defaultene ville løyet bundle_id_source · code_forms external_calls — tom liste er en PÅSTAND om at ingenting utenfor prosessen ble kontaktet Fail-fast, ikke tolerant load_contracts bygger de fire i rekkefølge og reiser på den FØRSTE som er malformert, før noen chat-klient finnes. Ingen degradering til noe som «virker likevel»: en tolerant baseline løsner porten i stillhet.
Feltene som kan lyve er enten påkrevde uten default, eller har en begrensning Pydantic håndhever ved konstruksjon. BindingRequirement bærer stien og kravets eget nummer som frontmatteren skriver det, aldri en parafrase — målt bakgrunn: 0 av 26 fasit-konsepter ble noen gang åpnet, i begge runder, mens hver kjøring likevel produserte forslag.

To ting som IKKE er det samme. TerminationContract og Budget er ressurstak; GoalContract er et domenemål — en besparelse i øre eller prosent — og det målet bor ett sted, ikke i mandatet. McpConfig er extra=forbid, så en forvillet "credential": "sk-…" blir en nekt i stedet for en ignorert nøkkel.

Kilde: src/portfolio_optimiser/contracts.py:31-101 · ir.py:15-63 · mandate.py:46-156 · budget.py:67-81 · provenance.py:53-104 · explore.py:65-99 · mcp_tools.py:50-58

17Del 3 · Elementene

Debatten er maker–checker i en GroupChat, med rundetak og en VERDICT-linje.

proposer checker deterministisk validator 1 · ett typet kandidat-tiltak (streng IR via response_format) 2 · angriper resonnementet, avslutter med VERDICT: APPROVE eller REJECT 3 · revidert kandidat — selection_func sykler proposer, checker, proposer … 4 · den konvergerte kandidaten går til porten 5 · ved nekt: KUN Rejection.reason inn i neste forsøks prompt, aldri forrige JSON Ferske agenter, ferske klienter, per kjøring Målt: en gjenbrukt bygget workflow er ENGANGS — klientkall-serien [2, 0, 0] over tre .run() på ett objekt. Gjenbruk gir TOMME kjøringer, ikke bare forurensede. Derfor en fabrikk, ikke et delt objekt. Tre uavhengige stopp with_max_rounds(max_rounds) er det bindende taket make_termination(max_rounds × 2 + 1) er sikkerhetsnettet over det BudgetMiddleware nekter NESTE kall før det gjøres. Mer i Del 5.
output_from=agents er load-bearing: uten den gir get_outputs() bare orkestratorens «reached max rounds»-melding, og både proposerens forslag og checkerens dom går tapt. run.py skiller dem på author_name. På grenen uten pre-pass-payload har debatten de samme fire navigasjonsverktøyene som utforskningen.

Slik ser en falsifisering ut når den reiser tilbake til modellen — hele blokken, ordrett:

Your previous proposal was REJECTED by the deterministic validator.
Reason: {prior_rejection.reason}
Produce a REVISED SavingsProposal that resolves this.

Checker-porten er fail-open. Bare en eksplisitt VERDICT: REJECT blokkerer. Mangler markøren, eller er den uleselig, står den deterministiske validatoren alene som port. Stadiene i den porten er Del 3b; MAF-objektene rundt debatten er Del 5.

Kilde: src/portfolio_optimiser/workflow.py:26-117 · generate.py:362-367 · generate.py:701-793 · run.py:673-687 · run.py:1331-1346

18Del 3 · Elementene

Utvidelsespunktene er navngitte sømmer, ikke plugin-magi.

SømmenSlik utvider duRegelen bak
Eget prosjektdata/reference_projects.json + en docs-mappeKun konfig og dokumenter. En test nekter hardkodede prosjekt-id-er i src/.
Datakilderetrieval.retrieve() bak en FunctionToolSti-sjekken er fail-closed, og sitatet er eksakt ved konstruksjon, ikke ved kontroll.
Modell-mapdata/model_map.json, PORTFOLIO_MODEL_MAPKonfig, ikke kode. Plassholdere feiler fail-fast før en klient bygges.
Ingest-kildematerialize(http_get=…)http-familien er det dokumenterte eksempelet. Nett er et kwarg, aldri et manifest-felt.
MCPingest-adapter, og --mcp-config i debattenAllowlist er påkrevd, og hver server og hvert tillatte verktøy navngis før første kall.
Retriever / embedderRetriever- og Embedder-protokolleneLUKKET registry — aldri en import-sti, som ville vært kodekjøring ved konfig-lasting.
Notifierbuild_notifier(config, allow_egress=…)Egress er et kode-kwarg per kjøring, aldri et konfig-felt. Webhooken er eneste egress-punkt.
client_factoryrun_project(client_factory=…)Det ekte backend-byttet. Det står ikke i docs/extending.md — en reell dokumentasjonsglipe.

Bevisste kutt, ikke gjeld. Full taksonomi for motstridende ekspertdommer, checkpointing av en kjøring og parallell fan-out er skrevet ned som kutt, hver med sømmen navngitt. To sømmer til finnes i koden uten å stå i utvidelsesdokumentet: METHOD_CAPS (metodetaket i validatoren) og personabytte ved å re-peke shared/.

Kilde: docs/extending.md:15-321 · src/portfolio_optimiser/semretrieval.py:215-226 · notify.py:175 · run.py:1097 · validator.py:52 · persona.py:30-47

19Del 3 · Elementene

Validatoren er en portrekke, og den første porten som slår, avgjør.

NEKT Stage 1 · Pydantic (ved konstruksjon) claimed_saving_nok ≤ Σ item.total · båndet omslutter unit_cost Stage 0 · cost-baseline koden må finnes i baselinen · avvik over 5 % av baseline nektes Stage 0b · forankring mot input verbatim · treghet · kodeform · klausulnummer (fire underkontroller) Stage 2 · solver (CBC LP) max Σ x_i, s.t. Σ x_i ≤ 0,30 · Σ total_i, 0 ≤ x_i ≤ total_i Stage 3 · Monte Carlo 512 trekk · Uniform(low, high) · seed 20260624 · P10 / P50 / P90 Stage 4 · P90-blokk claimed over P90 feasible nektes (strukturell overgrense) Stage 4b · nominal-blokk claimed over nominal feasible nektes, uavhengig av stage 4 Stage 5 · metodetak energy_efficiency: claimed over 0,15 · Σ totals nektes ValidatedProposal(p10, p50, p90, nominal_feasible) alle porter passert
Returnerer ved FØRSTE port som slår. Stage 1 er en Pydantic-ValueError ved konstruksjon; de øvrige returnerer Rejection. Stage 2 og 3 regner, de nekter ikke. Stadienavnene i rapporten kommer fra rejection_stage(), som er en RAPPORT og ikke en port.

Et tredje utfall ligger utenfor validatoren. Unsupported er definert i validator.py, men produseres i run.py etter at validatoren har sagt ja.

Kilde: src/portfolio_optimiser/validator.py:648-748 · validator.py:749-775 · docs/invarianter.md

20Del 3 · Elementene

Solver og Monte Carlo: dette er hva som faktisk regnes.

Stage 2 · CBC LP max Σ x_i s.t. Σ x_i ≤ 0,30 · Σ total_i 0 ≤ x_i ≤ total_i én kontinuerlig variabel per affected_item Stage 3 · Monte Carlo 512 trekk, ingen CBC per trekk unit_cost ~ Uniform(low, high) seed 20260624 (fast) poster uten bånd beholder oppgitt unit_cost Desiler quantiles(n=10, inclusive) nominal feasible kommer fra LP-en, persentilene fra trekkene P10 rapporteres P50 rapporteres P90 porter Bare P90 er en port: claimed over P90 gir Rejection
P10 og P50 rapporteres på det validerte forslaget, men ingen av dem blokkerer noe. Stage 4b legger en uavhengig grense på det nominelle LP-svaret, fordi et oppoverskjevt bånd kan løfte P90 over nominal.

Ærlig om LP-en: x = 0 er alltid tillatt, så «infeasible» har ingen kodesti. Et ikke-optimalt svar fra CBC er en miljøfeil og kaster CbcUnavailable, aldri en Rejection.

Kilde: src/portfolio_optimiser/validator.py:129-145 · validator.py:148-164 · validator.py:697-719

21Del 3 · Elementene

Et forslag kan ikke finne på kostnadslinjene det sparer mot.

Stage 0 avstemmer hver affected_item mot prosjektets egen CostBaseline: koden må finnes, og avviket i mengde og enhetspris må ligge innenfor 5 % av baselinen.

Prosjekt01.1 enhetsprisΣ prosjektUtfall
FV42-GSV-E1850 0006 721 500validated
RV13-RAS-TP1 450 0007 529 700rejected
BRU-LAKS-REHAB620 0003 099 700rejected
SKOLE-VVS-OPPGR480 0004 640 000rejected

READMEs portefølje-demo sender det SAMME forslaget mot alle fire. Alle fire bærer kostnadskoden 01.1, men til fire ulike beløp. Ett validert forslag, tre avvisninger. Ingenting ved forslaget endret seg.

SYNTETISK. De fire referanseprosjektene er AI-forfattede placeholders i data/reference_projects.json, ikke faktiske prosjekttall.

Ett forslag, fire prosjekter 01.1 · 1 stk à 850 000 · claimed 40 000 FV42-GSV-E1 01.1 = 850 000 · innenfor toleransen · videre til solveren RV13-RAS-TP 01.1 = 1 450 000 · avviket er langt over 5 % · nektet BRU-LAKS-REHAB 01.1 = 620 000 · avviket er langt over 5 % · nektet SKOLE-VVS-OPPGR 01.1 = 480 000 · avviket er langt over 5 % · nektet Alle fire siterer samme verdict id: dommen nøkles på KANDIDATEN, ikke på prosjektet.
Porten er forankret i prosjektets egne tall, ikke i forslagets interne aritmetikk.

Kilde: src/portfolio_optimiser/validator.py:225-279 · src/portfolio_optimiser/data/reference_projects.json · README.md steg 6

22Del 3 · Elementene

En kode må ha en form, og et klausulnummer er ikke en pris.

1 · verbatim koden må stå i teksten Koden er ikke en delstreng av inputen kjøringen fikk. Alle fire nekt starter med «ungrounded identifier {code}: it …» 2 · treghet kortere enn 3 tegn, eller overalt «appears in N of the M documents this run was given» Grensen er max(10; 5 % av dokumentene). Nevneren står i meldingen, fordi Steg 5 mater den ordrett inn i neste forsøk. 3 · kodeform fritatt i baselinen eller identifier-form «has no identifier form; the input offers N identifiers of its own» Slår IKKE når inputen ikke tilbyr identifikatorer i det hele tatt: en base kan ikke svares i en form den ikke bruker. 4 · klausulnummer kun når basen er UFORANKRET «not one of the N this knowledge base declares» «an unanchored base carries no price for a clause number». Slår bare på komplementet: kravformet kode som IKKE står i vokabularet. forankret videre til stage 2 classify_codes: identifier | prose | requirement En RAPPORT om hva kjøringen gjorde av koden, ikke porten. Havner i provenance-stemplet som code_forms.
Fire underkontroller per berørt post, første treff vinner. Målt i P18 runde 2: to alminnelige ord fra en standards prosa (4 av 270 N500-dokumenter og 4 av 1 133 N200-dokumenter) passerte hele porten som kostnadskoder før kodeform-regelen fantes.

Kilde: src/portfolio_optimiser/validator.py:471-491 · validator.py:494-571 · validator.py:574-645 · validator.py:388-408

23Del 3 · Elementene

Tre utfall, ikke to: validated, rejected og unsupported.

validate_proposal den deterministiske porten avgjør bare TALLENE Rejection ingen persentiler, bare grunnen ValidatedProposal p10 · p50 · p90 · nominal Tre vilkår, alle må holde 1 · kjøringen har et deklarert mandat 2 · approach har ikke sitt eget krav 3 · ingen deklarasjon på approach-id run.py:589-597, ikke et validator-stadium Unsupported bærer de validerte tallene videre ja nei rejected unsupported validated
Unsupported arver fra Rejection. Hver konsument som spør «er dette validert?» med isinstance(..., ValidatedProposal) svarer nei uten å endres, så utfallet blir aldri talt eller summert som en suksess. Konsumentene som NAVNGIR statusen sjekker klassen først.

Uttalt svakhet: deklarasjonens KVALITET vurderes ikke. Regelen spør om et krav ble deklarert, ikke om deklarasjonen var god.

Kilde: src/portfolio_optimiser/run.py:589-597 · validator.py:113-126 · outbox.py:106-146

24Del 3 · Elementene

Ingen ubegrenset sløyfe: hvert tak er påkrevd ved oppstart.

TakHvor det borHva som skjer når det slår
Debattrunderwith_max_rounds (workflow.py:113)Hardt tak. En debatt som ikke konvergerer terminerer ikke seg selv.
Refinement-forsøkself_repair(max_attempts=3)Første ValidatedProposal, ellers siste Rejection. max_attempts ≤ 0 nektes.
Token-takBudgetMiddleware, strict_usage=TrueNekter kallet FØR det gjøres når taket er brukt opp. Manglende usage gir UsageUnavailable, aldri stille telling.
Portefølje-målerPortfolioBudget + PortfolioMeterBudgetRefused ved START når passet ikke kan finansiere én kjøring. Nekt, aldri reparasjon.
UtforskningstakExplorationContract (explore.py:65-101)Seks felt, ingen defaults. MAF-ens egne defaults er ubegrenset, så et utelatt felt ville falt tilbake på den ene formen spec-en forbyr.
Parse-feil_fetch_parsed (generate.py:642-672)Retryen bærer GRUNNEN inn i neste prompt. Før dette ble de samme bytene sendt på nytt.
kontrakt-sorasen-04, runde-3-kjøring: 11 av 12 runder brukt på svar som feilet på samme måte Alle elleve feilet på «claimed_saving_nok: 0», fordi ingenting fortalte modellen hva som var galt.
Målingen som gjorde retryen informert. Den tolvte runden er markert dempet fordi den ikke er en suksess, bare den siste.

Kilde: src/portfolio_optimiser/budget.py:67-256 · validator.py:778-796 · explore.py:65-101 · generate.py:310-319 · generate.py:642-672

25Del 3 · Elementene

Hvert forslag bærer sitt eget provenance-stempel.

{run_id}-proposal.json proposal SavingsProposal.model_dump() total er en property, så den er ikke med provenance ProvenanceStamp.model_dump() ni felt, ingen av dem valgfrie i praksis citations minst én, håndhevet av min_length=1 model · role hvilken modell og hvilken rolle som svarte validator_decision "validated" eller "rejected", validatorens egen dom token_usage fra tilbyderens egne UsageDetails cost_baseline_anchored kjørte stage 0? Påkrevd uten default: begge defaults ville løyet bundle_id_source hvilken kunnskapsbase, eller None når ingen var involvert code_forms hver kode som identifier, prose eller requirement external_calls tom liste er en POSITIV påstand, ikke et fravær Feltene er STRUKTURERTE, ikke prosa: «var falsifikatoren forankret» er et operativt spørsmål som må kunne leses av en maskin.
Stemplet følger artefaktet ut av prosessen. Det gater ingenting; det gjør et hopp over stage 0 leselig i stedet for stille.

Kilde: src/portfolio_optimiser/provenance.py:53-105 · outbox.py:81-90

26Del 3 · Elementene

Ekspertens dom kommer dager senere, som en fil i en mappe.

KJØRING N {run_id}-outcome.json bærer verdict_id nøkkelen finnes også uten at noen dømte Eksperten, dager senere ingen levende sesjon antas kjøringen er for lengst avsluttet {verdict.id}.json {decision, rationale} én fil per dom i innboksmappa KJØRING N+1 load_verdicts_from_dir tolerant: hopper over det den ikke forstår manglende mappe gir tom liste, ikke feil VerdictStore in-memory, first-write-wins varig lagring er utsatt til fase 3 retrieve, topp k 0,60 Jaccard over kodesettet 0,25 measure · 0,15 størrelsesbøtte format_fewshot() settes FORAN generasjonskonteksten run.py:1551-1560. Én linje per treff: [id] decision: rationale. Teksten i beskrivelsen rangerer ikke. Id-en myntes av {sorterte koder, measure_type, claimed_saving_nok}, sha256[:16] Identisk forslag gir identisk id. Det er slik en senere kjøring finner den tidligere dommen. Samme regel betyr at to dommer om samme kandidat deler filnavn: siste skriving vinner på disk, første i minnet.
ExpeL-folden. Folden er gatet på at basen har en IR-projeksjon å nøkle mot; en base uten en slik teller sine dommer som unkeyed_verdicts og rapporterer dem, i stedet for å slippe dem i stillhet.

Halv dom nektes ved navn. --decision og --rationale går sammen eller ikke i det hele tatt. Fram til 1.1.0 defaultet --decision til approved, så hver flaggløs kjøring myntet en godkjenning ingen ga og bar den inn i neste prosjekts hypotese. Den defaulten er borte.

Kilde: src/portfolio_optimiser/verdicts.py:73-110 · verdicts.py:214-277 · verdicts.py:300-374 · run.py:1551-1560 · run.py:3166-3177

27Del 3 · Elementene

Promotion-porten er fail-closed: bare en godkjent dom blir wiki-kunnskap.

Verdict decision + rationale rått agent-output promoterer aldri seg selv promote_verdict _APPROVED_DECISIONS: approved approved_with_adjustment PromotionRefused skriver og lenker INGENTING samme nekt-familie som BudgetRefused og NotifyRefused promoted-verdict-{token}.md frontmatter type: verdict, med sin EGEN strukturelle nøkkel navigerbar for neste kjørings seed_store_from_bundle Indeks-lenken har en nøytral etikett, «Promotert ekspert-vurdering (gated)», så signalet ikke kan lekke inn i prompten via bundle_context.
Tidsstempelet er et påkrevd argument uten wall-clock-default, så promoteringen er deterministisk og stempelet reproduserbart.
hitl pending

Outboxens verdict_id minus innboksens id-er. Svaret er hvilke kandidater som fortsatt venter på et menneske.

hitl route

Rutingtabell på kostnadskode-PREFIKS, ikke på measure. Measure er åpen prosa og ble målt som ikke-diskriminerende.

Ingen match

Uroutbar rute får expert=None og sendes likevel ut. Flere treff gir laveste id og ambiguous=True.

Kilde: src/portfolio_optimiser/verdicts.py:673 · verdicts.py:699-773 · hitl.py:159-165 · hitl.py:347-391

28Del 3 · Elementene

En kjøring etterlater seg deterministiske artefakter.

_dump(payload) = json.dumps(payload, sort_keys=True, indent=2) + "\n"   # UTF-8

Sorterte nøkler, to mellomrom innrykk, eksplisitt avsluttende linjeskift. Ingen wall-clock og ingen uuid: run_id kommer fra kalleren, så samme kjøring gir byte-identiske filer.

FilHva den bærer
-proposal.jsonforslaget og provenance-stemplet
-outcome.jsonvalidated, rejected eller unsupported, med verdict_id
-coverage.jsonstoppgrunn og én rad per approach
-runconfig.jsonprofil, modeller, tak og top_k
-debate.jsonverktøykall og deklarerte krav
-multibase.jsonfullførte kjøringer, kollisjoner, budsjettstopp
FilHva den bærer
-prepass.jsonforhåndspasset
-exploration.jsonsporet fra Magentic-utforskningen
-plan-review.jsonplanen som venter på en signatur
-proposal-reviews.jsonmenneskets approve/revise per forsøk
-parse-failures.jsonsvar som ikke lot seg parse, ordrett

To dokumenterte unntak. write_parse_failures hevder ikke byte-determinisme, fordi innholdet er levende modellprosa, og skriver fra en finally. write_exploration er byte-deterministisk, men skriver også fra en finally.

Kilde: src/portfolio_optimiser/outbox.py:44-46 · outbox.py:49-447

29Del 3 · Elementene

Et validert forslag er en påstand. En ledger-post er et resultat.

PÅSTAND RESULTAT Forslaget, med provenance citations, modell, rolle, token_usage produsert av agentene under debatten Besparelsen materialiserte seg en kontrakt ble endret, en faktura kom lavere et faktum om verden, ikke en slutning systemet kan trekke Den deterministiske validatoren p10 · p50 · p90 · nominal_feasible tallene holdt, innenfor de rammene som ble oppgitt LedgerEntry, skrevet av et menneske amount_ore: int, verdict_id, provenance-linje rammeverket skriver ALDRI ledgeren, og ingen kommando lager den som sideeffekt Checker-verdicten resonnementet, ikke tallene to falsifikatorer på samme kandidat realize() er fail-closed alt som ikke er godkjent gir RealizationRefused --report er kun lesing: null modellkall, null skriving to_ore er rammeverkets ENE pengekonvertering int((Decimal(str(nok)) * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP)) Kvantiser per beløp, SUMMER så heltallene. Tre linjer à 60 000,005 NOK gir 18 000 003 øre kvantisert først, 18 000 001 summert først. Heltallsaddisjon er assosiativ, så totalene er uavhengige av rekkefølge.
Å holde de to fra hverandre er en beslutning, ikke en mangel. Et validert forslag er en påstand systemet kan lage selv; en ledger-post er noe bare verden kan bekrefte.

To nøkler, én telling. Lagringsnøkkelen er trippelen prosjekt, dimensjon og kandidat-identitet. Sumnøkkelen er dimensjonsfri, så den samme besparelsen realisert under to dimensjoner telles ÉN gang og flagges som overlapp. Verdirapporten kaller ledgerens dedupliserte aksessorer og summerer aldri postene på nytt.

Kilde: src/portfolio_optimiser/ledger.py:45-58 · ledger.py:92-241 · value_report.py:48-115 · README.md steg 7

Del 4 · Arbeidsflyten og sløyfene

Arbeidsflyten og sløyfene

Arbeidet er ikke én løkke, men et knippe sløyfer med svært ulik lengde: en chat-tur på sekunder, en terminal-review på minutter, en ekspert-dom på dager, en realisert besparelse på måneder. De korte lever i minnet. Alt som er tregere enn én kjøring krysser som fil.

30Del 4 · Arbeidsflyten og sløyfene

Arbeidsflyten går fra kunnskapsbase til realisert besparelse, og bare det midterste leddet er en kjøring.

FAGPERSON Kunnskapsbase OKF-bundle: index.md og konseptfiler 1-2 ukers arbeid OPERATØR Bestillingen mandat med approaches, cost-baseline, tak minutter MASKIN Kjøringen åtte steg: proposer, checker, validator sekunder til minutter MASKIN Utboksen -proposal.json -outcome.json byte-deterministisk FAGPERSON Dommen innboks/{id}.json én fil per dom dager til uker FAGPERSON Promotion fail-closed port type: verdict dager til måneder OPERATØR Ledger realisert besparelse i hele øre, så --report måneder innboksen flettes inn FØR Steg 1-folden i neste kjøring den promoterte dommen blir en fil i bundlen, og neste kjøring navigerer den som all annen kunnskap
Blå = fagpersonen handler. Hvit = operatøren. Grå = maskinen. De to tilbakeføringene er hele læringen: den stiplede er Steg 7, den heltrukne er Steg 8.

Kilde: shared/method-spec.md §3 · src/portfolio_optimiser/run.py:1180-1187 · src/portfolio_optimiser/verdicts.py:699-773 · docs/knowledge-base-recipe.md:12

31Del 4 · Arbeidsflyten og sløyfene

Én kjøring er en tilstandsmaskin der hvert tak er påkrevd, ikke valgfritt.

Steg 0 · explore (opt-in, Magentic manager) ExplorationContract: seks tak, alle påkrevd, ingen default. En utelatt grense ville falt tilbake på en ubegrenset loop, ikke en forsiktig en. Steg 1 · Forstå rammeverket, deterministisk top_k = 3 Steg 2 · Hypotese proposer-agenten én typet IR Steg 3 · Debatt proposer ⇄ checker max_rounds = 3 Steg 4 · Valider validator + checker 0.30-grensen, p10/p50/p90 Rejection grunn, ingen persentiler Unsupported tallene holdt, intet krav erklært ValidatedProposal p10 / p50 / p90 Steg 5 · Refine max_attempts = 3 kun SISTE reason, ordrett, aldri JSON-en og ingen CLI-flagg for taket Steg 6 · Utboks {run_id}-*.json + verdict key
Steg 7 og 8 står ikke her. De skjer etter at prosessen er død, og er temaet tre slides fram.

Ingen henger, alltid en typet stopp. BudgetExceeded(kind, limit, observed) med kind i {tokens, rounds, exploration_rounds}. Standardtakene er max_rounds = 3 og max_tokens = 100 000, og et cap-objekt nekter å konstrueres med en ikke-positiv verdi. Tokens telles fra leverandørens egen usage, aldri et ord-estimat.

Kilde: src/portfolio_optimiser/run.py:168-169 · generate.py:559 · workflow.py:110-116 · explore.py:82-99 · budget.py:30-44 · validator.py:39

32Del 4 · Arbeidsflyten og sløyfene

Tre porter dømmer den samme kandidaten, og de slås aldri sammen til ett felt.

Kandidaten den typede IR-en Validatoren TALLENE · blokkerende stadie 0: cost-baseline, 5 % toleranse 0.30-grensen på hver kostnadslinje Monte Carlo, seedet, 512 trekk Rejection grunn, ingen persentiler Unsupported intet krav erklært for retningen ValidatedProposal p10 / p50 / p90 Checker-porten RESONNEMENTET · fail-open kun en eksplisitt VERDICT: REJECT overstyrer. Ingen markør blokkerer. REJECT overstyrer
Unsupported arver fra Rejection i koden, men er en egen type: tallene holdt, og nettopp derfor er det ikke en numerisk avvisning. Den summeres aldri.

Spec-en sier det som en regel: de to falsifikatorene er «the deterministic validator (numbers) and the debate checker (reasoning) — they judge the same candidate and are never conflated». Den tredje porten er yngre: et forslag hvis approach ikke erklærte noe krav i kunnskapsbasen blir unsupported, verken validert eller avvist.

Kilde: shared/method-spec.md §2, §9 · src/portfolio_optimiser/validator.py:39, :67-68, :97-114 · run.py:673-687 · README.md:502-509

33Del 4 · Arbeidsflyten og sløyfene

Sløyfene er ikke like lange, og det er hele poenget med arkitekturen.

Sekunder Minutter Timer Dager til uker Måneder Chat-tur lukkes av modellen ChatResponse + usage_details Debattrunde lukkes av checkerens VERDICT proposerens konvergerte tekst Parse-feil-retry lukkes av IR-parseren kun parse-grunnen Avvisning inn i refine lukkes av validatoren kun siste grunn, ordrett --proposal-review et menneske ved tastaturet approve, eller revise med tekst --plan-review, synkron et menneske ved tastaturet planen ut, godkjenning inn costsim operatøren, før pengene en hva-hvis-tabell i hele øre Run A / Run B, offline testsuiten og demoen to markører via filsystemet Stressrunde + judge judgen leser rundens artefakter {run_id}-*.json + fasit.json --across-bundle én bestilling, flere baser ett verdict-store på tvers Parkert plan review et menneske, en annen dag spørsmålsfil + svarfil Ekspert-dom i innboksen en fagperson skriver JSON {id}.json, én fil per dom v1-gaten operatøren, per runde tre runder ekte tilbakemelding Ny kunnskapsbase et team, 1-2 ukers arbeid kildefiler blir OKF-konsepter Promotion i wikien et menneske, eksplisitt port type: verdict-konseptfil Realisert besparelse virkeligheten: en lavere faktura LedgerEntry i hele øre --report operatøren per prosjekt og portefølje disse to kolonnene krysser bare som filer
Farge = hvem som lukker sløyfa. Grå: maskinen. Hvit: operatøren eller testsuiten. Blå: et menneske. Grønn: verden utenfor. Varighetene er lest ut av dokumentasjonen og koden, ikke målt med klokke. Repoet måler tokens og filer, aldri veggtid.

Kilde: shared/method-spec.md §3 Steg 7, §8 · src/portfolio_optimiser/generate.py, workflow.py, proposal_review.py, hitl.py, verdicts.py, ledger.py, costsim.py, stress.py · docs/knowledge-base-recipe.md:12

34Del 4 · Arbeidsflyten og sløyfene

De korte sløyfene lukkes på sekunder, og hver av dem bærer bare grunnen videre.

Chat-tur proposer BaseChatClient TokenMeter + BudgetMiddleware manglende usage feiler lukket get_response(messages, response_format=…) ChatResponse + usage_details, belastes straks Debattrunde, GroupChat med round-robin proposer checker max_rounds = 3 begge deltakere overflates, så run.py kan skille dem på author_name kandidaten, som prosa VERDICT: APPROVE eller VERDICT: REJECT med grunn Avvisning inn i neste forsøk validatoren prompt-byggeren blokk 3 av fire mulige proposer forsøk i + 1 kun grunnen aldri JSON-en max_attempts = 3 kun den siste fast rekkefølge: base · approach · rejection · feedback · parse-feil
Alle tre lukkes uten at noe forlater prosessen. Avvisningene samles opp i RunResult.refinements, så en leser kan se hva kjøringen faktisk ble fortalt.

Målt, ikke antatt. Før parse-grunnen ble båret videre, kalte _fetch_parsed modellen med byte-identisk prompt. Én betalt kjøring (kontrakt-sorasen-04) brukte elleve av sine tolv runder på svar som feilet på samme måte, fordi ingenting noensinne fortalte modellen hva som var galt.

Kilde: src/portfolio_optimiser/generate.py:282-327, :642-673 · workflow.py:100-116 · run.py:664-687, :204-214 · budget.py:44

35Del 4 · Arbeidsflyten og sløyfene

De mellomlange sløyfene kjøper informasjon før pengene brukes, og beviser at læringen går gjennom filsystemet.

Terminaldørene

Avbryter inne i forsøksløkka på en kandidat validatoren alt godtok. Lukket vokabular: approve eller revise <tekst>. Et revise kjøper ett forsøk til, og preger ingen dom.

Planen, synkront

Et menneske signerer Magentic-planen før løkka kjører. Løkka venter. Derfor nekter den hostede flaten den: å blokkere en forespørsel på et menneske blokkerer også /readiness.

Før pengene

En offline hva-hvis over modell-mappet og prislista, i hele øre. Null modellkall. Kjøringskost blir en beslutning i forkant, ikke en overraskelse etterpå.

Run A tom wiki, uinformert hypotese validatoren falsifiserer første krav det refinerte kravet validerer personaen feller sin dom Steg 8 · promotion promote_verdict skriver en fil i bundlen seed_store_from_bundle leser den igjen markør 1: promoter, seed på nytt, fold Steg 7 · innboksen write_verdict legger {id}.json i mappa load_verdicts_from_dir fletter den inn markør 2: skriv fil, flett, fold Run B samme base, seedet på nytt begge markørene står i generasjons- prompten før hypotesen finnes marker_in_run_a_prompt == False · marker_in_run_b_prompt == True inbox_marker_in_run_a_prompt == False · inbox_marker_in_run_b_prompt == True
To markører, med vilje forskjellige. Begge veiene ender i den samme prompten, og én delt markør ville latt den ene bære beviset alene. Ingenting krysser i minnet.

Kilde: src/portfolio_optimiser/simulation.py:124-128, :505-525 · proposal_review.py:23-27, :248-305 · hosting.py:200-205 · costsim.py:1-16 · README.md:114-118

36Del 4 · Arbeidsflyten og sløyfene

De lange sløyfene krysser prosessgrenser, og derfor krysser de alltid som filer.

Hvem handler, og når Hva som krysser grensen Kjøringen slutter rammeverket skriver utboksen og dør sekunder til minutter etter start {run_id}-outcome.json byte-deterministisk, med verdict key nøkkelen en framtidig dom vil lande under Fagpersonen svarer leser utboksen, skriver én JSON-fil i innboksen dager til uker senere, i en helt annen prosess innboks/{id}.json én fil per dom, tolerant lest: en halvskrevet fil hoppes over systemet leser innboksen, eksperten skriver den Neste kjøring fletter innboksen inn FØR Steg 1-folden en prosess som aldri så den forrige kjøringen Relevant prior verdicts (ExpeL few-shot) de tre nærmeste dommene, rangert strukturelt limt foran hypotese-prompten Promotion-porten eksplisitt, fail-closed, aldri koblet inn i kjøringen dager til måneder senere promoted-verdict-*.md type: verdict, med nøytral lenke fra index.md etiketten bærer ingen dom, ellers ville signalet lekket rundt porten Virkeligheten kontrakten ble endret, fakturaen kom lavere måneder senere, skrevet av et menneske savings-ledger.json LedgerEntry i hele øre, aldri skrevet av rammeverket et validert forslag er en påstand, en ledger-rad er et resultat
Ingen av de fem lever i samme prosess som naboen. Systemet må være fullt gjenopptakbart mellom kjøringer atskilt i tid, uten antakelse om en levende sesjon.

Kilde: shared/method-spec.md §3 Steg 7-8, §5, §6 · src/portfolio_optimiser/run.py:1180-1187 · verdicts.py:214-240, :673-678, :732-736 · ledger.py:216-226 · README.md:231-237

37Del 4 · Arbeidsflyten og sløyfene

Ingen steder i kjørestien er et menneske påkrevd, og kjøringen sier det høyt når ingen dømte.

Sted i arbeidsflytenMenneske?DøraHva skjer uten
Steg 1 til 6, hele kjøringenNei. Ved validatoren og checker-porten er mennesket fraværende ved konstruksjoningenkjøringen fullfører og skriver no expert verdict given; verdict key=…
Review av et validert forslagValgfritt--proposal-reviewingen review, og intet ekstra forsøk kjøpes
Plan review under --explorePåkrevd når den er slått på--plan-reviewinput som slutter uten svar er en feil, aldri et samtykke
Dom på denne kjøringenValgfritt--decision + --rationaleingen dom registreres; halve nektes ved navn
Dom dager senereValgfrittinnboks-mappaneste kjøring folder ingenting nytt inn
Promotion inn i wikienPåkrevdpromote_verdictwikien vokser ikke; ingenting krysser porten
Realisert besparelsePåkrevdledger.realize--report nekter: savings ledger not found

De to halvdelene. --decision og --rationale går sammen eller ikke i det hele tatt. Fram til 1.1.0 defaultet --decision til approved, så hver flaggløs kjøring registrerte en godkjenning ingen hadde gitt. Den defaulten er borte.

Status, uten pynt. Ingen ekte ekspert-dom har kommet inn i treet ennå. Hver seedet dom er en syntetisk, AI-forfattet seed som er merket som det. Én live-kjøring mot et ekte endepunkt har fullført, den 14.08.2026: første forsøk døde på rundetaket (BudgetExceeded, rounds 12/13), gjenkjøringen konkluderte korrekt i rejected. Ingen kjøring har ennå produsert et validert forslag mot en levende modell.

Kilde: README.md:172-177, :231-237, :281-297, :545-556 · src/portfolio_optimiser/run.py:746-758, :3162-3176 · ledger.py:213-218 · verdicts.py:706-708 · docs/ekspert-svar.md:7-11

38Del 4 · Arbeidsflyten og sløyfene

Begrepene er ikke våre, de står ordrett i spec-en som begge implementasjonene deler.

Lagene og læringen
BegrepOrdrett fra shared/method-spec.md
Context layer §2«one curated, version-controlled knowledge bundle per project (an 'LLM wiki') in the open OKF format»
Output layer §2«a run-scoped folder structure of raw results … Append-heavy, never part of the wiki.»
Promotion gate §2«the only path from the output layer into the context layer. Only expert-approved knowledge crosses it.»
IR §2, §7.1«the typed intermediate representation of a candidate measure»
Store §2, §4.2«the in-memory collection of historical verdicts retrieval ranks over»
Fold §2, §3 Steg 1«the injection of retrieved prior verdicts into the hypothesis prompt»
Portene og reglene
BegrepOrdrett fra shared/method-spec.md
Two falsifiers §2, §9«the deterministic validator (numbers) and the debate checker (reasoning) — they judge the same candidate and are never conflated»
Mandate §3«one project, plus the objective its candidates are generated against»
Navigate, never stuff §3 Steg 1«MUST be built by navigating its OKF bundle with progressive disclosure — never by stuffing the whole bundle … into the prompt»
Refinement §3 Steg 5«Only the reason is carried — never the prior proposal JSON»
Long loop §3 Steg 7«The system MUST be fully resumable across runs separated in time — no live-session assumption.»
Fail-closed §6«a verdict whose decision is not in {approved, approved_with_adjustment} MUST be refused with an error, writing and linking NOTHING.»

Hvorfor ordrett. Spec-en ligger i shared/, som er et git subtree delt med søsken-implementasjonen på Claude Agent SDK. Et begrep som glir her, glir i to stacker samtidig. Derfor siteres definisjonen med paragrafnummer, ikke parafraseres.

Kilde: shared/method-spec.md §2, §3, §6, §7.1, §9 · shared/README.md

39Del 4 · Arbeidsflyten og sløyfene

Hver artefakt en kjøring legger igjen, mater nøyaktig én senere sløyfe.

Filen som blir liggende igjen Tidsavstand Sløyfa den mater {run_id}-outcome.json dager hitl pending: fagpersonens kø utboksens nøkler minus innboksens, én rad per vurdert approach {run_id}-coverage.json uker v1-gaten hvorfor hver bestilt approach endte som den gjorde {run_id}-plan-review.json dager den parkerte plan-reviewen svaret matches på request_id: et svar på et annet spørsmål nektes {run_id}-debate.json timer stress-judgen hvilke dokumenter debatten åpnet. Tom liste ER signalet. innboks/{id}.json neste kjøring ExpeL-folden i Steg 1 flettes inn i storet før hypotesen finnes, aldri etterpå savings-ledger.json måneder --report per prosjekt og portefølje, overlapp talt én gang
Utboksen og innboksen skal være to forskjellige mapper. Eierskapet er motsatt: systemet skriver utboksen, fagpersonen skriver innboksen.

Kilde: src/portfolio_optimiser/outbox.py:106-148, :206-244, :321-349, :378-412 · hitl.py:159-165, :221-259 · value_report.py:48-87 · docs/ekspert-svar.md:100-104

Del 5 · MAF-harnessen

Harnessen: Microsoft Agent Framework

Rammeverket under rammeverket. Hva MAF er, hvilke byggeklosser det tilbyr, hvilke av dem dette repoet faktisk tok i bruk, og hvilke det lot stå — med grunnen skrevet ned hver gang.

40Del 5 · MAF-harnessen

MAF er Microsofts egen arvtaker etter Semantic Kernel og AutoGen, og Python-linjen er GA.

Ett SDK for agenter og multi-agent-arbeidsflyter i .NET, Python og Go. Python-pakken agent-framework 1.0.0 kom 02.04.2026. Det distribueres som en pakkefamilie på 39 underpakker, ikke som ett hjul.

Learn, ordrett: «Semantic Kernel and AutoGen pioneered the concepts of AI agents and multi-agent orchestration. The Agent Framework is the direct successor, created by the same teams.»

agent-framework (meta) drar core[all] og dermed beta-integrasjoner — bevisst ikke brukt her agent-framework-core 1.18.0 · pinnet >=1.18.0,<2 -openai 1.14.3 OpenAIChatCompletionClient -foundry 1.13.0 FoundryChatClient -orchestrations 1.1.1 GroupChatBuilder, MagenticBuilder + 35 andre devui, mem0, redis, anthropic, a2a, postgres, qdrant … ikke her
Alle har prefikset agent-framework. Blått = pinnet og installert i dette repoet; grått = finnes, men er ikke en avhengighet her.

Kilde: pyproject.toml:8-31 · uv.lock · learn.microsoft.com/agent-framework/overview/#why-agent-framework · pypi.org/project/agent-framework-core/

41Del 5 · MAF-harnessen

Fem lag er i bruk på default-stien, to er opt-in, og to står helt urørt.

brukt på default-stien opt-in bak flagg eller env-variabel finnes i MAF, ikke brukt her AGENT / CHATCLIENT modellkall og samtaletilstand Agent AgentSession OpenAIChatCompletionClient FoundryChatClient TOOLS data inn i modellen @tool / FunctionTool MCPStdioTool MCPStreamableHTTPTool MCPWebsocketTool hosted tools ORCHESTRATIONS ferdige samarbeidsmønstre GroupChatBuilder MagenticBuilder SequentialBuilder ConcurrentBuilder HandoffBuilder WORKFLOWS (GRAF) eksplisitt kjørerekkefølge WorkflowBuilder Executor, add_edge FileCheckpointStorage ctx.request_info MIDDLEWARE avskjærer hvert kall ChatMiddleware FunctionMiddleware AgentMiddleware MiddlewareTermination CONTEXT PROVIDERS minne inn i prompten ContextProvider HistoryProvider Mem0 / Redis OBSERVABILITY spor, logger, metrikker configure_otel_providers OTLP-eksportører get_tracer() HOSTING agenten som container Foundry hosted agents Workflow.as_agent() ResponsesHostServer DEVUI utviklings-UI, ikke produksjon devui
ctx.request_info er koblet, men slått av: 0 kallsteder i src sender True, så biblioteket er eneste dør. Navnene er de gjeldende etter GA-omdøpingen i april 2026; ChatAgent, @ai_function og setup_observability() finnes bare i den etterslepende API-referansen.

Kilde: learn.microsoft.com/agent-framework/workflows/orchestrations/ · …/agents/observability · …/support/upgrade/python-2026-significant-changes · docs/2026-09-11-p12-maf-gjeld.md § 3

42Del 5 · MAF-harnessen

Av alt MAF tilbyr, er det to orkestreringsbyggere og én chat-klient-søm som bærer hele kjørestien.

MAF-elementBrukt?HvorHvorfor
GroupChatBuilderja, hver kjøringworkflow.py:104-113Maker-checker er debatt-default. selection_func sykler proposer → checker; output_from=agents løfter begge utdataene ut
Agentjaworkflow.py:63To deltakere, bygget friske per kjøring sammen med sine klienter (B7)
OpenAIChatCompletionClient · FoundryChatClientja, én per profilbackends.py:150, :160-164Sømmen mot leverandøren. Chat Completions og ikke streaming, fordi usage da fylles ut None-trygt
FunctionTool / @toolja, defaulttools.py:24, :46Datasømmen i kjørestien er in-process. Ingen nettverkskall for å nå kunnskapsbasen
ChatMiddlewarejabudget.py:228BudgetMiddleware leser usage etter hvert kall og kortslutter med BudgetExceeded; strict_usage gjør manglende usage til hard feil
MagenticBuilderopt-in, --exploreexplore.py:1816-1822Utforskning ligger OVER åttestegs-sløyfen, aldri inni debatten
MCP-verktøyopt-in, --mcp-configmcp_tools.py:154, :170Eksterne servere blir verktøy agentene kan kalle under debatten. Allowlist er påkrevd
configure_otel_providersopt-in, PORTFOLIO_OTELtracing.py:160Av betyr at MAF aldri kalles: et kall med tom eksportørliste ville likevel installert providere og lest hver OTEL_EXPORTER_OTLP_*-variabel

Én rad er halvveis. ContextProvider er med, men den bærende bruken er format_fewshot() strengkonkatenert inn i prompten (run.py:1557), ikke MAFs egen injeksjon. Å lukke det står som åpent punkt F3.

Kilde: src/portfolio_optimiser/{workflow,backends,tools,budget,explore,mcp_tools,tracing}.py · docs/invarianter.md

43Del 5 · MAF-harnessen

Porten som avgjør verdien står utenfor grafen, og det er derfor grafen kan være så liten.

Grafen
  • WorkflowBuilder: 0 treff i src. De eneste Workflow-objektene er de to byggerne bygger selv
  • Sequential / Concurrent / Handoff: portefølje-fan-out er håndskrevet asyncio.gather
Debatten
  • Magentic er ikke debatt-default. shared/method-spec.md §3 er normativ og eies av commons
  • Ingen blokkerende middleware. Primitivet finnes i installert core; porten er deterministisk Python utenfor grafen
Flatene
  • Workflow.as_agent(): et bygget workflow er engangs (målt klientkall-serie [2, 0, 0]), og det ville servert ugatede forslag
  • SkillsProvider, DevUI, OTLP-eksportører: egen laster, ingen prod-UI, og eksportørene er egress i et hjul
MAF: GROUPCHAT-GRAFEN Agent «proposer» Agent «checker» BaseChatClient · tools · middleware alt agentene trenger for å snakke, telle og hente forslaget validator.py deterministisk og MAF-fri: importerer ikke agent_framework validated · rejected · unsupported ledger + value report claim mot result, målt i ettertid
Elleve moduler, blant dem validator.py, ir.py og okf.py, sier i sin egen docstring at de aldri skal importere agent_framework. tests/test_okf.py::test_okf_is_maf_free gjør påstanden til noe som kan feile.

Kilde: src/portfolio_optimiser/hosting.py:19-23 · docs/2026-09-11-p12-maf-gjeld.md § 3 (U1, U2, U5, U8) · docs/invarianter.md:32

44Del 5 · MAF-harnessen

Hele leverandørvalget er én funksjon som returnerer en MAF-chat-klient; resten av koden ser ingen forskjell.

get_backend(profile) returnerer alltid en BaseChatClient profile=local profile=azure LocalBackend OpenAIChatCompletionClient Chat Completions, ikke streaming: usage fylles ut None-trygt og Responses-klienten slapp et verktøy på /v1 http://127.0.0.1:11434/v1 loopback, aldri en fjernvert. Konstruksjonen er offline: ingen nettverkskall før en agent faktisk kjører Ingen egress i lokal profil AzureFoundryBackend FoundryChatClient(endpoint, model, credential) endepunkt, i denne rekkefølgen, på VERDI og ikke deklarasjon: PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT → FOUNDRY_PROJECT_ENDPOINT → ValueError FOUNDRY_HOSTING_ENVIRONMENT satt og ikke tom? nei ja AzureCliCredential på en laptop ManagedIdentityCredential i en hostet container
Credential er alltid eksplisitt, aldri DefaultAzureCredential: Learn fraråder det for å unngå utilsiktet credential-probing, som ville gjort en konfigurasjonsfeil om til en treg feil.

Dokumentasjonen motsier seg selv her. Samme Learn-side sier både at plattformen injiserer «a nonempty value» og at variabelen er «set to 1». Repoet leser den på truthiness og ikke på tilstedeværelse, så en eksportert-men-tom verdi er et shell-uhell og ikke et hosting-signal.

Kilde: src/portfolio_optimiser/backends.py:28-30, :45, :49, :146-164 · docs/invarianter.md:183 · learn.microsoft.com/azure/foundry/agents/how-to/configure-hosted-agent-env-variables

45Del 5 · MAF-harnessen

Debatten har tre uavhengige stopp, og ingen av dem er modellens eget skjønn.

with_max_rounds(max_rounds) det harde taket (B4) — den bindende grensen termination_condition sikkerhetsnett godt over taket, ikke eneste stopp BudgetMiddleware → BudgetExceeded token-vakt på hvert kall, utenfor orkestreringen fresh_workflow(): friske agenter OG friske klienter per prosjektkjøring (B7) — et gjenbrukt workflow akkumulerer tråden selection_func sykler rollene Agent «proposer» foreslår ett tiltak Agent «checker» kritiserer og flagger brudd neste runde — og kun hvis ingen av de tre stoppene har slått til get_outputs() output_from=agents proposer-output videre til generering (F1) checker-verdict APPROVE | REJECT
Uten output_from=agents gir get_outputs() bare orkestratorens «reached max rounds»-melding. run.py skiller de to utdataene på author_name.

Kilde: src/portfolio_optimiser/workflow.py:78, :104-116 · src/portfolio_optimiser/budget.py:228 · kalt fra run.py

46Del 5 · MAF-harnessen

Magentic har ingen forsvarlige standardverdier, så kontrakten krever hvert eneste tak eksplisitt.

max_plan_revisions ikke et MAF-tak, men repoets eget: en plan-review-«revise» koster to manager-kall, emitterer ingen progress ledger og bruker ingen runde — rundetelleren ser den aldri ExplorationContract max_round_count max_stall_count max_reset_count enable_plan_review alle påkrevd, ingen har default MagenticBuilder deltakere: manager, navigator, hypothesiser defaulter selv til None = ubegrenset Mandate run_project(mandate=…) validate_proposal() nivå 2: hvert tall porteres her uansett. Utforskningen foreslår; den avgjør ingenting. hypothesiserens quick_validate er rådgivende, aldri provenance --plan-review synkron: et menneske eller en persona signerer planen før sløyfen kjører NEKTET på den hostede flaten --checkpoint-dir FileCheckpointStorage på BYGGEREN, målt: checkpoint_storage= på run() er ikke bærende park → spørsmålsfil → svarfil → resume
max_reset_count må være positiv, og det er en måling: grensesjekken er reset_count >= max_reset_count mot en teller som starter på null, så taket 0 er allerede nådd før første runde.

Hvorfor nektet hostet. Plan review er synkron og blokkerer sløyfen. En HTTP-forespørsel har ingen anmelder, så kallet ville hengt — og stoppet den samme event-loopen som svarer på /readiness. Operatørdøren er CLI-en.

Kilde: src/portfolio_optimiser/explore.py:63-117, :1816-1830 · src/portfolio_optimiser/hosting.py:200-205 · learn.microsoft.com/agent-framework/workflows/orchestrations/magentic

47Del 5 · MAF-harnessen

Default-sømmen gjør null nettverkskall, og hver utvidelse må navngi seg selv før den brukes.

I PROSESS — DEFAULT @tool / FunctionTool navigasjonsverktøyene mot kunnskapsbasen, pluss quick_validate ingen nettverkskall uten konfig er verktøylista uendret Egen kjørbar søm for kildedokumenter FØR kjøring: ingest_mcp.py. Samme protokoll, annen jobb. OPT-IN: --MCP-CONFIG MCPStdioTool MCPStreamableHTTPTool 1 · allowlist er påkrevd allowed_tools har min_length=1: en tom liste ville latt motparten bestemme hva agentene får kalle 2 · kunngjort før første kall hver server og hvert tillatt verktøy navngis i kunngjøringen, også uten mandat: ingen udeklarert egress 3 · --live-dry-run åpner ingenting og konfigen er extra="forbid", så en streifende literal hemmelighet er en nekt, ikke en ignorert nøkkel OPT-IN: PORTFOLIO_OTEL console skriver til stderr, slik at den byte-pinnede demoens stdout overlever otlp virker først når operatøren selv har installert en eksportørpakke console NEKTES når en OTLP-endepunktvariabel er deklarert i miljøet — fordi eksportører bygges fra de variablene ubetinget, og «bare konsoll» ville da vært løgn
MAF propagerer W3C traceparent inn i MCP-kall for klientåpnede transporter, som er nøyaktig de to som er tillatt her.

To detaljer som ikke er kosmetiske. Strukturert utdata sendes som en JSON-Schema-mapping og ikke som en Pydantic-klasse: gitt en klasse emitterer klientens konverter minimum, exclusiveMinimum, minItems og prefixItems, fire ting den publiserte strict-undermengden utelukker. Og den skriptede offline-klienten arver OpenAIChatCompletionClient framfor å være en frisk BaseChatClient, ellers ville BudgetMiddleware stilltiende blitt en no-op.

Kilde: src/portfolio_optimiser/mcp_tools.py:50-61, :237-241 · tracing.py:41-46, :63, :160-197 · generate.py:65-67, :219-235 · simulation.py:379-388

48Del 5 · MAF-harnessen

Pinnen er en snubletråd og ikke en frys, og tellingen av hva vi bruker råtner på fire dager.

23.06.2026 prior-art-registeret 19 MAF-kapabiliteter ført opp som USE-rader, U1 til U19. Dette er nevneren alt senere måles mot 25.08 → 29.08 misjonsgjennomgang 5 ja / 5 delvis / 9 nei av 19. Fire dager senere: 6 / 5 / 8, fordi checkpointing landet imellom 02.09 · F15 core 1.9.0 → 1.16.0 17 private former, 16 sjekket mekanisk, 2 endret. Vakten ble kjørt RØD først. Suite: 1089 passed / 5 skipped 11.09 · P12 re-måling på HEAD 6 / 5 / 8 uendret, rad for rad, over 109 commits. Av de seks ja-radene ligger ÉN på default-stien: GroupChat 12.09 · F16 core 1.16.0 → 1.18.0 16 former · 19 sjekker · 0 endret. Suite: 1577 passed / 5 skipped, begge gullfilene byte-uendret
Gulvet bor i én konstant, og pyproject-assertet utleder sin forventede streng fra den: to literaler for ett faktum driver fra hverandre, og et drevet gulv er en vakt som slutter å vokte uten en lokal diff. Orchestrations står på 1.1.1 fordi 1.1.1 fortsatt er siste utgivelse på PyPI.

Funn 99, 08.09 — den beste live-historien i repoet. Tre quick_validate-kall feilet mot en bundle-id modellen hadde funnet på. MAF gjør et kastende verktøy om til «Error: Function failed.» med detaljen undertrykt med mindre include_detailed_errors er satt, teller det som en feil, og stopper etter DEFAULT_MAX_CONSECUTIVE_ERRORS_PER_REQUEST = 3. Det ene nektet visste og modellen ikke, nemlig hvilke id-er som finnes, nådde den aldri. Fiksen: verktøyet returnerer nå et strukturert nekt som navngir id-ene.

Kilde: docs/2026-09-02-f15-maf-pinnen.md · docs/2026-09-11-p12-maf-gjeld.md · docs/2026-09-12-f16-maf-1180.md:21-22 · docs/2026-09-08-funn-99-chatclient-refusal.md:46-50 · tests/test_maf_version_guard.py:26-31

Del 6 · Kvalitetsmetoden og målt status

Kvalitetsmetoden og målt status

Et måleresultat er aldri et faktum om verden. Hvert tall i denne delen bærer nevneren sin, og der nevneren mangler står det «ikke målt» — aldri null.

49Del 6 · Kvalitetsmetoden og målt status

En load-bearing test er en test som FEILER når sømmen kobles fra.

Målingen er ikke at testen er grønn. Målingen er mutasjonen: koble sømmen fra i en kopi, kjør hele suiten, tell hvor mange tester som blir røde.

1 · Sømmen en beslutning som er tatt i koden 2 · Load-bearing test skrevet for å feile, ikke for å passere 3 · Mutasjon sømmen kobles fra i en kopi 4 · Hele suiten kjøres én mutasjon per kjøring RØD sømmen er vitnet: beslutningen kan ikke fjernes i stillhet GRØNN et FUNN, aldri en pass: fiks testen, eller uttal sømmen som uvitnet, med grunn Tre sømmer, tre tester, tellingen bak hver test_checker_gate_loadbearing.py Kobler checkeren fra utfallet. Rød på TO uavhengige punkter: reverter output_from=agents, eller fjern overstyringen. 2 armer test_ungiven_verdict_loadbearing.py Kobler dommen fra stillheten. Hver flaggløs kjøring myntet en ekspertgodkjenning ingen hadde gitt, og den reiste til neste prosjekt. 15 armer, 8 mutasjoner røde test_frozen_bundles_loadbearing.py Kobler målingen fra det frosne korpuset. Avvik er høylytt, fravær er en skip — de to tilstandene blandes aldri. 17 armer, 8 mutasjoner røde
Armtallene er lest med pytest --co mot hver fil 18.09.2026. Mutasjonstallene står i hovedbokens egne rader.

Fellen har et navn i repoet: «grønn-men-død». Fase 2 er der den ble funnet. run_portfolio hadde ikke parametrene bundle_dir/verdict_dir i det hele tatt, så ingen tidligere dom nådde noen hypotese-prompt, mens docstringen kalte det kryss-prosjekt-læring — og testen var grønn fordi den bare sjekket retrieved, aldri prompten.

Jernloven gjelder også vaktene selv. F15s versjonsvakt ble kjørt RØD først: 2 failed, så 4 passed. Og da v1-gaten ble uavhengig gjennomgått 17.09 overlevde 10 av 20 mutanter; etter herding var 20 av 20 røde.

Kilde: README.md:375 · docs/review-2026-07.md:57 · docs/invarianter.md:3093 · docs/2026-09-02-f15-maf-pinnen.md · STATE.md § Driftsmodell

50Del 6 · Kvalitetsmetoden og målt status

Hovedboken fører beslutningen, målingen som tvang den fram, og testen som går rød.

docs/invarianter.md er 3 176 linjer og 94 rader (talt 18.09.2026). Rader adresseres med grep på overskriften, aldri med linjenummer. Åtte av dem:

RegelenMålingen som tvang den framGår rød
To falsifiserere over én kandidat: validatoren gater tallene, checkeren gater resonnementet. De blandes aldri.Metodespeken §2/§6, steg 3 og 4test_checker_gate_loadbearing.py, rød på begge frakoblingspunkter
Gaten er forankret i prosjektets egen kostnadsbasis. Stadium 0 avstemmer mot CostBaseline før solveren. Validering, aldri reparasjon.Før S4.0 resonnerte hvert stadium bare om tall forslaget selv leverte, så en internt konsistent hallusinasjon klarerte hele gatentest_s40_cost_baseline_loadbearing.py, 6 mutasjoner røde
Et kravnummer er ikke en pris.P20 del B, 15.09: 10.4 er erklært ingen steder og står i 12 av 274 dokumenter. Ordrens egen regel ble målt falsk mot begge sine kjent-positivedocs/invarianter.md:2750
En ekspertdom kan ikke oppstå av stillhet. RunResult.verdict er Verdict | None.F2, økt 66: hver flaggløs kjøring myntet en godkjenning ingen ga, den gikk i det delte lageret, og den ble båret inn i neste prosjekts prompt — på flaten som ble overlevert 14.08test_ungiven_verdict_loadbearing.py, 15 armer, 8 mutasjoner røde
Penger kvantiseres i én rekkefølge, fra én kilde: per beløp, så summeres heltallene.Tre linjer à 60000,005 NOK er 18 000 003 øre kvantisert først, 18 000 001 summert først. En fiks på bare ett av de to kallstedene overlevde hele suitentest_money_quantization_loadbearing.py, 5 mutasjoner røde
Sporing er opt-in, og «av» betyr at MAF aldri kalles.Et kall med tom exporter-liste ville installert providers og lest hver OTEL_EXPORTER_OTLP_* i omgivelsene. «Av» må være fravær av kallettest_tracing_loadbearing.py, 9 mutasjoner røde
En nekt modellen skal kunne rette seg etter er en returverdi, aldri en raise.Funn 99, 08.09: MAF gjør en raise om til Error: Function failed., undertrykker detaljen og teller den mot grensen på 3 påfølgende verktøyfeil. Modellen gjettet på JSON-formatet, som var riktig hele veientest_run_cli_chatclient_refusal.py, 8 armer, 7 mutasjoner røde
Målingene leser en FROSSET kopi av korpuset, pinnet med sha256 — aldri et annet repos build-mappe.17.09 kl. 17:43: nabo-repoet bygde om r761-2025, gatens rad 6 og 7 ble IKKE MÅLT og fem tester falt, for en endring ingen her hadde gjorttest_frozen_bundles_loadbearing.py, 17 armer, 8 mutasjoner røde

Hovedboken ble flyttet 18.09.2026. CLAUDE.md hadde vokst til 310 919 byte mot Claude Codes kutt på 150 000 tegn, så omtrent halvparten av reglene nådde ingen økt. Etter flyttingen er fila 6 033 byte, og 93 rader står ordrett i hovedboken.

Kilde: docs/invarianter.md · STATE.md § NESTE · talt med grep -c '^- \*\*' docs/invarianter.md

51Del 6 · Kvalitetsmetoden og målt status

Kontekstkostnaden ble målt før noe ble bygget, og den formet navigasjonen.

list_bundles — katalogen 3 flate Vegnormal-baser, o200k_base docs/2026-08-26-katalogkostnaden.md før 112 116 tok etter 362 tok read_bundle — payloaden tunnel-basen, 5 kopier per kjøring docs/2026-09-02-read-bundle-kontekstkostnad.md før 12 595 tok etter 259 tok K2: listing i alt 629 konsepter; 89 % til 26 % av prompt-tokens docs/2026-09-03-hierarkisk-navigasjon-k2.md før 307 573 tok etter 12 595 tok bundle_context i debatten K2: 648 962 tokens × 3 kopier = 99,1 % av prompt-tokens docs/invarianter.md:1719 — etter: en O(1)-peker før 1 947 342 tok etter 753 tok
Hver rad er skalert mot sin EGEN «før»-søyle; radene er ikke sammenlignbare med hverandre. Søyler under 5 px er tegnet som 5 px.

Dette er ikke «−N % kostnad» for en kjøring. Det som er målt er hvert verktøys eget bidrag til prompt-tokens. En navigatør som går k nivåer ned betaler k listinger; gevinsten er at den betaler for det den valgte. Instrumentet ble validert mot et kjent-positivt først: det reproduserte commons sine egne publiserte tall eksakt (3 861 / 10 406 / 12 595).

Kilde: docs/invarianter.md:1027, :1104, :1719 · docs/2026-08-26-katalogkostnaden.md · docs/2026-09-03-hierarkisk-navigasjon-k2.md

52Del 6 · Kvalitetsmetoden og målt status

Mekanismen er vist. At metoden gir verdi, er ikke vist.

Vist, med nevner Sløyfa lukkes ende til ende offline, mot en skriptet stand-in-klient Gaten biter 18 av 20 rader avvist i runde 1, hver grunn sann Én levende kjøring, 14.08.2026 gpt-4.1-mini på Foundry; 1. forsøk RC=1 BudgetExceeded, gjenkjøring rejected Ingest-goldens er bit-identiske fil/CSV og SQL, på begge stacker 1 995 tester samlet 18.09.2026 179 testfiler, 124 av dem load-bearing Mutasjonene er talt, ikke antatt en grønn mutasjon er et funn, aldri en pass Ikke vist Ingen validert forslag mot en levende modell null, målt ved misjonsreview 25.08 Ingen ekte ekspertdom i treet hver seedet dom er merket AI-forfattet syntetikk Ingen base fra en levende kilde ingest er kjørt mot fixtures og en lokal mock Claude SDK-søskenet er parkert ikke utviklet parallelt Ingen stressrunde er et utvalg én modell, ett deployment, én kjøring per sett Ingen faktura er lest hvert NOK-tall er et listepris-anslag
Testtallet er målt i denne økten med uv run pytest -q --co (kun innsamling; suiten ble ikke kjørt). STATE fører hovedtreet som 1 984 passed / 5 skipped / 5 xfailed.

To forsøk samme dag, begge dokumentert. Måleprotokollen fra 14.08 fører første forsøk som RC=1 med BudgetExceeded på rundetaket (rounds 12/13), som en uhåndtert traceback; det ble senere den hostede 429-armen. Gjenkjøringen samme dag (protokollens § 6) konkluderte i rejected: validatoren tok en kostlinje modellen hadde funnet på, på toleranseporten fordi bundlen ikke bar noen baseline. README beskriver gjenkjøringen. Kilde: docs/2026-08-14-fase1b-forste-levende-kjoring.md:35-60 og :189-224.

Kilde: docs/2026-08-14-fase1b-forste-levende-kjoring.md:36, :55 · README.md:281, :284 · docs/2026-08-25-fable-misjonsreview.md:21, :22 · docs/2026-09-14-p16-stressrunde-1.md:178

53Del 6 · Kvalitetsmetoden og målt status

Seks betalte runder flyttet fasit-treffet fra 0 av 26 til 3 av 20 — og nevneren skiftet underveis.

P16 · runde 1 14.09.2026 0 av 26 fasit-konsepter åpnet 2 679 305 tokens Tre av sju kjøringer døde på rundetaket. Dokumentert kommando kunne ikke kjøres. P18 · runde 2 14.09.2026 0 av 26 fasit-konsepter åpnet 289 054 tokens −89 % tokens mot runde 1, uendret treff. 0 av 5 døde på taket. P19 · runde 3 15.09.2026 1 av 32 fasit-konsepter åpnet 539 207 tokens (+1 umålt) F1: klausulnummeret 10.4 ble validert som kostkode. Rundetaket kappet 5 av 5. P20 · runde 4 15.09.2026 2 av 32 fasit-konsepter åpnet 896 492 tokens --max-rounds 5 var den største enkelteffekten: 0 av 6 traff taket, eget forslag vurdert 6 av 6. ulik nevner P21 · runde 5 15.09.2026 1 av 20 grunnet, per tilnærming 1 130 145 tokens Første FORANKREDE runde: 0 av 20 validert. a4 bestod 5 av 5, men gaten nektet 20 av 20 — armen skilte ingenting. P22 · runde 6 16.09.2026 3 av 20 grunnet, per tilnærming 913 320 tokens Validert 10 av 20, priset 16 av 20 — men must_refuse falt til 2 av 5: ingen stage stiller falsifiseringsspørsmålet.
Runde 1–4 teller fasit-KONSEPTER åpnet (dokumenter i fasit.must_cite). Runde 5–6 rapporterer i stedet grounded per TILNÆRMING, 20 approach-rader. De to nevnerne er ulike og tallene er ikke direkte sammenlignbare; de står som publisert. Runde 3 er publisert både som 1 av 32 og som 1 av 26 pluss P17bs 0 av 6. Og en sammendragslinje i STATE blander de to målene: «0/26, 0/26, 1/32, 2/20, 1/20, 1/20» har skiftende nevner, og den siste verdien tilhører named, ikke grounded — runde 6 leser grounded 3 av 20. Konklusjonen overlever korreksjonen; aritmetikken i linja gjør det ikke.

«N100 = FERDIG» fra økt 102 er motsagt av P16 til P22. Mekanikken virker og navigerer ekte korpus; de faglige treffene kommer ikke. Ingen faktura er lest i noen runde, så hvert NOK-tall er et listepris-anslag. Og som P22 selv skriver: at must_refuse falt fra 5 av 5 til 2 av 5 er ikke bevis for at systemet er blitt verre, det er bevis for at den forrige målingen ikke kunne skille.

Kilde: docs/2026-09-14-p16-stressrunde-1.md · -p18- · -p19- · -p20- · -p21- · docs/2026-09-16-p22-stressrunde-6.md · STATE.md

Del 7 · Etter v1.1

Etter v1.1

v1.1.0 ble sluppet 14.08.2026, og det er den siste utgivelsen. Alt etter den datoen er måling. Denne delen sier hva planen kaller destinasjonen, hvor langt unna vi er, og hva som med vilje ikke skal bygges.

54Del 7 · Etter v1.1

Destinasjonen er ikke en tag. Den er én rapport en fagperson har rettet tre ganger.

Ferdig-kriteriet, ordrett fra PLAN.md § Destinasjonen: «Én ekte kostnadsrapport for shared/examples/bygg-energi-mikro, laget gjennom tre tilbakemeldingsrunder med operatøren som fagperson. Hver runde endret rapporten målbart, og i runde 3 kunne fagpersonen beholde minst 80 % uendret.»

Hvorfor det ble omskrevet

Økt 102 konkluderte «N100 = FERDIG». P16 til P22 motsa den. Seks betalte runder på syntetiske mandater, uten et menneske i sløyfa, produserte ingen rapport en fagperson har rettet.

Hva målingen faktisk viste

Mekanikken virker: stigen navigerer ekte korpus, gaten avviser med sanne grunner, og i runde 6 ble 10 av 20 tilnærminger validert. De faglige treffene fulgte ikke med: grounded nådde aldri over 3 av 20.

Hva som endret seg i planen

«v1» ble omdefinert fra en tag til en produkttilstand — repoet står allerede på 1.1.0. Kriteriet ble skrevet så det kan FELLES, og en ny syntetisk stressrunde 7 er eksplisitt forbudt.

En ramme planen ber om at ikke skjules: én person er bestiller, fagperson og operatør. «Det er en kjent begrensning.» Loggen i PLAN.md har åtte rader, og seks av dem sier «ingen bevegelse på kriteriet». En serie av dem er i seg selv et funn.

Kilde: PLAN.md § Destinasjonen, § Problemet vi løser, § Rammer, § Logg · STATE.md (local-only, ikke publisert)

55Del 7 · Etter v1.1

Avstanden måles av en gate som kjører offline og returnerer exit 1.

uv run python -m portfolio_optimiser.evals.v1_gate — deterministisk, ingen modellkall, ingen kvote. Hver nevner kommer utenfra det som måles.

rad krav i dag status 1 runder med ekte fagperson på bygg-energi-mikro 0 av 3 RØD 2 runder som endret rapporten målbart mot forrige 0 av 3 RØD 3 tilbakemeldingstyper med vei inn OG handling 3 av 8 RØD 4 runde 3-rapport beholdt: minst 80 % av innholdslinjene ingen rapport RØD 5 MAF-punkter i bruk, hvert med peker til en tilbakemeldingstype 3 av 8 RØD 6 forslag validert UTEN erklæring fra tilnærmingen prober 2 av 2 IKKE MÅLT 7 named — diagnose, ingen terskel, flytter aldri exit-koden 1 av 20 DIAGNOSE
Tre farger, ikke to. IKKE MÅLT feller exit-koden nøyaktig som RØD, og blir aldri rendret som null: rad 6s prober er grønne, men stressrunde 6s artefakter er eldre enn regelen — 16 erklæringer bærer ingen approach_id. Gaten skriver en attestering på hver kjøring: «rad 1–2 beviser ikke at en fagperson skrev feedbacken; det bekrefter operatøren.»

Kilde: src/portfolio_optimiser/evals/v1_gate.py:1-15 · PLAN.md § Ferdig-kriteriet · STATE.md § NESTE · gaten kjørt 18.09.2026 (exit 1)

56Del 7 · Etter v1.1

Åtte arbeidsstrømmer står mellom gaten og exit 0.

StrømStatusHva som gjenstår, måltKilde
Rad 2: bind runden til en KJØRING gaten selv kan verifiserePÅGÅROrdre i køen fra 17.09. PM re-målte 18.09: rad 2 ble 3 av 3 grønn på håndskrevne outcome.json og coverage.json pluss os.utime. «Kjøringen finnes» er implementert som «en fil med det navnet finnes», og det oppfyller touch.ordre …-1292712426
Rapportfila en fagperson faktisk leserPLANLAGTAvgjort 17.09: en ny, lesbar fil bygget fra utboksen. Den finnes ikke i dag, og verken --report eller proposal-filene er noe en fagperson leser. 80 %-regelen måles mot den.PLAN.md åpent spm. 2
Kapabilitetsordrer, én per rød rad og typePLANLAGTRad 3 står 3 av 8. Fem typer mangler vei inn: fjern en retning, lett på et krav, konseptgrafer, skills, inline kontekst. grep relax i src gir 0 mekanismer.PLAN.md § Nevnere
P22 funn 1: falsifiseringsarmens spørsmålÅPENT (operatørbeslutning)Ingen stage i gaten spør om kunnskapsbasen bærer et krav som støtter retningen. must_refuse 2 av 5 i runde 6. Anslått én økt.P22 § 4
named og terskelenÅPENT SPØRSMÅL1 av 20 fjerde runde på rad. Å be modellen gjengi ref gjør den til noe den blir BEDT om; derfor står raden som diagnose.PLAN.md åpent spm. 6
HostingBYGGET, IKKE TATT I BRUKInngangen finnes fra fase 4d: main.py rundt run_project på én asyncio-løkke, med readiness og invocations. Dockerfile og azure.yaml ble fjernet 14.08 til fordel for én startkommando, agent-framework-foundry-hosting er ikke installert i treet, og ingen deployment er målt.docs/invarianter.md:192
Prisskjemaet som tabell med kolonnerKRYSS-REPOObservasjon videresendt til llm-ingestion-okf, ikke bestilt: et prisskjema med kolonner ville latt --derive-cost-baseline forankre stadium 0. Det leverte skjemaet er uleselig i FORM, ikke fraværende i byte.S7c-målingen
MAF-gjelden: context provideren som ikke bærer noeUTSATTSkopet 29.08, ikke bygget. Strukturelt, ikke en enlinjes: context_providers er et konstruksjonsargument på Agent, mens proposeren kaller chat_client.get_response() direkte.docs/2026-08-29-maf-gjelden-omfang.md:26-38

En strukturell spenning — dette er en lesning, ikke repoets tekst (antakelse). Rad 6 kan bare måles mot artefakter yngre enn regelen, og ordren sier gaten ikke når exit 0 «før en ny stressrunde finnes». Planen forbyr samtidig en ny stressrunde på syntetiske mandater. Begge holder bare hvis runden som løsner rad 6 er én av de tre ekte fagperson-rundene. Det står ingen steder, og må bekreftes av operatøren.

Måle-hygiene som egen sak. Samme commit bærer tre suite-tall — 1 967, 1 962 og 1 917 — og ingen av dem lot seg gjenfinne. Regelen ordren setter er kort: før et suite-tall med kommandoen som ga det. Tallet i denne presentasjonen er 1 995 samlet, målt 18.09 med uv run pytest -q --co.

Kilde: PLAN.md § Ferdig-kriteriet, § Åpne spørsmål · STATE.md § NESTE · ordre 20260917T223645Z-1292712426 · docs/2026-09-16-p22-stressrunde-6.md § 4

57Del 7 · Etter v1.1

Listen over hva som ikke skal bygges er skrevet ned, og den er like bindende som kriteriet.

Bygges mot v1 Rapportfila, bygget fra utboksen avgjort 17.09; 80 %-regelen måles mot den Rundekatalogen, bundet til en ekte kjøring v1-rounds/ er gitignored; formen står i --help De fem manglende tilbakemeldingsflatene type 2, 4, 5, 6 og 8 Stadiet som stiller falsifiseringsspørsmålet P22 funn 1, anslått én økt Kostnadsmodell per analyse planen låser seg ikke til dagens ene modell Bygges ikke Ny stressrunde på syntetiske mandater og ingen niende tilbakemeldingstype Nye korpus: N400 og R762 basene publiseres ikke, ingen etatskontakt Compliance-funksjoner DPIA, ROS og behandlingsformål eies av den som deployer Reallokering mellom prosjekter besparelsene finnes INNI hvert prosjekt Autonom beslutning validatoren kan bare blokkere; å godkjenne er fagpersonens
Venstre kolonne er planens arbeid mot ferdig-kriteriet. Høyre kolonne er PLAN.md § Hva som IKKE skal bygges og § Rammer, pluss READMEs non-goals.

Fire forbud til, ført i STATE: Magentic er ikke debatt-default — utforskningen legges OVER Group Chat-sløyfa, aldri inni. git subtree push fra en konsument skjer aldri; shared/ er pull-only. MAF-evals og SkillsProvider er avvist som flate. Og på den navngitte ikke-bygg-lista: timeout-sømmen fra 1a, N101, oppslagsdøra --question og --docs-dir-omveien.

Kilde: PLAN.md § Hva som IKKE skal bygges, § Rammer · README.md:265 · STATE.md § Låste beslutninger, § NESTE

58Avslutning

Hele kjeden kan gås offline: sju kommandoer, null nettverk, null kostnad.

1 · Se kunnskapsbasen ls shared/examples/bygg-energi-mikro/ kuratert markdown, ikke en svart boks 2 · Læringssløyfen lukkes uv run portfolio-optimiser-demo Run A → ekspertdom → Run B, begge markører 3 · Egne skriptede svar run … --scripted-replies replies.json ender i ValidatedProposal 4 · Se den si nei claimed_saving_nok: 250000 Rejection, uansett hvor sikker proposer var 5 · Kostnad før du betaler costsim --projects 4 --profile local øvre grense per rolle og modell, kilde oppgitt 6 · Hele porteføljen run --portfolio --scripted-replies … 1 validated, 3 rejected: porten forankres per prosjekt 7 · Det som faktisk er realisert run --report --ledger savings-ledger.json leser en ledger du selv skrev; null modellkall Hver skriptet kjøring skriver et banner Det offline-beviset viser er at sløyfen lukkes og at porten biter. Det viser ikke hvor godt en gitt modell foreslår eller dømmer. En skriptet kjøring som leser som en modellkjøring ville vært verre enn ingen offline-modus. Derfor sier banneret det høyt. Deretter: egen bundle i --bundle-dir (trenger validator-input.json), så en lokal modell på loopback, så Foundry med minimal, målrettet verifisering.
De sju stegene i README-ens «Walk the whole chain offline», i den rekkefølgen de er ment å kjøres.
Terskelen er lav med vilje

Klon, uv sync, uv run pytest, og så de sju stegene. Ingen CI i organisasjonen: testsuiten fra en ren klone er verifikasjonen.

Wheelen er ikke nok alene

To git-pinnede sikkerhetskomponenter må oppgis ved siden av wheelen; pinnene bor i pyproject.toml.

Fork-and-own

Solo-vedlikeholdt, MIT. Issues er signaler; pull requests tas ikke imot.

Kilde: README.md «Install» og «Walk the whole chain offline» · pyproject.toml

59Avslutning

Helheten på én side: fem lag, tre porter, to mennesker, én sløyfe tilbake.

Mennesker, inn Fagfolk kuraterer kunnskapsbasen (OKF) Operatør bestiller: mandat + cost-baseline Ingest-laget materialiserer, content gate foran Opt-in: MCP-kilder, prepass-kutt, explore Maskinen, én kjøring (MAF GroupChat + deterministisk validator) Navigerindex.md først, fire verktøy Hypotese (IR)+ tidligere dommer foldet inn Debattproposer ↔ checker, rundetak Validatortall: portrekke, blokkerende Utfall + provenancevalidated · rejected · unsupported Refinement: avvisningsgrunnen inn i neste forsøk, maks 3, under token-tak {run_id}-*.json, deterministisk outbox Mennesker, ut (dager senere) Fagperson dømmer: {verdict.id}.json i innboks-mappe Promotion gate, fail-closed → type: verdict Realisert besparelse → ledger (menneske skriver) → --report Neste kjøring leser dommen som wiki-kunnskap: læring på tvers av kjøringer, aldri i minnet Tre porter som aldri slås sammen Validatoren gater tallene. Kan bare blokkere, aldri godkjenne på agentenes ord. Checkeren gater resonnementet. Promotion-porten gater hva som blir varig kunnskap: kun en godkjent dom. Ingen ubegrenset sløyfe: rundetak, forsøkstak, token-tak og stoppkriterier er påkrevd ved oppstart, og hver søm har en test som feiler når den kobles fra.
Samme figur som i Del 2, nå med alt du har sett i mellomtiden lagt inn.

Kilde: README.md «How it works» · docs/invarianter.md · src/portfolio_optimiser/run.py, validator.py, verdicts.py, ledger.py

Spørsmål

Spørsmål?

Alt i dette decket kan slås opp. Start her:

Repoet

git.fromaitochitta.com/open/portfolio-optimiser

README (engelsk), docs/invarianter.md (hovedboken), docs/extending.md

Den delte kjernen

git.fromaitochitta.com/open/portfolio-optimiser-commons

shared/method-spec.md, shared/ingest-spec.md, ekspert-persona som Agent Skill, eksempelbundle + golden suite

Harnessen

learn.microsoft.com/agent-framework/

Overview, orchestrations, observability, og «Python 2026 significant changes» for navneendringene

For fagfolk og ledere

docs/kort-presentasjon.html (12 slides, allmennpublikum)

docs/presentasjon-fagpersonens-bidrag.html, docs/presentasjon-bygge-kunnskapsbase.html

Decket er AI-generert (Claude Code) med menneskelig gjennomgang. Hvert tall bærer nevner og kilde; det som ikke er verifisert, står som det.