The short loop captured the expert verdict inline into an in-memory store, so a verdict arriving days/weeks later in a separate run could not influence any future hypothesis (målbilde §5 row 7). Steg 7 adds the long timescale: run_project gains an opt-in verdict_dir async inbox that load_verdicts_from_dir -> store.add MERGES into the store BEFORE the Step-1 ExpeL fold, so a verdict dropped after an earlier run reaches a separate, later run's hypothesis — fully resumable across runs separated in time. - verdicts.py: verdict_to_dict / verdict_from_dict (id read verbatim, never re-minted), write_verdict (public authoring primitive, NOT wired into run_project — system reads the folder, expert/persona writes it, §3 role split), tolerant load_verdicts_from_dir (missing/foreign/half-written files skipped, not raised — RAW layer per §10 R2), VerdictStore.from_dir. - run.py: verdict_dir kwarg; ingest-merge block after load_contracts (merge not replace keeps run_portfolio's cross-project threading; store.add idempotent on content-hash id; no change to the fold). CLI --bundle-dir/--verdict-dir thread the long loop to the console entry. No auto-persist of the run's own captured verdict (outbox/Steg 8). - Load-bearing PAIR (test_step7_async_loop_loadbearing.py): a verdict dropped after run A must reach run B's prompt (run B uses a FRESH store -> the transfer is the file loop, not in-memory carryover); empty-inbox control proves causality. Marker = a realization value absent from the bundle (not the seed's 0.82). Proven RED on ingest detach. Suite 138 -> 140 passed, 4 skipped; mypy + ruff check clean. Målbilde treated as frozen (no §3/§5/§7 edit). Step 8 (gated wiki promotion) remains. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHR8iKxJRxDiDfNw8HZmWE
78 lines
6.7 KiB
Markdown
78 lines
6.7 KiB
Markdown
# portfolio-optimiser
|
|
|
|
## Kontekst
|
|
Generisk, åpent Python-rammeverk på **Microsoft Agent Framework (MAF)** som finner kostnadsbesparelser INNI hvert prosjekt i en portefølje av uavhengige prosjekter. Multi-agent samarbeid genererer kandidat-tiltak; en obligatorisk deterministisk validator avgjør verdiene; fageksperter vurderer via HITL, og systemet lærer av dommene. Publiseres på Forgejo for alle som har MAF.
|
|
|
|
Bakgrunn og beslutninger: [research](docs/research/2026-06-23-prior-art-platform.md) (§15 implementeringsregister), [plan](docs/plan/2026-06-23-incremental-plan.md). Løpende state: `STATE.md` (local-only).
|
|
|
|
## Stack
|
|
Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To backend-profiler: Azure/Foundry (full) + lokal (fallback).
|
|
|
|
## Konvensjoner
|
|
- Type hints overalt (`mypy` der mulig). Pydantic for validering/IR.
|
|
- `ruff` for lint+format. `pytest` for test.
|
|
- Modell-valg som konfig (modell-map rolle→Foundry-deployment), ikke spredt i kode.
|
|
- Metode kodifiseres som **Agent Skill** (`agentskills.io`: `SKILL.md` + `scripts/` + `references/`).
|
|
- Datatilgang via **MCP**-servere; data-source-konfig JSON-Schema-validert, fail-fast.
|
|
|
|
## Kommandoer
|
|
- Sync: `uv sync`
|
|
- Test: `uv run pytest`
|
|
- Lint: `uv run ruff check .` + `uv run ruff format .`
|
|
- Type: `uv run mypy src`
|
|
|
|
## Arbeidsflyt (invarianter)
|
|
- **Rent teknisk rammeverk:** deployer eier DPIA/ROS/behandlingsformål. Bygg IKKE compliance-funksjoner — kun tekniske forutsetninger (lokal-only, provenance, ingen stille egress) + disclaimer.
|
|
- **90%-prinsipp:** bygg den generiske kjernen + tydelige extension points; jakt IKKE de siste 10 %.
|
|
- **Deterministisk validator er obligatorisk og blokkerende** — aldri valgfri plugin.
|
|
- **Framework-nøytral kontekst-søm:** OKF-bundle-navigasjon (`okf.py`) og den delte
|
|
`shared/`-kjernen er ren stdlib — null `agent_framework`/`mcp`-import, så samme bundles
|
|
konsumeres uendret av begge stacker (D7-portabel). Håndhevet av
|
|
`tests/test_okf.py::test_okf_is_maf_free`; importér aldri MAF inn i kontekst-laget.
|
|
- **OKF-navigert bundle-kontekst (ikke stuffing):** på bundle-stien bygges agent-lese-konteksten
|
|
ved å NAVIGERE bundelen (`okf.bundle_context`: index + frontmatter + cross-links, progressiv
|
|
disclosure) — aldri keyword-chunk-stuffing (målbilde §2/§4). `type: verdict`-laget ekskluderes
|
|
fra denne konteksten: tidligere dommer når hypotese-prompten KUN via den gatede ExpeL-folden.
|
|
Load-bearing: `test_bundle_context_excludes_verdict_layer` + empty-store-kontrollen i
|
|
`test_step1_expel_loadbearing.py` (realiseringssignalet lekker aldri inn via kontekst).
|
|
- **Stoppkriterier + budsjett-tak påkrevd ved oppstart** (fail-fast, aldri ubegrenset loop).
|
|
- **Group Chat maker-checker** som debatt-default (IKKE Magentic, som er eksperimentell).
|
|
- **To falsifiserere, samme kandidat (Steg 3/4, målbilde §2/§6):** den deterministiske validatoren
|
|
gater *tallene* (blokkerende), checkeren gater *resonnementet*. Checkeren avslutter turen med en
|
|
`VERDICT: APPROVE` / `VERDICT: REJECT — <grunn>`-linje; et eksplisitt avslag blokkerer et ellers
|
|
validert forslag (`run_project` overflater begge debatt-deltakere via `output_from=agents` og
|
|
overstyrer utfallet til en checker-kilde-`Rejection`). Gaten er opt-in-reject (fail-open ved
|
|
manglende markør), og `provenance.validator_decision` forblir ærlig — den speiler KUN validatoren,
|
|
aldri checkeren (de to falsifisererne blandes aldri). Load-bearing:
|
|
`tests/test_checker_gate_loadbearing.py` blir rød ved BEGGE detach-punkt (revert `output_from`,
|
|
eller fjern override). Checkeren «må faktisk gate, ELLER vi slutter å kalle det maker-checker».
|
|
- **Informert forbedring, bundet (Steg 5, målbilde §5/§7):** `generate_via_llm`s ytre
|
|
`max_attempts`-løkke er ikke lenger blind — validatorens *forrige* `Rejection.reason` mates inn i
|
|
neste forsøks prompt (`_build_messages(prior_rejection=...)`), så proposeren korrigerer i stedet
|
|
for å gjenta. Kun den mest-nylige falsifiseringen (`last`, ikke akkumulert), kun grunnen (aldri
|
|
forrige proposal-JSON), under EKSISTERENDE tak (`meter.tick_round` + `max_attempts` — ingen ny
|
|
løkke; «forbedre til god nok» uten tak er forbudt). Eneste *per-forsøk*-falsifiserer her er
|
|
validatoren; å seede generering med checker-*kritikken* er run-nivå og separat scoped (IKKE bygget
|
|
her) — så koden påstår ikke mer enn den gjør. Load-bearing:
|
|
`tests/test_step5_refine_loadbearing.py` blir rød når reason-injeksjonen detaches (utfallet
|
|
flipper aldri + reason-verbatim-asserten faller); kontrollen beviser at løkka forblir bundet.
|
|
- **Lang/async fil-løkke (Steg 7, målbilde §3/§7):** `run_project(verdict_dir=...)` er den lange
|
|
tilbakemeldings-tidsskalaen — en ekspert/persona dropper en verdict-fil (vanlig JSON, RAW-laget
|
|
per §10 R2) i en inbox-mappe ETTER en kjøring, og en separat, senere kjøring `load_verdicts_from_dir`
|
|
→ `store.add` **merger** den inn FØR Steg-1-folden (ingen endring i folden), så dommen når neste
|
|
hypotese. **Rolledeling (§3, ufravikelig):** systemet LESER mappa; eksperten/personaen SKRIVER den
|
|
— `run_project` persisterer ALDRI sin egen fangede dom tilbake (det er outbox/Steg 8). Merge, aldri
|
|
erstatt (`run_portfolio`-tråding intakt); tolerant last (manglende mappe / fremmede / halvskrevne
|
|
filer hoppes over, ikke raises — RAW-lag, kontrast `okf.load_ir_projection`s fail-fast); `id` leses
|
|
verbatim, re-mintes aldri. `write_verdict` er den offentlige authoring-primitiven (persona/test +
|
|
framtidig Steg 8), men wires IKKE inn i `run_project`. Load-bearing:
|
|
`tests/test_step7_async_loop_loadbearing.py` — en dom droppet etter Run A MÅ nå Run B's prompt
|
|
(Run B bruker FERSK store → overføringen er fil-løkka, ikke in-memory-carryover); tom-inbox-kontroll
|
|
beviser kausalitet. Markør = realiseringsverdi som finnes ingen steder i bundelen (ikke frøets 0.82).
|
|
- **Kostnadsdisiplin:** utvikle primært på lokal profil (gratis); Foundry/Azure (privat tenant finnes) kun til målrettet, minimal verifisering; billigste modeller + små syntetiske data + harde token-tak. Ingen tunge test-kjøringer.
|
|
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
|
|
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.
|
|
|
|
## Communication patterns
|
|
|
|
When linking to local files in responses, use named markdown links — `[Human-friendly name](file:///absolute/path)`, never bare `file://` URLs or autolinks `<file://...>`, always absolute paths (never `~/` or relative), one bullet per file when there are several. (Bare `file://` URLs render only the first as clickable across multiple lines; named links stay independently clickable.)
|