feat(explore): U4+U13 synkron - utforskningssloeyfa som mandat-former (ORDRE 20260823T185602Z) [skip-docs]

Magentic legges OVER den normative sloeyfa, aldri inni Steg 3: prompt +
kunnskapsbaser -> Mandate -> run_project(mandate=...) UENDRET. Manageren velger
VEI; det som forlater friheten er et Mandate, aldri et forslag. explore() skriver
ingenting - niva 3 (skriverettigheter) tilhoerer pipelinen alene.

Levert i denne oekten (kjernen; kallstedene staar til oekt 57):
- ExplorationContract: seks paakrevde felt uten default. max_reset_count=0 nektes
  paa en MAALING - reset_count >= max_reset_count mot en teller som starter paa 0
  terminerer kjoeringen FOER foerste runde med null ledger-events, altsaa en
  utforskning som utforsket ingenting, forkledd som en stall som aldri skjedde.
- explore() + fresh_exploration_workflow(): fersk builder per utforskning,
  BudgetMiddleware paa HVER agent inkl. manageren, synkron plan review via
  request_info, og max_plan_revisions som binder den ubundne revise-loekka.
- Tre kanaler: tokens OG runder raiser BudgetExceeded (rundene oversatt av vaart
  lag som kind="exploration_rounds", fordi orkestreringen maalt ikke raiser ved
  sitt eget rundetak); alt semantisk er en VERDI i stop.
- quick_validate (niva 1, raadgivende) + navigator-verktoey over safe_resolve.
- U14s tre utsatte events landet som span-events paa EN exploration-span.

Load-bearing MAALT mot HELE suiten, groenn kontroll 975/5, golden ea8c534
uendret: tolv mutasjoner alle roede. TO av dem falsifiserte testen foerst -
skrivefrihets-testen naadde aldri en verktoeykropp (ScriptedChatClient emitterer
ingen verktoeykall), og stdout-testens capsys er blind for ConsoleSpanExporter,
hvis out-default bindes ved modulimport. Begge er rettet; stdout-armen er naa en
subprosess, som er P4-presedensen.

[skip-docs] fordi flaten ikke er naabar for en bruker enna: --explore, det
whitelistede hosting-feltet og sim-scenarioet bygges i oekt 57, og en
README-oppfoering naa ville vaert en paastand om en inngang som ikke finnes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-23 22:39:00 +02:00
commit f0c54cc8dc
4 changed files with 1892 additions and 4 deletions

View file

