# MAF-utnyttelsesrevisjon U1–U15 + plan for Magentic-basert utforskningssløyfe > **Ordre** `20260823T125528Z-1876024635-from-.claude` · **Økt 53**, 2026-08-23 · Fable 5/xhigh uten > advisor (operatør-overstyring). Planarbeid: ingen kode i `src/`, ingen tester. **Hvert tall i dette > dokumentet kommer fra en kommando som ble kjørt denne økten** (vedlegg V1–V3); alt som ikke ble > målt er merket «ikke målt» eller «spike». ## 0. Sammendrag — de seks setningene som bærer resten 1. **Målingen:** av `.claude`s ni «null i src/»-påstander holder 7 ordrett; U5 og U11 trenger presisering (artefaktet/sitatbæringen finnes, MAF-konstruktet gjør det ikke). Nevner: 32 filer. 2. **Magentic-status:** hele Learn-siden hentet — **ingen experimental-advarsel for Python** (eneste «experimental» er et C#-pragma), og installert kode har ingen `@experimental`-markør på Magentic. F14-sitatet «untested … outside of the original Magentic-One design» **står fortsatt**. PyPI latest er 1.1.1 (Production/Stable), men bare 1.0.0/1.0.1 kjører på core 1.9.0. 3. **Designet:** Magentic legges **OVER** den normative 8-stegs-sløyfa, ikke inni Steg 3. Utforskningen er en **mandat-former**: prompt + bundles (+ fagpersonens frø-hypoteser) → `Mandate` → `run_project(mandate=…)` **uendret**. Validatoren gater hvert tall i nøyaktig samme gate som i dag. 4. **Fire ting ble MÅLT på installert 1.0.0, ikke antatt:** en Magentic-workflow er single-use (høylytt `RuntimeError`), men **builder og manager-instans blør** (4/4 kontaminering, worker hoppet over, fabrikkert sluttsvar); plan-review-rundturen **virker**, men har **ingen revise-cap**; `max_round_count` gir en **kanonisk streng, aldri et typet signal**; `MagenticResetSignal` **nullstiller ikke** deltakernes sesjon (orphan-attributt i 1.0.0). 5. **U14 er ikke «én funksjon»:** `setup_observability` finnes ikke; inngangen er `agent_framework.observability.configure_otel_providers`, og **`opentelemetry-sdk` er ikke installert** — kallet krasjer med `ModuleNotFoundError`. U14 er en avhengighetsbeslutning. 6. **Rekkefølgen:** S0–S6-spikes (1 økt) → U14 (1) → U4+U13 synkron (2) → U12 + asynkron U13 (1) → U15 som egen sammenligning (1). U1/U2/U5/U11 avvises med begrunnelse. Seks økter. --- ## A. U1–U15-måling (nevner: 32 `.py`-filer i `src/`; kontroll: 117 i `tests/`, 6 i `spikes/`) Metode: `grep -rEn --include='*.py' "" src` per kapabilitet, samme regex mot `tests/` og `spikes/` som kjent-positiv-kontroll, og — for hver regex som ga 0 i alle tre — en tredje kjøring mot installert MAF (`.venv/lib/python3.12/site-packages/agent_framework{,_orchestrations}`) som beviser at spørringen KAN finne (Verifiseringsloven ansikt 4). Skript og rå output: vedlegg V1. En uavhengig andre sveip (Opus-agent, 35 tokens, samme nevner) ga identiske nuller (V3). | ID | Kapabilitet (§15.1) | src | tests | spikes | venv-kontroll | Status | Belegg / spørring | |----|----|---:|---:|---:|---:|----|----| | U1 | `ConcurrentBuilder` fan-out | 0 | 4 | 4 | — (tests/spikes finner) | **IKKE** — erstattet av håndrullet bølge | `run.py:1262` `await asyncio.gather(` (D-D). Regex `ConcurrentBuilder\|BuildConcurrent\|FanIn` | | U2 | `WorkflowBuilder` graf | 0 | 0 | 0 | 21 filer | **IKKE** | Regex `WorkflowBuilder\|@executor\|class .*\(Executor\)\|WorkflowContext\|SwitchCase\|add_edge\|add_fan_out` | | U3 | `GroupChatBuilder` debatt | 10 | 7 | 7 | — | **LANDET** | `workflow.py:24` import · `:104` `GroupChatBuilder(` · `:108` `termination_condition=` · `:113` `.with_max_rounds(max_rounds)` | | U4 | Magentic | 0 | 4 | 9 | 4 filer | **IKKE** | tests-treff: `tests/spikes/test_b_footguns.py`, `tests/spikes/test_imports.py`, `tests/test_method_spec_loadbearing.py:40` (framework-navn-svarteliste for spec-prosa — ikke bruk). `.claude`s «kun spikes/b_footguns.py» er riktig for BRUK | | U5 | Agent Skills (`SkillsProvider`) | 0 | 0 | 0 | 3 filer | **IKKE** som MAF-konstrukt; **DELVIS** som artefakt | `persona.py:3`/`:47` leser `shared/skills/expert-reviewer/SKILL.md` via EGEN `load_persona_example`, aldri `SkillsProvider`. Installert core: `@experimental(feature_id=ExperimentalFeature.SKILLS)` på `SkillsProvider` (`_skills.py:1718`) | | U6 | MCP-tools | 6 | 6 | 0 | — | **LANDET** (opt-in) | `mcp_tools.py:42-43` import · `:154` `MCPStdioTool(` · `:170` `MCPStreamableHTTPTool(`. Ingest-sømmen bruker rå `mcp.ClientSession` (`ingest_mcp.py:144-179`) | | U7 | Function tools | 4 | 10 | 0 | — | **LANDET** | `datasource.py:18` `from agent_framework import FunctionTool, tool` · `:65` · `:69` `@tool(` | | U8 | Middleware | 16 | 20 | 0 | — | **LANDET** | `budget.py:27` · `:228` `class BudgetMiddleware(ChatMiddleware)` · `run.py:635/:645` | | U9 | `ContextProvider` | 8 | 11 | 4 | — | **LANDET** | `verdicts.py:29` · `:319` `class ExpeLContextProvider(ContextProvider)` · `run.py:724/:843` | | U10 | Vektorlager | 2 | 19 | 0 | — | **DELVIS** (egen, ikke MAF) | `semretrieval.py:355` `save_vector_store` · `:393` `load_vector_store` — numpy brute-force (D-C). 0 på `AzureAISearch\|cosmos\|qdrant\|redis\|VectorStore` | | U11 | Citation-aware RAG | 26 | 35 | 0 | — | **DELVIS** (egen, med MAF-typer) | `provenance.py:18` `from agent_framework import Annotation, TextSpanRegion` · `:78` `to_annotations()` («display only, NOT the source of truth»); `datasource.py:1` «citation-bearing data source». 0 på `AIContextProvider\|TextSearchProvider\|agentic_retrieval\|KnowledgeAgent` | | U12 | Checkpointing | 3 (kun prosa) | 0 | 0 | 14 filer | **FRAVALGT** — §A.1 | `run.py:22`, `workflow.py:14`, `:115`: «no checkpoint — research 01: durable resume is fragile». 0 kode-treff på `checkpoint_storage\|CheckpointStorage` | | U13 | HITL-gates | 3 | 0 | 0 | — | **DELVIS, og UØVD** | `workflow.py:116` `builder.with_request_info(agents=[agents[-1]])` bak `enable_layer1_hitl=False` (`:78`, `run.py:524`; ingen CLI-flagg). ENESTE test: `tests/test_workflow.py:54` `test_layer1_hitl_option_builds` — bygger, kjører aldri gaten, sender aldri et svar. `run.py` awaiter `debate.run` til ferdig (`:684-686`) og leser aldri strømmen. 0 på `RequestInfoEvent\|approval_mode\|MagenticPlanReview` i src OG tests | | U14 | OpenTelemetry | 0 | 0 | 1 | 4 filer | **IKKE** | Regex `opentelemetry\|setup_observability\|OBSERVABILITY\|get_tracer\|start_as_current_span`. `uv pip list`: KUN `opentelemetry-api 1.42.1` (transitiv) — ingen sdk, ingen exporter | | U15 | Evaluering | 0 | 0 | 0 | 2 filer | **IKKE** | Regex `evaluate_workflow\|LocalEvaluator\|Evaluator\|_evaluation`. `evaluate_workflow` finnes (`_evaluation.py:1833`) under `@experimental(feature_id=ExperimentalFeature.EVALS)` (`:1832`); 20 `@experimental` i modulen | Utenfor de 15 (samme kjøring): U16 `CompactionProvider` 0/0/0 (venv 4) · U17 `as_tool(` 0/0/0 (venv 1) · U18 `FoundryChatClient` **LANDET** `backends.py:150` · U19 Harness 0/0/0 (venv 1). **Mot `.claude`s ni null-påstander:** 7 av 9 holder ordrett (U1, U2, U4, U12, U13, U14, U15). 2 av 9 trenger presisering: **U5** og **U11**. De 6 landet-påstandene holder (U3, U6, U7, U8, U9, U10-delvis). Aliasing/re-eksport er utelukket: alle 14 `agent_framework`-importlinjer i `src/` er listet i V3 og hver er et direkte navn; `from agent_framework_orchestrations import` → 0 treff (importstien i bruk er `agent_framework.orchestrations`, `workflow.py:24`). ### A.1 U12 — fravalgt på et premiss som har råtnet, eller som holder? Premisset i koden: «research 01: durable checkpoint resume is fragile» (`workflow.py:14`). **«research 01» finnes ikke som dokument i repoet** (`grep -rn -i "research.01" docs/ STATE*.md` → 0). Nærmeste dokumenterte grunnlag: kapabilitetskartet (`docs/research/2026-06-24-maf-capability-map.md:54`): «superstep-granular (not mid-LLM-call); resume needs identical graph. Defer past MVP unless needed»; egnethetsplanen 08-09 P9/P10 («pass-nivå-gjenopptakelse er levert via spend-fila; ingen checkpointing»); `docs/extending.md:315` («A run either completes or is re-run; the async verdict inbox is the resumable boundary»). **Vurdering:** begrunnelsen holder **for dagens debatt** (sekunder–minutter; re-kjøring er billigere enn gjenopptakelse). Den holder **ikke** for målbildet: en utforskning som stopper i `MagenticPlanReviewRequest` og venter på en fagperson i dager kan ikke holde en prosess i live, og «re-kjør» kaster planen fagpersonen skulle revidere. **Premisset er ikke råttent — det er utenfor sitt gyldighetsområde.** Det som ble MÅLT i installert core 1.9.0 denne økten (V2 §7): mekanismen FINNES og dekker Magentic-tilstand — `FileCheckpointStorage` (`_checkpoint.py:239`), «restore then send» (`run(responses=…, checkpoint_id=…)` eksplisitt tillatt, `_workflow.py:877`), pending `request_info` re-emitteres ved resume (`_runner_context.py:414-426`), og `MagenticOrchestrator. on_checkpoint_save` skriver kontekst (runde/stall/reset-tellere), ledgere og managerens hele sesjon (`_magentic.py:1266-1284`, `:746`). «Skjør» er i dag konkret: base64-pickle av tilstand med allow-list (`_checkpoint.py:242-250`), graf-signatur må matche (`_runner.py:275-279`), og manager-state-feil ved lagring/gjenoppretting **svelges med warning** (`_magentic.py:1281-1282`, `:1320-1321`). Ingen ende-til-ende-resume med pending plan review er kjørt → **spike S4** (§D). --- ## B. Magentic-status mot primærkilden (hele siden hentet 2026-08-23) Kilde: `microsoft_docs_fetch` mot `learn.microsoft.com/en-us/agent-framework/workflows/orchestrations/magentic` — hele siden. 1. **Ingen experimental-advarsel for Python.** Eneste «experimental» på siden: C#-blokken `#pragma warning disable MAAIW001 // Magentic types are experimental` (.NET-diagnostikk). `.claude`s fravær er **bekreftet**, ikke bare ikke-motbevist. 2. **F14-sitatet står ordrett:** «it is untested how well the Magentic orchestration will perform outside of the original Magentic-One design», og tipset «consider using the Group Chat pattern instead» for enklere koordinering. F14s VURDERING var riktig lest; operatøren har flyttet PREMISSET (målbildet krever planlegging), ikke sitatet. G8 i §15.3 er halvt riktig: funksjonelle workflows er fortsatt `ExperimentalFeature.FUNCTIONAL_WORKFLOWS`; Magentic er ikke markert. 3. **Installert kode:** `agent_framework/_feature_stage.py:43-65` — `ExperimentalFeature` har 13 medlemmer (DECLARATIVE_AGENTS, EVALS, FILE_HISTORY, FIDES, FOUNDRY_TOOLS, FOUNDRY_PREVIEW_TOOLS, FUNCTIONAL_WORKFLOWS, HARNESS, MCP_LONG_RUNNING_TASKS, MCP_SKILLS, PROGRESSIVE_TOOLS, SKILLS, TO_PROMPT_AGENT). **Magentic er ikke der**; `grep -n -i "experimental\|feature_stage" agent_framework_orchestrations/*.py` → 0. 4. **PyPI** (`curl https://pypi.org/pypi/agent-framework-orchestrations/json`): latest **1.1.1** (2026-08-21), `Development Status :: 5 - Production/Stable`, 26 utgivelser. Krav per versjon: 1.0.0/1.0.1 → core ≥1.9.0; 1.0.2/1.1.0 → ≥1.13.0; 1.1.1 → ≥1.15.0. Core latest 1.15.0 (21.08). 5. **Installert = pinnet:** `uv pip list` → orchestrations 1.0.0, core 1.9.0; `uv.lock:61-63` låser 1.0.0; `pyproject.toml:17` `>=1.0.0`, `:12` core `>=1.9.0,<2` (`tests/test_maf_version_guard.py`). 6. **Learn-siden beskriver LATEST API** (`manager_agent=`, `enable_plan_review=`, `intermediate_output_from=`). Installert 1.0.0 har de samme konstruktør-navnene (V2 §2) — men builder-docstringen i 1.0.0 beskriver et HITL-API (`MagenticHumanInterventionRequest`, `with_human_input_on_stall`) som **ikke finnes i koden** (kun i docstrings, `_magentic.py:1388-1559`). Designet i §C er skrevet mot det som er målt installert, aldri mot docstring eller Learn. 7. **Core 1.12.0-notatet (21.07, samme minutt som orchestrations 1.0.1):** «Prevent the Magentic manager from duplicating conversation history (#6297)». Om 1.0.1 bærer fiksen er **ikke verifisert** — det er spike S0. --- ## C. Design: den utforskende sløyfa ### C.0 Beslutningen — Magentic OVER sløyfa, ikke INNI Steg 3 Kjernespørsmålet: Magentic gir manageren frihet til å velge vei; validatoren gater utfallet. Er de forenlige? **Ja — på én betingelse: utforskningen produserer KANDIDATER, aldri FORSLAG.** Tre fakta avgjør formen: 1. **Metodespesifikasjonen er normativ og commons-eid** (`shared/method-spec.md`, pull-only). §3 Steg 3 KREVER en to-rolle maker-checker-debatt, rundetak og fersk tilstand per kjøring; Steg 2 krever «exactly one candidate measure» per proposer-kall; §8 «Never an unbounded loop, anywhere». En Magentic-sløyfe som ERSTATTER Steg 3 bryter spec-en og krever commons-amendment FØR bygging — og den ville flyttet validatoren INN i grafen. Sømkartet (V3) er entydig: alt som gjør et svar etterprøvbart ligger UTENFOR workflow-grafen og ETTER `debate.run` — generering `run.py:748-759`, validator `generate.py:491`, provenance `run.py:793`, checker-override `:816`, dimensjonsgate `:826`, domfangst `:849`, outbox `:857`. 2. **`Mandate` finnes og ER «en prompt om hva som skal gjøres»** (`mandate.py:55-88`): `objective` + `approaches[{id, label, description}]` + `allow_own_proposals` + `success_criteria`. En `Approach.description` er «the expert's own prose reason … fed to the proposer VERBATIM» (`mandate.py:44-49`, `generate.py:308-317`) — altså «hypoteser fagpersoner kommer med». `_evaluate_mandate` (`run.py:292-344`) kjører hver approach gjennom `generate_via_llm` → `validate_proposal` under SAMME meter, og rapporterer `not_evaluated` for det budsjettet ikke rakk (aldri utelatt). `announce` (`mandate.py:130-183`) sier høyt hva kjøringen skal gjøre FØR første betalte kall — repoets eksisterende før-kjøring-menneskesjekk. 3. **Validatoren er ren og offline** (`validate_proposal`, `validator.py:194-260`; CBC via PuLP, `_MC_SAMPLES = 512`, ingen `agent_framework`-import) og kan eksponeres som `FunctionTool` (U7) uten å endre hva den er. Derfor: **utforskningen er en MANDAT-FORMER.** Inn: operatørens prompt + tilgjengelige bundles (+ valgfrie frø-approaches fra fagpersoner). Ut: en `Mandate` hvis `approaches` er hypotesene utforskningen fant verdt å teste, med managerens begrunnelse som `description`, og `bundle_id` per approach. Deretter `run_project(bundle_dir=…, mandate=…)` **uendret**: debatt (Steg 3) → generering (Steg 2) → deterministisk validator (Steg 4, blokkerende, stempler `validator_decision`) → checker-gate → outbox/HITL (Steg 6–8). Ingen linje i den kjeden endres. Manageren velger VEI i et rom som ikke kan skrive til outbox eller wiki; validatoren gater hvert TALL som forlater rommet, i samme gate som i dag. **Tre nivåer av garanti (uttalt, så ingen leser mer inn i det enn det er):** | Nivå | Hva | Hvor | Stempel? | |---|---|---|---| | 1 | `quick_validate`-verktøyet i utforskningen | samme `validate_proposal`, samme baseline, kalt av hypothesiser-agenten | **NEI** — rådgivende tall i en utforskningslogg, aldri provenance | | 2 | Pipelinen per approach | `run.py:748` `_evaluate` → `validate_proposal` | **JA** — `ProvenanceStamp.validator_decision` | | 3 | Skriverettigheter | kun pipelinen når outbox (`outbox.write_outbox`) og wiki (`promote_verdict`, gated) | utforskningens sluttsvar er RÅ (`{run_id}-exploration.json`), aldri et forslag | Ærlighetsregelen (§1) følger direkte: managerens «final answer» presenteres ALDRI som et resultat. Resultatet er det `_evaluate_mandate` returnerer. ### C.1 Manager + deltakersett (mot installert 1.0.0-API, V2 §2–3) | Rolle | Type | Gjør | Verktøy / kanal | |---|---|---|---| | **manager** | `MagenticBuilder(manager_agent_factory=…)` → `StandardMagenticManager(agent=…)` med EGNE ledger-prompter (konstruktør-parametre `task_ledger_*_prompt`, `progress_ledger_prompt`, `final_answer_prompt`) | planlegger, velger neste taler, fører progress ledger, replanlegger | ingen — men **`BudgetMiddleware` PÅ manager-agenten** (C.2) | | **navigator** | `Agent` | leser bundles PROGRESSIVT (§3 Steg 1) og svarer med sitater | `list_bundles()` · `read_bundle(id)` → `okf.navigate_bundle` + `Bundle.index_summary()`/`bundle_context` · `read_file(id, path)` innenfor `retrieval.safe_resolve` | | **hypothesiser** | `Agent` | former én kandidat om gangen som IR-JSON (gjenbruker `generate._build_messages` + `proposal_response_format()`) | `quick_validate(ir_json)` → `{decision, reason, p10, p50, p90}` (nivå 1) | | **expert_liaison** | menneske/persona | svarer på spørsmål, reviderer retning | plan review (`MagenticPlanReviewRequest.approve()/.revise(feedback)`) — og, etter spike S3b, `AgentApprovalExecutor` som deltaker for svar MIDT i kjøringen | **Hvorfor `StandardMagenticManager`, ikke egen subklasse:** egen manager = egen ledger-parsing = egen kopi av Magentic-One-logikken (kø-(p): den andre kopien drifter). Prompt-overstyring dekker behovet (norsk domene, IR-disiplin, «aldri oppgi et tall uten `quick_validate`»). **Målt grunn til å overvåke:** manageren parser ledgeren med en håndrullet JSON-skraper (`_extract_json`, `_magentic.py:406-449`, `ast.literal_eval`-fallback), 3 forsøk (`:588-590`, `:710-723`) → `RuntimeError`; `MagenticProgressLedgerItem.answer` er ikke typesjekket per felt (`:300-302`). Egen manager FØRST når en målt mangel krever det. **Hvorfor `manager_agent_factory=` og ikke `manager_agent=`:** målt E4 (V2 §6) — `manager_agent=` konstruerer manageren eagerly (`:1683`) og gir SAMME instans til hvert `build()` (`:1729-1730`); factory-varianten kalles per build (`:1732-1738`). ### C.2 Progress ledger som styringssignal, koblet mot `budget.py` Ledgeren (`_magentic.py:310-314`, navn verifisert): `is_request_satisfied` / `is_in_loop` / `is_progress_being_made` / `next_speaker` / `instruction_or_question`. Stall-regelen (`:1109-1118`): `not is_progress_being_made or is_in_loop` → `stall_count += 1`, ellers **dekrementer** (ikke nullstill); `stall_count > max_stall_count` (strengt `>`) → `_reset_and_replan` (`:1149`): kontekst nullstilles, `reset_count += 1`, replan, plan review på nytt med `is_stalled=True` hvis aktivert. To signaler, to vakter, aldri slått sammen (S3.4-invarianten): | Signal | Kilde | Vakt | Utfall | |---|---|---|---| | **Semantisk** — stall | ledger | `max_stall_count` → reset+replan → `max_reset_count` | `exploration_stop="stalled"` — strukturert; IKKE krasj, IKKE suksess | | **Ressurs** — tokens | `BudgetMiddleware` (`budget.py:228`) på HVER agent **inkl. manager** | pre-call-guard `:242-244` | `BudgetExceeded(kind="tokens")` → 429-kanalen | | **Ressurs** — runder | Magentic `max_round_count` (= `Budget.max_rounds`) | intern (`:1238`) | **oversettes** av vårt lag til `BudgetExceeded(kind="exploration_rounds", limit, observed)` | **Målt grunn til oversettelsen (E5, V2 §4):** `max_round_count`/`max_reset_count` gir **ingen exception** — en kanonisk assistent-melding «Workflow terminated due to reaching maximum round count.» + `_terminated=True` (`:1222-1256`), uten `prepare_final_answer`. Ved transporten er det uskillbart fra suksess. Vårt lag leser derfor `round_count` fra `MagenticContext` (eksponert via `PROGRESS_LEDGER_UPDATED`-eventene, ett per runde) og `orchestrator._terminated`, og produserer det typede stoppet SELV — slik `BudgetExceeded`/429 alt gjør for debatten. Strengen pinnes mot den installerte konstanten (`ROUND_CAP_LOGGER`-presedensen), aldri fri-tekst-matchet. **`tick_round` er IKKE koblet til orkestreringsrunder i dag** (sømkart: to kallere, begge i `generate.py:390/:455`; `run.py:632` gir meteret `max(max_rounds*4, 4)` for GENERERINGS-forsøk). Utforskningen får derfor sin egen `Budget` (tokens + runder) i en egen `TokenMeter`, og `run_project` sitt meter er urørt. Stall-telleren er IKKE en budsjettvakt og kobles ikke inn i `TokenMeter`. Hver runde logges (ledger + `next_speaker` + `instruction_or_question`) til `{run_id}-exploration.json`, så en stoppet utforskning er lesbar uansett hvilken vakt som fyrte. **⚠ Spike S2:** manageren er et `Agent` (`StandardMagenticManager(agent=…)`, `:528`; ett `create_session()` i `__init__`, `:572`; alle kall via `agent.run(…, session=…)`, `:601`), så agent-nivå `ChatMiddleware` SKAL fyre — men det er **ikke målt** for manager-stien, og en feil her gjør «harde token-tak» til en løgn for den mest pratsomme deltakeren. Kontroll: bar `BaseChatClient` no-op-er middleware (`simulation.py:373-375`) — spiken bruker `ScriptedChatClient`. ### C.3 Grensene — B4 bygges ikke bort | Dagens | Magentic (installert) | Regel | |---|---|---| | `with_max_rounds(max_rounds)` (`workflow.py:113`) | `max_round_count` (ctor-kwarg; default `None` = ubegrenset, `:542`) | PÅKREVD, positiv, fail-fast (G1/B4/A4) | | — | `max_stall_count` (default 3, `:540`) | PÅKREVD, positiv — ingen arv av defaulten | | — | `max_reset_count` (default `None`, `:541`) | PÅKREVD, ≥0 | | termination-safety-net (`:108`) | manageren avslutter ved `is_request_satisfied` (`:1104-1107`) **eller** ved ukjent `next_speaker` (`:1128-1131` — stille sluttsvar!) | rundetaket ER nettet; ukjent taler logges som avvik | | — | plan review: **ingen revise-cap, revises telles ikke som runder** (målt E6: 2 manager-kall per revise, 0 ledger-kall) | NY `max_plan_revisions` i kontrakten; hver revise `tick_round()`-es | `ExplorationContract` (pydantic, fail-fast, speiler `contracts.TerminationContract`): `max_rounds`, `max_tokens`, `max_stall_count`, `max_reset_count`, `max_plan_revisions`, `enable_plan_review`. Ingen defaults: en utforskning uten uttalte grenser nekter å starte. ### C.4 B7 — tilstandsisolasjon. MÅLT (V2 §6; skript i V1) | Eksperiment | Resultat | Konsekvens | |---|---|---| | **E1** én bygget workflow, to `.run()` | run 2 → `RuntimeError: This Magentic workflow has already been completed…` (`_magentic.py:914-918`), **0 modellkall** | single-use — STERKERE enn GroupChat 1.9.0s stille tomme kjøringer [2,0,0]; kan ikke korrumpere, for den kan ikke kjøre | | **E2** to workflows, ÉN delt `StandardMagenticManager` | run 2s manager-kall ser run 1s oppgave **4/4**; meldingstall vokser 2→…→23→**26**→39 over kjøringsgrensen; run 2s første ledger svarte `satisfied=True` og **workeren ble aldri kalt (0)** — et fabrikkert sluttsvar | ikke bare kost: **en plausibel fasit produsert av null arbeid** | | **E3** kontroll, fersk manager per build | **0/5** kontaminering, 5 kall inkl. `ledger_UNSAT` (workeren KALT) | 5-vs-4-kallformen er diskriminatoren | | **E4** én `MagenticBuilder`, to `.build()` | samme 4/4-blødning som E2 (`:1683`, `:1729-1730`) | «ny workflow» er ikke nok — **ny BUILDER per oppgave**, eller `manager_agent_factory=` | | **E7** `MagenticResetSignal` | `_magentic.py:1366` skriver `self._agent_thread` — lest av INGENTING (1 treff repo-vidt; den levende er `AgentExecutor._session`, 7 treff). Målt: cache/full_conversation tømt, **sesjonsidentitet uendret** | stall-replan gir fersk manager-ledger men **gamle deltakere**; docstringens løfte (`:1341-1344`) holder ikke i 1.0.0 | **Regel:** per utforskning **ny builder → ny build → én kjøring**, fersk klient per agent (som `fresh_workflow`/`_default_factory` alt gjør, `run.py:436-440`). Stol aldri på stall-reset for å tømme deltakerminne. `fresh_exploration_workflow()` speiler `fresh_workflow()` (`workflow.py:74`). ### C.5 HITL-spranget: fra JA/NEI til «be om svar, bruke svarene» **Installert flyt (målt E6, V2 §5):** `with_plan_review()`/`enable_plan_review=True` → `ctx.request_info(MagenticPlanReviewRequest{plan, current_progress, is_stalled}, MagenticPlanReviewResponse)` (`:1041-1048`) → event `type="request_info"` med `request_id` → kjøringen STOPPER uten output → `workflow.run(responses={request_id: req.approve()})` eller `req.revise("…")` (`_workflow.py:899-908`) → ved revise: feedback inn i historikken, `manager.replan`, `REPLANNED`-event, **ny review-forespørsel** (`:1014-1031`) → ved approve: løkka kjører. Dette er målbildets «be om svar, bruke svarene og jobbe videre» ordrett — og det virker på 1.0.0. **Ingen landingsplass i dag** (sømkart §4): `with_request_info` bygges men ingenting konsumerer strømmen; `hitl.py` er et offline inspeksjonsverktøy (id-join outbox − inbox, `hitl.py:159`), ikke en forespørsel/svar-mekanisme. Koblingen mot den asynkrone køa **gjenbruker formen, ikke filene:** | Eksisterende | Utforskningen | |---|---| | outbox `{run_id}-outcome.json` med `verdict_id` | **NY artefaktklasse** `{run_id}-plan-review.json` = `{request_id, plan, current_progress, is_stalled, checkpoint_id}` — samme RAW-lag, samme mappe, byte-deterministisk via `outbox._dump` | | inbox `{id}.json` (`verdicts.write_verdict`) | `{request_id}.json` = `{decision: approve\|revise, feedback}` — eksperten SKRIVER, systemet LESER (§3 Steg 7-rolledelingen) | | `hitl.py pending` (id-join) | samme join over den nye klassen; `python -m portfolio_optimiser.hitl pending` viser begge | | `FeedbackContract.decision ∈ {approved, rejected}` | **IKKE gjenbrukt** — en plan-revisjon er ikke en dom; eget lite pydantic-skjema | | `simulation`-personaen (`load_persona_example`) | spiller eksperten SYNKRONT i demoen: `revise("Test også …")` én gang, så `approve()` | To tidsskalaer, som Steg 7: **synkron** (persona/operatør i samme prosess — demoen og første leveranse) og **asynkron** (fil-innboks over dager → krever U12: `checkpoint_id` i artefaktet, `run(responses=…, checkpoint_id=…, checkpoint_storage=…)`). Det er DÉT som flytter U12 fra «NEI» til «nå» — og spike S4 avgjør om 1.9.0 bærer det. **Svar MIDT i kjøringen (ikke bare ved plan):** plan review er det ENESTE `request_info` orkestratoren selv reiser (4 treff i `_magentic.py`, 0 i basen). Men `AgentApprovalExecutor` (`_orchestration_request_info.py:168`, `propagate_request=True` `:201`) kan stå som DELTAKER: når manageren velger `expert_liaison`, blir dens output en `request_info`, og mennesket svarer med `AgentRequestInfoResponse.approve()`/`.from_strings(…)` (`:55-79`) — svaret blir deltakerens output i historikken. **Ikke målt** → spike S3b. Lander den, er «stille spørsmål … be om svar» dekket både ved plan og underveis. ### C.6 «Hypoteser fagpersoner kommer med» — tre inngangsdører, i kostnadsrekkefølge 1. **I mandatet (finnes):** `Approach{label, description}` → proposeren verbatim. Utforskningen tar frø-approaches som INPUT og **MÅ bevare dem i output-mandatet** (en hypotese fagpersonen ba om forsvinner aldri stille — `not_evaluated`-regelen anvendt på utforskning). 2. **I plan review (U13):** `revise("Test hypotesen at …")` → replan med teksten i task-ledgeren. 3. **Underveis (S3b):** `expert_liaison`-deltakeren — manageren SPØR, fagpersonen svarer, svaret blir kontekst for neste runde. 4. **I kunnskapsbasen (senere, commons-sak):** `type: hypothesis`-konseptfil navigatoren finner. Krever OKF-vokabular-beslutning hos commons — **ikke i denne planen**. ### C.7 OKF-bundles som inngang — hva manageren må vite Manageren planlegger mot en **katalog**, aldri innholdet (§3 Steg 1). Per bundle: `id` · `Bundle.index_summary()` (`okf.py:154`) · kandidat-features (`verdicts.bundle_candidate_features`, `:482`) · **om `cost-baseline.json` finnes** (`okf.load_optional_cost_baseline`, `:411`) — avgjørende: en uforankret bundle kan ikke quick-valideres mot stage 0, og manageren skal vite hvilke hypoteser som KAN testes deterministisk · antall `type: verdict`-filer (`Bundle.verdicts`). Navigatoren åpner én bundle om gangen på managerens instruks. **Multi-bundle er NYTT:** `run_project` tar ÉN `bundle_dir` (`run.py:514`; `bundle_dirs` → 0 treff); utforskningen tar `bundle_dirs`, hver approach bærer `bundle_id`, og pipelinen kjøres per bundle som i dag (`run_portfolio`-formen). ### C.8 Modulplassering (for byggeøktene, ikke for denne) `src/portfolio_optimiser/explore.py` (NY): `ExplorationContract`, `ExplorationResult{mandate, ledger_log, stop, plan_reviews}`, `fresh_exploration_workflow(...)`, `explore(...)`. Importerer `agent_framework.orchestrations` — **aldri** fra `okf.py`/`mandate.py`/`hitl.py` (MAF-fri-guarden `test_okf_is_maf_free`). Verktøyene (`quick_validate`, navigator-tools) bor i `explore.py` og wrapper de rene modulene. `run.py` får ett nytt kallsted (`--explore ""` → `explore()` → `run_project(mandate=…)`), `hosting.py` ett nytt whitelistet felt (`explore_prompt`), og `simulation.py` ett nytt scenario. Demo-transkriptet (`tests/golden/demo-transcript.stdout`) er **byte-uendret** fordi utforskningen er opt-in. --- ## D. Prioritert vei gjennom de ni gapene ### D.0 Versjonsbeslutningen (FØR noe bygges — én avgjørelse, ikke en glidning) **Anbefaling: bygg mot 1.0.x (installert) i denne planen.** (1) core-bumpen 1.9→1.15 er seks minor-versjoner over privat-API-premisser `test_maf_version_guard` vokter, og tre målte 1.9-egenskaper (`ExperimentalWarning`-paret, single-use GroupChat [2,0,0], middleware-no-op på bar klient) må re-måles; (2) en bump er en egen beslutning, ikke et biprodukt av sløyfa; (3) **1.0.1 er et patch-hopp på samme core** som kan tas gratis hvis `uv lock --upgrade-package agent-framework-orchestrations` gir 1.0.1 og suiten er grønn — og som KAN bære #6297 (§B pkt. 7) og/eller E7-fiksen. Det er spike S0. Bumpen til 1.1.x/core 1.15 er en EGEN økt etter denne planen (bonus: `InvocationsHostServer` krever core ≥1.13) — ikke en forutsetning. ### D.1 Spikes (økt 54 — alle i `spikes/e_magentic.py` + `tests/spikes/`, aldri `src/`) | # | Spørsmål | Måling | Felles hvis | |---|---|---|---| | S0 | Gir 1.0.1 samme API og fjerner den E2/E4/E7? | `uv lock --upgrade-package agent-framework-orchestrations` → `uv pip list`; re-kjør V1-skriptene `magentic_b7*.py` + `magentic_reset.py`; `uv run pytest -q` | E7 står → bli på 1.0.0, noter | | S1 | B7 i repoets egen form | E1–E4 + E7 portert til `spikes/` med `ScriptedChatClient` (ikke agentens `FakeClient`) | 4/4 på delt builder, 0/5 på fersk | | S2 | Fyrer `BudgetMiddleware` på MANAGERENS kall? | manager-`Agent(middleware=[BudgetMiddleware(meter)])`, `Budget(max_tokens=1, …)`; assert `BudgetExceeded` propagerer ut av `workflow.run` OG at `meter.tokens > 0` etter ett kall | middleware fyrer ikke → egen klient-wrapper; plan-endring | | S3 | Plan-review-rundtur i repoets form | E6 portert: request_info-event → `run(responses=…)` med `revise` så `approve`; tell manager-kall per revise (målt 2) | rundturen feiler på 1.0.0 | | S3b | `AgentApprovalExecutor` som deltaker | svar injiseres via `from_strings("svar")`, assert svaret står i neste manager-prompt | ikke nåbar → kun plan review (dør 1+2) | | S4 | Resume med pending plan review | `with_checkpointing(FileCheckpointStorage(tmp))`, kjør til review, **ny prosess** (subprosess), `run(responses=…, checkpoint_id=…, checkpoint_storage=…)`; assert `request_info` re-emittert + sluttsvar | feiler → U12 synkron-først, asynkron etter planen | | S5 | `quick_validate`-latens | `validate_proposal` ×20 på `bygg-energi-baseline-mikro` med `time.perf_counter` | > 2 s/kall → budsjettér kall i kontrakten | | S6 | OTEL gratis? | `uv add opentelemetry-sdk` i scratch-venv; `configure_otel_providers(enable_console_exporters=True)`; kjør demoen; tell spans | ingen `workflow.run`-span → U14 er større enn én økt | ### D.2 Rekkefølgen | # | Hva | Gap | Load-bearing for målbildet? | Økt | Modell | |---|---|---|---|---|---| | 0 | **Spikes S0–S6** | U4/U13/U12/U8/U14 | JA — hver er en antakelse planen ellers bygger blindt på | 54 | Opus 5/xhigh | | 1 | **U14 OpenTelemetry** — `opentelemetry-sdk` som deklarert dep, `configure_otel_providers` bak `PORTFOLIO_OTEL=…` (console default; OTLP KUN med eksplisitt `OTEL_EXPORTER_OTLP_ENDPOINT` — «ingen stille egress»), spans på DAGENS pipeline først; egne events for `PLAN_CREATED`/`REPLANNED`/`PROGRESS_LEDGER_UPDATED` (målt: `_magentic.py` emitterer null spans selv) | U14 | JA for «stole på» — og baselinen U15 måler mot | 55 | Opus 5/high | | 2 | **U4 + U13 synkron** — `explore.py` (C.8), `ExplorationContract`, mandat-former, `quick_validate`, navigator-tools, plan review med persona; `simulation`-scenario; `--explore` i CLI | U4, U13 | JA — dette ER målbildet | 56–57 | Opus 5/xhigh | | 3 | **U12 + asynkron U13** — `FileCheckpointStorage`, `{run_id}-plan-review.json` + innboks-svar, `hitl.py pending` utvidet, `--resume ` | U12, U13 | JA for svar over dager — **betinget av S4** | 58 | Opus 5/xhigh | | 4 | **U15** — EGEN sammenligning på golden-bundelen: utforskning vs. debatt på tre tall (validerte kandidater, tokens, runder), skrevet til `docs/` som måling | U15 | JA for «finner den bedre tiltak» — men MAF-evals AVVISES (eksperimentell i kode, «wrong shape», kapabilitetskart :58) | 59 | Opus 5/high | | — | U1 `ConcurrentBuilder` | U1 | NEI — bølgemodellen er målt, bærer budsjett-reservasjon (S3.4); Concurrent har ingen runder å reservere mot | etter planen, om noensinne | — | | — | U2 `WorkflowBuilder` | U2 | NEI — Magentic ER graf-laget; direkte adopsjon gir null ny kapabilitet | nei | — | | — | U5 `SkillsProvider` | U5 | NEI — `ExperimentalFeature.SKILLS`; egen loader virker; commons eier innholdet | etter planen | — | | — | U11 MAF RAG-providere | U11 | NEI — «navigate, never stuff» forbyr query-time retrieval; sitatbæring finnes | avvist med begrunnelse | — | Seks økter (54–59); faller S4, sparer pkt. 3 én økt. ### D.3 For og mot de fire kandidatene ordren navngir **U4 + U13 tidlig — FOR:** selve målbildet; alt annet er instrumentering av status quo. **MOT:** hviler på umålte antakelser (S2, S3b, S4) og på en `request_info`-sti repoet ALDRI har kjørt. Derfor spikes først — ikke omvendt. **Plass #2 og #3.** **U14 tidlig — FOR:** (1) repoets deploy-gate; (2) uten sporing er en utforskning en svart boks — og Magentic-koden gir selv null spans, så ledger-eventene må vi logge; (3) LITE i kode: én funksjon + env; (4) instrumentert FØR utforskningen gir U15 sin baseline. **MOT (målt):** det er **ikke gratis i avhengigheter** — `opentelemetry-sdk` + exporter må deklareres (wheel-vekt, `uv sync`, handover- pakka), og `ENABLE_INSTRUMENTATION` er default `True` (`observability.py:697`) så spans LAGES alt i dag og kastes. OTLP-exporter er egress og må være opt-in bak eksplisitt endepunkt. **Plass #1, betinget av S6.** **U15 — FOR:** uten måling kan vi ikke påstå at sløyfa er bedre; ærlighetsregelen forbyr påstanden. **MOT `evaluate_workflow`:** eksperimentell i kode (20 dekoratorer) og feil form (kvalitetsscore offline, ikke domeneobjekt). **Konklusjon:** evaluering JA, som egen sammenligning; MAF-flaten NEI inntil `EVALS` forlater `ExperimentalFeature`. **Plass #4** — den trenger noe å måle. **U12 — FOR:** asynkron plan review over dager er umulig uten; mekanismen dekker Magentic-tilstand (målt i kode, §A.1). **MOT:** ende-til-ende-resume på 1.9.0 er umålt, restore-feil svelges, og en synkron persona-variant leverer målbildet i demoen uten den. **Konklusjon:** S4 avgjør; bygg i #3 hvis grønn, ellers synkron-først og U12 etter planen, sammen med core-bumpen. ### D.4 Det som IKKE gjøres (uttalt) - Ingen endring i `shared/method-spec.md`. Utforskningen er et lag OVER §3; en amendment («Step 0 — Explore, optional») foreslås til commons via coord (operatørbeslutning, §F) — planen avhenger ikke av svaret. - Ingen D7-speiling nå: po-claude er PARKERT (STATE); utforskningslaget meldes dit når parkeringen oppheves, på linje med Steg-5-returtypen og demo-innboksen som også venter. - Ingen erstatning av GroupChat-debatten (Steg 3 står). Ingen `open/`-push, versjonsbump, Azure. - Ingen egen `MagenticManagerBase`-subklasse før en målt mangel krever det. --- ## E. Verifisering — testbare kriterier (kommandoer, ikke «sjekk at det virker») **Denne økten (planen selv):** | # | Kriterium | Kommando → forventet | |---|---|---| | E1 | Nevneren er 32 | `find src -name '*.py' \| wc -l` → `32` | | E2 | Hver null-spørring kan finne | `bash scratchpad/u_measure.sh` (V1) → `venv_files` ≥ 1 for hver av de 9 null-regexene | | E3 | Magentic ikke kode-markert | `grep -c -i "experimental" .venv/lib/python3.12/site-packages/agent_framework_orchestrations/_magentic.py` → `0`; `grep -n "SKILLS\|EVALS" …/agent_framework/_feature_stage.py` → `:54`, `:64` | | E4 | Installert = lås | `uv pip list \| grep orchestrations` → `1.0.0`; `sed -n '61,63p' uv.lock` → `version = "1.0.0"` | | E5 | Single-use + builder-blødning reproduserbar | `uv run python scratchpad/magentic_b7.py` → `E1 run2 … RuntimeError`, `E2 VERDICT … 4/4`, `E3 VERDICT … 0/5` | | E6 | Plan-review-rundtur reproduserbar | `uv run python scratchpad/magentic_b7b.py` → `E6 after APPROVE outputs: ['FINAL: …']`, `E5 outputs: ['Workflow terminated due to reaching maximum round count.']` | | E7 | OTEL-sdk mangler | `.venv/bin/python -c "import opentelemetry.sdk"` → `ModuleNotFoundError` | | E8 | Planen er dok-gate-klassifisert | `uv run pytest -q tests/test_doc_constant_sync_loadbearing.py` → grønn (datert sti = arkiv-klasse) | | E9 | Treet urørt utenom planen | `git status --porcelain` → kun `docs/plan/2026-08-23-…md` + den fremmede `presentasjon`-HTML-en | **Byggeøktene (kriterier som FELLES når sømmen detaches — load-bearing-metoden):** | Leveranse | Rødt-først-kriterium | |---|---| | S1/B7 | test som deler builder mellom to utforskninger er RØD (4/4); `fresh_exploration_workflow` gjør den grønn (0/N) | | S2/budsjett | `Budget(max_tokens=1)` på manager-agenten → `BudgetExceeded` ut av `explore()`; detach middleware på manageren → testen rød | | C.2/runder | `max_rounds=1` → `BudgetExceeded(kind="exploration_rounds", limit=1, observed=1)`; detach oversettelsen → kjøringen «lykkes» med den kanoniske strengen → rød | | C.3/kontrakt | `ExplorationContract(max_stall_count=None)` → `ValidationError` ved konstruksjon | | C.5/plan review | persona-`revise` én gang → `REPLANNED` i loggen og `plan_reviews == 2`; `max_plan_revisions=1` + persona som alltid reviderer → typet stopp, aldri evig løkke | | C.6/frø | frø-approach `id="fagperson-1"` finnes i output-`Mandate.approaches` uansett hva manageren fant; detach bevaringen → rød | | C.0/nivå 3 | `explore()` skriver ingenting til outbox/wiki: `ls outbox` før/etter → kun `{run_id}-exploration.json`; `git status shared/` ren | | U12/S4 | subprosess-resume: pending `request_info` re-emittert + sluttsvar; detach `checkpoint_storage` → rød | | U14 | demoen under `PORTFOLIO_OTEL=console` → ≥1 `workflow.run`-span på stderr; uten variabelen → stdout byte-identisk med `tests/golden/demo-transcript.stdout` | | Golden | `uv run pytest -q` grønn; `shasum tests/golden/demo-transcript.stdout` uendret | --- ## F. Nøkkelantakelser, risiko og åpne beslutninger **Alle åtte radene er MÅLT i økt 54** (ordre `20260823T162224Z`, spikes S0–S6). Måleapparatet er `spikes/e_magentic.py` + `tests/spikes/test_e_magentic.py` (16 tester), kjørt mot orchestrations **1.0.1** på core 1.9.0; hele suiten 920 passed / 5 skipped, golden-transkriptet uendret (`ea8c534773acdbe41ae68f2c55724d69aaf8be4f`). | # | Antakelse | Test | Status | |---|---|---|---| | A1 | Agent-nivå `ChatMiddleware` fyrer på managerens kall | S2 | **MÅLT GRØNN.** `BudgetMiddleware` på manager-agenten krediterer meteret (`meter.tokens == 8` etter ett kall) og stopper kjøringen. Detach-kontroll: uten middleware fullfører SAMME 1-token-budsjett. Planens budsjettgaranti står. | | A2 | `BudgetExceeded` reist inni en Magentic-deltaker propagerer ut av `workflow.run` | S2 | **MÅLT GRØNN, og som repoets EGEN type** — ikke pakket i en `ExceptionGroup`: `isinstance(exc, BudgetExceeded)`, `kind="tokens"`, `limit=1`, `observed=8`. Trippelen kø-(y) leser er intakt, så 429-kanalen kan brukes uendret. | | A3 | `request_info`-rundtur | S3 | **MÅLT GRØNN på 1.0.1.** Review stopper kjøringen uten output (manageren har da kun kalt `facts`+`plan`); `revise` koster nøyaktig 2 manager-kall (`facts_update`, `plan_update`), **null ledger-kall og null runder**, og **spør på nytt**; `approve` kjører løkka til sluttsvar. Bekrefter at `max_plan_revisions` MÅ inn i kontrakten — ellers er en alltid-reviderende ekspert et ubundet forbruk. | | A4 | Resume med pending plan review i NY prosess | S4 | **MÅLT GRØNN.** Foreldreprosessen stopper på review og etterlater checkpoints; en subprosess (`spikes/e_magentic_resume.py`) som aldri så kjøringen svarer fra checkpointen alene og driver workflowen til sluttsvar. **Pris, ikke forutsett:** `FileCheckpointStorage` NEKTER å deserialisere `MagenticPlanReviewRequest`/`…Response` uten at begge navngis i `allowed_checkpoint_types` — uten det er checkpoint-fila uleselig og listingen TOM, altså en resume som feiler som et FRAVÆR. Begge prosesser må deklarere dem. | | A5 | `AgentApprovalExecutor` som deltaker gir svar midt i kjøringen | S3b | **MÅLT GRØNN — dør 3 i C.6 står.** Ekspertens ord når både liaisonen og en senere manager-prompt. **To rundturer per menneskesvar:** `from_strings([svar])` mater svaret tilbake INN i liaisonen og gjenopptar IKKE manageren (målt: null manager-kall mellom de to forespørslene); først `approve()` sender liaisonens output videre. **Kostnad:** `AgentApprovalExecutor` er IKKE re-eksportert fra `agent_framework.orchestrations` (svartypen ER det) — døra koster i dag en privat-API-import. | | A6 | Persona-manuset kan drive en Magentic-manager (ledger-JSON) offline | S1/S3 | **MÅLT GRØNN med repoets `ScriptedChatClient`** (ikke lenger en ad-hoc `FakeClient`). Ett forbehold funnet: selectoren får den SAMMENSLÅTTE prompten, så ett av fem manager-kall bærer to markører — rutingen må teste senere-stadium-markøren først. Og en `next_speaker` som ikke matcher en deltaker gir **stille sluttsvar uten at noen ble spurt** (`_magentic.py:1128-1131`), målt da liaison-spiken først het `worker`. | | A7 | Utforskning som opt-in holder demo-transkriptet byte-uendret | `shasum` | **MÅLT uendret** gjennom hele økten (`ea8c534…`). | | A8 | 1.0.1 er API-identisk med 1.0.0 | S0 | **MÅLT: API-identisk, ATFERD ikke.** Diffen er upstream-regresjonsfiksen #4371: `StandardMagenticManager.__init__` mistet `self._session = agent.create_session()`, og hvert manager-kall mynter nå en engangs-sesjon. **Konsekvens: E2 OG E4 er BORTE** (4/4 → **0/5** begge). E1 (single-use `RuntimeError`, null kall) og E7 (`MagenticResetSignal` skriver til orphan-attributtet `_agent_thread`, `:1369`, sesjonsidentitet uendret) står. Manager-sesjonen fjernet også fra checkpoint-state, konsistent med at manageren nå er tilstandsløs per kall. | **Målt korreksjon til E-tabellen (U12/S4):** kriteriet «detach `checkpoint_storage` → rød» er FEIL — å fjerne `checkpoint_storage=` fra `run()` lar HELE suiten stå grønn (920 passed), fordi `.with_checkpointing(...)` på builderen allerede ga workflowen lageret. De to bærende punktene er `checkpoint_id=` (fjernes → rød) og builderens `.with_checkpointing(...)` (fjernes → rød). **Load-bearing MÅLT** (mot HELE suiten, grønn kontroll 920/5): `manager_keeps_persistent_session` konstant `True` (2 røde — E2+E4 alene) · aldri fest `BudgetMiddleware` på manageren (3 røde, mens detach-armen forblir grønn) · detach markør-registreringen (1 rød — S3b-positiven alene, kontrollen grønn) · flipp `_route`-rekkefølgen så `pre-survey` testes først (4 røde) · resume uten `checkpoint_id=` (1 rød) · builder uten `.with_checkpointing()` (1 rød) · tom `_ALLOWED_CHECKPOINT_TYPES` (1 rød) · og den falsifiserte: resume uten `checkpoint_storage=` (**0 røde** — funnet over). **S5 — `quick_validate`-latens: median 13,6 ms** over 20 kall på `bygg-energi-baseline-mikro` (forankret baseline + assumption-bånd, så stage 0 + CBC + 512-sample Monte Carlo er alle med). Langt under 2 s-terskelen: verktøyet kan kalles fritt i løkka, og kontrakten trenger ingen egen latens-post. Båndet er med med vilje — uten det faller `_monte_carlo` tilbake på `item.unit_cost`, alle draw blir identiske, og tallet ville underrapportert den ekte kostnaden. **S6 — OTEL er gratis, men IKKE via `enable_console_exporters`.** I et scratch-venv pinnet til samme stack (core 1.9.0 / orch 1.0.1 / `opentelemetry-sdk` 1.44.0; **ingenting lagt til `pyproject.toml`**) gir `configure_otel_providers(enable_console_exporters=True)` **2 `workflow.run`-spans** — pluss `workflow.build`, `executor.process`, `edge_group.process`, `message.send`, `invoke_agent`, `chat synthetic` — men de skrives til **stdout**, som ville ødelagt golden-transkriptet. Med `exporters=[ConsoleSpanExporter(out=sys.stderr)]` kommer de 2 `workflow.run`-spanene på **stderr** og demoens stdout er **byte-identisk med fasiten** (samme shasum). U14 er altså én økts arbeid — forutsatt at exporteren konstrueres eksplisitt mot stderr, aldri via flagget. **U14 er LANDET (økt 55, ordre `20260823T165757Z-6762213199`).** Operatøren tok alle fire beslutningene som anbefalt 23.08, og bekreftet samme dag at **låsen på orchestrations 1.0.1 er ENDELIG, ikke midlertidig** — spike-ordrens «revert hvis E7 står» er overstyrt av den senere beslutningen, som betinget på grønn suite (920/5, målt) og ikke på E7. Levert: * `opentelemetry-sdk>=1.42,<2` som DEKLARERT dependency (beslutning 2). Én ny pakke, ikke to: `ConsoleSpanExporter` bor inne i sdk-en. `opentelemetry-api` fulgte med fra 1.42.1 til 1.44.0 — målt uskadelig (golden `ea8c534…` uendret før og etter). * `src/portfolio_optimiser/tracing.py` — `PORTFOLIO_OTEL` på truthiness, modusene `console` (spans → stderr) og `otlp` (kun mot et navngitt endepunkt), `tracing_notice` som eneste renderer. * Wiret i TRE inngangspunkter: `run.main`, `simulation.main`, `hosting.main`. * **S6-forbeholdet er implementert som skrevet:** `ConsoleSpanExporter(out=…)`, aldri `enable_console_exporters=True`. Målt: demoen under `PORTFOLIO_OTEL=console` gir `workflow.run`- spans på stderr og stdout BYTE-IDENTISK med fasiten; uten variabelen null spans. * **To ting E-tabellen ikke forutså, begge lukket:** (i) MAF bygger env-avledede OTLP-exportere UBETINGET, så `console` NEKTER når en `OTEL_EXPORTER_OTLP_*_ENDPOINT` er satt — ellers var «console» en usann påstand om hvor kjøringens innhold tok veien; (ii) `enable_console_exporters` faller tilbake på `ENABLE_CONSOLE_EXPORTERS` i miljøet, så den pinnes eksplisitt `False` i begge moduser. Begge er MÅLT som defektklasser, ikke antatt. * **IKKE bygget, med grunn:** `PLAN_CREATED`/`REPLANNED`/`PROGRESS_LEDGER_UPDATED` — de hører til sløyfa U4 bygger, og en emitter uten kallsted er en form gjettet i stedet for målt. De landes i økt 56–57, på sømmen U14 nettopp la. OTLP-exporter-PAKKENE er bevisst ikke deklarert (egress + grpc/protobuf-vekt i et publisert wheel); uttalt ærlighets-grense i README/DEPLOY/env.template. Suite etter U14: **943 passed / 5 skipped**, golden `ea8c534…` uendret, `mypy src` + `ruff` rene. **Beslutninger som trenger operatøren (speiles i avslutningsblokken):** 1. **Versjon — AVGJORT 23.08: låsen står på 1.0.1, ENDELIG.** Bakgrunnen: ordrens «felles hvis» sa at hvis E7 står i 1.0.1, revert til 1.0.0. E7 STÅR — men 1.0.1 fikset noe større enn det som ble håpet (E2/E4, «en plausibel fasit produsert av null arbeid»). Operatøren aksepterte 23.08 at gevinsten veier tyngre enn ordrens bokstav, og at betingelsen som gjelder er «grønn suite» (målt 920/5), ikke E7. **Ikke midlertidig, og ikke å gjenåpne uten en ny måling.** Bumpen til 1.1.x / core 1.15 er fortsatt en EGEN økt etter denne planen. 2. **U14 som deklarert avhengighet — AVGJORT 23.08: ja.** Landet i økt 55; se «U14 er LANDET» over. Kostnaden ble ÉN pakke (`opentelemetry-sdk`), ikke to. 3. **HITL — AVGJORT 23.08: synkron-først.** Merk at ordrens BEGRUNNELSE er utdatert mens konklusjonen står: den sier «A4 er umålt og kun kode-lest», men A4 ble MÅLT GRØNN i økt 54, så asynkron er teknisk mulig. Valget er dermed ren rekkefølge, ikke risiko — asynkron U13 forblir pkt. 3 i rekkefølgen, betinget av S4 som nå er grønn. 4. **Commons-amendment «Step 0 — Explore» — AVGJORT 23.08: sendt.** `.claude` sendte forslaget til `portfolio-optimiser-commons` via coord. Vi venter IKKE: planen slår selv fast at ingen amendment er påkrevd, og «Step 0» er foreslått som VALGFRI. Kommer den, er den en tilføyelse. --- ## Vedlegg V1 — målingsskript og rå output (U1–U19) Skript (`scratchpad/u_measure.sh`, kjørt 2026-08-23; kopiert hit ordrett så målingen kan gjentas): ```bash #!/bin/bash cd "$(git rev-parse --show-toplevel)" SRC_N=$(find src -name '*.py' | wc -l | tr -d ' ') TST_N=$(find tests -name '*.py' | wc -l | tr -d ' ') SPK_N=$(find spikes -name '*.py' | wc -l | tr -d ' ') echo "DENOMINATOR src=$SRC_N tests=$TST_N spikes=$SPK_N" m() { s=$(grep -rEn --include='*.py' "$2" src | wc -l | tr -d ' ') t=$(grep -rEn --include='*.py' "$2" tests | wc -l | tr -d ' ') k=$(grep -rEn --include='*.py' "$2" spikes | wc -l | tr -d ' ') printf "%-4s %-60s src=%-4s tests=%-4s spikes=%s\n" "$1" "$2" "$s" "$t" "$k" [ "$s" != "0" ] && grep -rEn --include='*.py' "$2" src | head -12; } m U1 'ConcurrentBuilder|BuildConcurrent|FanIn' m U1b 'asyncio\.gather|create_task\(' m U2 'WorkflowBuilder|@executor|class .*\(Executor\)|WorkflowContext|SwitchCase|add_edge|add_fan_out' m U3 'GroupChatBuilder|with_max_rounds|termination_condition|RoundRobinGroupChatManager' m U4 'Magentic' m U5 'SkillsProvider|McpSkillsSource|AgentSkillsProvider|agent_framework.*skills' m U6 'MCPStdioTool|MCPStreamableHTTPTool|MCPWebsocketTool|as_mcp_server' m U7 'FunctionTool|ai_function|@tool|tools=\[' m U8 'Middleware|middleware=' m U9 'ContextProvider|HistoryProvider|context_providers=' m U10 'AzureAISearch|azure\.search|cosmos|qdrant|redis|VectorStore|vector_store|Qdrant|Chroma|chromadb' m U11 'AIContextProvider|TextSearchProvider|citation|SourceName|SourceLink|agentic_retrieval|KnowledgeAgent' m U12 'checkpoint|Checkpoint' m U13 'approval_mode|request_info|RequestInfoEvent|RequestPort|MagenticPlanReview|ApprovalRequired' m U14 'opentelemetry|setup_observability|OBSERVABILITY|get_tracer|start_as_current_span' m U15 'evaluate_workflow|LocalEvaluator|Evaluator|_evaluation|agent_framework.*evaluation' m U16 'CompactionProvider|SlidingWindow' m U17 'as_tool\(|AsAIFunction' m U18 'FoundryChatClient' m U19 'HarnessAgent|ShellExecutor|FileMemoryStore|ToolApprovalAgent' ``` Rå tellinger: `DENOMINATOR src=32 tests=117 spikes=6` · U1 0/4/4 · U1b 1/5/0 · U2 0/0/0 · U3 10/7/7 · U4 0/4/9 · U5 0/0/0 · U5b(`SKILL\.md`) 1/5/0 · U6 6/6/0 · U6b(rå mcp) 7/6/0 · U7 4/10/0 · U8 16/20/0 · U9 8/11/4 · U10 2/19/0 · U10b(numpy/cosine) 49/102/1 · U11 26/35/0 · U12 3/0/0 · U13 3/0/0 · U14 0/0/1 · U15 0/0/0 · U16 0/0/0 · U17 0/0/0 · U18 7/8/0 · U19 0/0/0. Kjent-positiv-kontroll mot installert MAF (`grep -rEl agent_framework agent_framework_orchestrations | wc -l`): U2 → 21 · U5 → 3 · U14 → 4 · U15 → 2 · U16 → 4 · U17 → 1 · U19 → 1 · `checkpoint_storage|CheckpointStorage` → 14 · `MagenticBuilder|StandardMagenticManager` → 4. B7-/HITL-målingsskriptene (`magentic_b7.py`, `magentic_b7b.py`, `magentic_reset.py`) ligger i øktens scratchpad og porteres til `spikes/e_magentic.py` i økt 54 (S1/S3) — de committes ikke herfra, fordi de bruker en ad-hoc `FakeClient` og ikke repoets `ScriptedChatClient`. ## Vedlegg V2 — installert Magentic-API (orchestrations 1.0.0, `_magentic.py`, 1802 linjer) - **Eksport** (`agent_framework_orchestrations/__init__.py:43-62`): `MagenticBuilder`, `MagenticContext`, `MagenticManagerBase`, `MagenticOrchestrator`, `MagenticOrchestratorEvent`/ `…EventType`, `MagenticPlanReviewRequest`/`…Response`, `MagenticProgressLedger`/`…Item`, `MagenticResetSignal`, `StandardMagenticManager`, `MagenticAgentExecutor`. **Ikke i koden:** `MagenticHumanIntervention*`, `with_human_input_on_stall` (kun docstrings). - **`MagenticBuilder(*, participants, manager=|manager_factory=|manager_agent=|manager_agent_factory= (nøyaktig én), task_ledger_*_prompt…, progress_ledger_prompt, final_answer_prompt, max_stall_count=UNSET→3, max_reset_count=None, max_round_count=None, enable_plan_review=False, checkpoint_storage=None, output_from, intermediate_output_from)`** (`:1396-1422`). Metoder: `with_plan_review(enable=True)` (`:1516`), `with_checkpointing(storage)` (`:1564`), `build()` (`:1772`; bidireksjonale kanter orkestrator↔deltaker `:1796-1797`). Grensene bor på MANAGEREN (`:1693-1695`, lest tilbake `:905/:1115/:1240`); `manager=` + prompt/limit-argumenter → warning og **stille ignorert** (`:1661-1677`). Ikke-agent-`Executor` som deltaker aksepteres (`:1498-1499`) men får placeholder-beskrivelse (`_base_group_chat_orchestrator.py:115`). - **`StandardMagenticManager(agent, …, max_stall_count=3, max_reset_count=None, max_round_count=None, progress_ledger_retry_count=None→3)`** (`:528-544`). Én `AgentSession` i `__init__` (`:572`), alle kall `agent.run(…, session=…)` (`:601`). Ledger-parsing: fri tekst + `_extract_json` (`:406-449`), dataklasser ikke pydantic (`:288-333`). - **Løkke:** runde-teller ×2 (`:1075-1076`), grense-sjekk FØR inkrement på `_round_index` (`:1238`); stall `:1109-1118`; reset `:1149-1185`; **grense → kanonisk streng + `_terminated`, ingen exception** (`:1222-1256`); sluttsvar `:1206-1220`; ukjent `next_speaker` → stille sluttsvar (`:1128-1131`); single-use-guard `:914-918`. - **Plan review:** request `:829-853` (`plan`, `current_progress`, `is_stalled`; `.approve()`, `.revise(feedback)`), response `:800-826` (`review: list[Message]`, tom = approve `:1010`), reises `:1041-1048`, svares `workflow.run(responses={id: …})` (`_workflow.py:899-908`; ukjent id → `ValueError` `:946`), revise-håndtering `:1014-1031` (ingen cap, ingen runde-telling). Per-tur-HITL via `AgentApprovalExecutor` (`_orchestration_request_info.py:168`). - **Checkpoint:** `CheckpointStorage`-protokoll `_checkpoint.py:119`, `InMemoryCheckpointStorage` `:192`, `FileCheckpointStorage(storage_path, *, allowed_checkpoint_types)` `:239`; `WorkflowCheckpoint.pending_request_info_events` `:81`; `run(checkpoint_id=, checkpoint_storage=)` `_workflow.py:701-712`, `responses`+`checkpoint_id` tillatt `:877`; superstep-granularitet `_runner.py:143-144`; re-emittering `_runner_context.py:414-426`; Magentic-state `_magentic.py:1266-1284` + manager-sesjon `:746`; feil svelges `:1281-1282`/`:1320-1321`. - **Observability:** `_magentic.py` → 0 spans. Runtime-spans `workflow.build`/`workflow.run`/ `executor.process`/`edge_group.process`/`message.send` (`observability.py:253-276`). Inngang `configure_otel_providers(*, enable_sensitive_data, enable_console_exporters, exporters, views, vs_code_extension_port, env_file_path, env_file_encoding)` (`:1168`), `enable_instrumentation` (`:1132`); env `ENABLE_INSTRUMENTATION` (default True, `:697`), `ENABLE_SENSITIVE_DATA`, `ENABLE_CONSOLE_EXPORTERS`, `OTEL_EXPORTER_OTLP_ENDPOINT`/`_PROTOCOL`/`_HEADERS`, `OTEL_SERVICE_NAME`. Ikke re-eksportert fra `agent_framework` (`hasattr` → False). `APPLICATIONINSIGHTS_CONNECTION_STRING` → ikke funnet. `opentelemetry-sdk`: **ikke installert**. - **Evals/Skills:** `evaluate_workflow(*, workflow, workflow_result|queries, evaluators, …)` (`_evaluation.py:1833`, `@experimental(EVALS)` `:1832`); `SkillsProvider(ContextProvider)` (`_skills.py:1719`, `@experimental(SKILLS)` `:1718`; `from_paths` `:1861`). ## Vedlegg V3 — kommandoer kjørt (utvalg; fullstendige lister i øktens agent-transkripter) ``` find src -name '*.py' | sort | wc -l # 32 uv pip list | grep -i -E "agent.framework|^mcp |opentelemetry|azure-ai|anyio" sed -n '61,75p' uv.lock ; grep -n "agent-framework" pyproject.toml curl -s https://pypi.org/pypi/agent-framework-orchestrations/json # + /1.0.0 … /1.1.1/json sed -n '1,80p' .venv/.../agent_framework/_feature_stage.py grep -rn "@experimental|@preview|feature_stage(" .venv/.../agent_framework/*.py .../_workflows/*.py grep -n -i "experimental|feature_stage" .venv/.../agent_framework_orchestrations/*.py # 0 bash scratchpad/u_measure.sh # V1 grep -rEl <9 null-regexer> .venv/.../agent_framework .venv/.../agent_framework_orchestrations | wc -l grep -rn -i "research.01" STATE.md STATE-arkiv-2026-08-21.local.md docs/ # 0 grep -rn "enable_layer1_hitl|layer1" src tests ; grep -rEn 'request_info|RequestInfoEvent' tests # 0 grep -rn "agent_framework" src/portfolio_optimiser/*.py # 14 importlinjer microsoft_docs_fetch learn.microsoft.com/en-us/agent-framework/workflows/orchestrations/magentic uv run python scratchpad/magentic_b7.py ; … magentic_b7b.py ; … magentic_reset.py # E1–E7 .venv/bin/python -c "import opentelemetry.sdk" # ModuleNotFoundError ```