portfolio-optimiser/docs/plan/2026-08-23-magentic-utforskningssloeyfe.md
Kjell Tore Guttormsen 2eb4622f44 docs(plan): portabel sti i V1-skriptet - pakke-gaten var roed paa HEAD [skip-docs]
Oekt 53s plan-commit (2e33905) bar `cd /Users/ktg/repos/portfolio-optimiser` i
V1-maalingsskriptet. `test_package_leaks_no_secret_content` leser `git archive HEAD`
og ble derfor roed i det oeyeblikket planen ble committet - suiten var ikke maalt
etter den commiten (STATEs «904 passed» er fra oekt 52). Ikke relatert til S0s
orchestrations-oppgradering: funnet er ordrett plan-dokumentets stistreng.

`git rev-parse --show-toplevel` holder skriptet kjoerbart og fjerner hjemstien.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTEa7uw2JMxgijx8k8XgxG
2026-08-23 19:00:25 +02:00

581 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# MAF-utnyttelsesrevisjon U1U15 + 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 V1V3); 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:** S0S6-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. U1U15-måling (nevner: 32 `.py`-filer i `src/`; kontroll: 117 i `tests/`, 6 i `spikes/`)
Metode: `grep -rEn --include='*.py' "<regex>" 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)``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** (sekunderminutter; 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 68). 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 §23)
| 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 "<prompt>"``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 | E1E4 + 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``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 S0S6** | 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 | 5657 | Opus 5/xhigh |
| 3 | **U12 + asynkron U13**`FileCheckpointStorage`, `{run_id}-plan-review.json` + innboks-svar, `hitl.py pending` utvidet, `--resume <run_id>` | 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 (5459); 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
| # | Antakelse | Test | Status |
|---|---|---|---|
| A1 | Agent-nivå `ChatMiddleware` fyrer på managerens kall | S2 | **umålt — RISIKO** (planens budsjettgaranti hviler her) |
| A2 | `BudgetExceeded` reist inni en Magentic-deltaker propagerer ut av `workflow.run` | S2 | målt for GroupChat (live-kjøringen 14.08), umålt for Magentic |
| A3 | `request_info`-rundtur på 1.0.0 | E6 | **MÅLT grønn** |
| A4 | Resume med pending plan review i NY prosess | S4 | umålt — kode-lest (`_runner_context.py:414-426`) |
| A5 | `AgentApprovalExecutor` som deltaker gir svar midt i kjøringen | S3b | umålt |
| A6 | Persona-manuset kan drive en Magentic-manager (ledger-JSON) offline | E1E6 brukte en `FakeClient` med gyldig ledger-JSON | **MÅLT mulig**; portering til `ScriptedChatClient` er S1/S3 |
| A7 | Utforskning som opt-in holder demo-transkriptet byte-uendret | `shasum` i E-tabellen | bygges slik; verifiseres per økt |
| A8 | 1.0.1 er API-identisk med 1.0.0 | S0 | umålt |
**Beslutninger som trenger operatøren (speiles i avslutningsblokken):**
1. Versjon: bli på 1.0.x (anbefalt) vs. core-bump til 1.15 nå.
2. U14 som deklarert avhengighet (`opentelemetry-sdk` + exporter) i wheel/handover — ja/nei.
3. HITL-leveranse: synkron-først (anbefalt, uansett S4) vs. asynkron som krav i samme plan.
4. Commons-amendment «Step 0 — Explore» — sende forslag nå (anbefalt: ja, via coord, uten å vente).
---
## Vedlegg V1 — målingsskript og rå output (U1U19)
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 <regex> 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 # E1E7
.venv/bin/python -c "import opentelemetry.sdk" # ModuleNotFoundError
```