@ -772,6 +772,70 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
fire-linjers stderr og `test_portfolio_cli_offline`s stille-pass — altså er omisjonen gatet av
uavhengige vitner) · detach demo-wiringen + detach CLI-wiringen (5 røde) · detach hosting-wiringen
(1 rød, KUN subprosess-testen — P4-presedensen).
- **Utforskningssløyfa er en MANDAT-FORMER, og de tre garantinivåene er strukturelle (U4+U13
synkron, økt 56):** `explore.py` legger en Magentic-manager OVER den normative sløyfa — `prompt +
kunnskapsbaser → Mandate → run_project(mandate=…)` UENDRET, Steg 3s maker-checker urørt (commons-eid
og normativ). Manageren velger VEI; det som forlater friheten er `mandate.Mandate`, aldri et forslag.
**Nivå 1** = `quick_validate`-verktøyet (SAMME `validate_proposal`, SAMME baseline, men rådgivende —
når ALDRI provenance); **nivå 2** = pipelinen som stempler; **nivå 3** = skriverettigheter, som kun
pipelinen har. `explore()` skriver INGENTING. **Mandatet bygges fra hypotesiserens MERKEDE turer**
(`HYPOTHESIS: {"label","rationale"}`), aldri fra sluttsvaret — sluttsvaret er RÅTT per design, og en
parser på det ville gjort det til et forslag. Markøren er dét som gjør fail-closed mulig: en umerket
tur er ikke en påstand (ingen stillhet å lukke), mens en MERKET-men-uleselig linje raiser
(`write_concept_file`-regelen). **Frø-approaches bevares ALLTID og FØRST** — også når sløyfa fant
ingenting og også ved stopp (§ C.6 dør 1 er en bevaringsregel, ikke en belønning for å bli ferdig).
**Tre kanaler, aldri én:** tokens OG runder raiser `BudgetExceeded` (rundene som
`kind="exploration_rounds"`, oversatt av VÅRT lag fordi orkestreringen MÅLT ikke raiser ved sitt eget
rundetak — den returnerer en kanonisk assistent-melding som ved transporten er uskillbar fra suksess),
mens alt semantisk er en VERDI i `stop` (S3.4-splitten: utmattelse og utfall er ikke samme sak).
Diskriminatoren mellom «nådde taket» og «ble kappet av taket» er SISTE ledgers
`is_request_satisfied` — samme felt orkestratoren selv forgrener på (`:1106`) — aldri
termineringsmeldingen, som er en inline f-string (`:1253`) uten konstant å pinne mot og som en modells
eget sluttsvar kan inneholde. **`speaker_known` sjekkes FØRST og slår alt annet:** en `next_speaker`
uten treff gir stille sluttsvar med NULL deltakerarbeid (`:1128-1131`), altså et plausibelt svar
ingen jobbet for (E2-klassen) — sløyfas egne funn holdes da tilbake, frøene ikke.
**Kontrakten nekter tre ting ved konstruksjon:** hvert av seks felt er PÅKREVD uten default (MAF
defaulter `max_round_count`/`max_reset_count` til ubegrenset, så et utelatt felt faller ikke tilbake
til noe forsiktig, men til dét `method-spec` §8 forbyr); `max_reset_count=0` nektes — **MÅLT**, ikke
resonnert: `reset_count >= max_reset_count` mot en teller som starter på 0 gjør at kjøringen
terminerer FØR første runde med kun `facts`+`plan`, null ledger-events og «maximum reset count», altså
en utforskning som utforsket ingenting, forkledd som en stall som aldri skjedde (`max_stall_count=0`
er derimot LOVLIG — strengt `>` gjør 0 til «reset ved første stallede runde»); og
`max_plan_revisions>0` med `enable_plan_review=False` nektes (en cap på en hendelse som ikke kan skje).
`max_plan_revisions` finnes fordi A3 MÅLTE at en `revise` koster 2 manager-kall, **null** ledger-kall
og **null** runder og så spør PÅ NYTT — under rundetaket alene er en alltid-reviderende ekspert
ubundet forbruk under vakter som alle ser tilfredse ut. Ved cap: typet stopp, ALDRI en påtvunget
approve (repair av et menneskes beslutning er den verste sorten). **U14s tre utsatte events er
landet** (`plan_created`/`replanned`/`progress_ledger_updated` som span-events på ÉN
`exploration`-span) — emisjon er UBETINGET og «av» betyr at OTel kaster dem, samme form MAFs egen
instrumentering alt har; en flagget emitter ville vært en andre oppløsning av regelen `tracing.py`
eier. Uttalt ærlighets-grense: eventene registreres når event-strømmen foldes, så REKKEFØLGEN er
tro og tidsstemplene er ikke øyeblikkene manageren handlet.
**Load-bearing MÅLT** (`tests/test_explore_loadbearing.py`, 32 tester), tolv mutasjoner alle røde mot
HELE suiten + grønn kontroll 975/5: detach rundetak-oversettelsen (1 rød) · test rundetaket FØR
tilfredsstillelse (1 rød — den motsatte feilen, som gjør en fullført utforskning til en budsjettfeil) ·
detach ukjent-taler-sjekken (1) · `BudgetMiddleware` av manageren, deltakerne beholder den (1) ·
detach revisjons-capen (1) · dropp frø-bevaringen (3) · la en ukjent-taler-kjøring levere funnene
videre (1) · detach alle tre span-events (1) · tillat `max_reset_count=0` (1) · gjør en merket-men-
uleselig hypotese tolerant (1) · la `read_bundle` skrive i basen den leser (1) · send spans til OTels
default-sink (2). **TO av dem FALSIFISERTE testen først, og begge er repoets vakuøs-gate-klasse:**
(i) skrivefrihets-testen drev kun `explore()`, men en `ScriptedChatClient` returnerer TEKST og
emitterer aldri et verktøykall — så ingen scriptet kjøring når en verktøykropp, og hele lesesømmen
(eneste sted en skriving realistisk kan komme fra) lå utenfor gaten; testen kaller nå hvert verktøy
DIREKTE. (ii) stdout-testen brukte `capsys`, men `ConsoleSpanExporter`s `out`-default bindes når
`opentelemetry.sdk.trace.export` FØRST importeres — under pytest er det stdout ved COLLECTION, som
`capsys` aldri ser; spans lå faktisk på stdout mens asserten var grønn. Dét er ikke en test-quirk å
omgå, det er nøyaktig faktumet U14 finnes for, og arven er P4-presedensen: **subprosessen er
målingen**. Begge armene kjøres nå i et barn (av: null trace-data noe sted; `PORTFOLIO_OTEL=console`:
spanet + `progress_ledger_updated`**stderr** og stdout tomt), med `EXPLORATION-OK` på stderr som
kontroll — uten den ville «stdout var tomt» vært like sant om et barn som krasjet ved import.
**Ærlighets-grenser, uttalt:** multi-base-dispatch (`Approach.bundle_id`, § C.7) venter til
`run_project` tar mer enn én `bundle_dir` — å shippe feltet før konsumenten er en form gjettet i
stedet for målt; `quick_validate`-dommene hypotesiseren så bor ikke i `ExplorationResult`, de er nivå
1 og hører hjemme i `{run_id}-exploration.json` som CLI-wiringen skriver; utforskningsrollene løses
via `resolve_model`s `default`-fallback til en operatør mapper dem eksplisitt; at en LEVENDE modell
kaller verktøyene er ikke bevist offline (samme klasse som structured-output-grensen). `--explore` i
`run.py`, `explore_prompt` i `hosting.py` og sim-scenarioet er IKKE bygget (økt 57).
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.