ANTHROPIC_API_KEY is now the ONLY accepted credential. Through v0.1.0 the preflight cleared on CLAUDE_CODE_OAUTH_TOKEN, and run_s10 went further: an unset key printed "note: relying on the CLI's own credentials" and carried on. That note was not a warning, it was a decision - made silently, on the operator's behalf, about who pays. Both paths are gone; a run with no key refuses with exit 2 before anything is opened. Red first, both halves: _check_credentials refuses an OAuth-only env, and the run entrance is driven as a real subprocess with a deliberately missing bundle, so the credential refusal must win the race against the bundle error. Detach it and the process reaches navigate_bundle instead - a different exit code, no refusal line, the fallback back in the output. The positive control (key set) gets past the gate and fails on the bundle, so the gate is a gate and not a wall. 997 -> 1002, offline, no key in env. The SDK exception is now stated where a reader meets it, not implied: this framework runs on the Claude Agent SDK, which starts the Claude Code CLI it bundles as a subprocess. That is the SDK's intended use WITH an API key, and it is a deliberate, stated exception to the owner's rule that his own code never starts Claude Code. Rewriting to direct HTTP calls was weighed and declined - measuring what the Agent SDK offers is the point of D7. The repo is closed as a worked example. Two prose claims were corrected rather than left standing: run_s10.py is no longer byte-frozen (it carries exactly one change, and runs/s10/ is still the v0.1.0 run), and its two round() call sites moved 110->118, 130->138. The credential paragraph is prose under an existing heading, not a new section: test_readme_anchors_loadbearing.py pins 14 heading ids MEASURED on the published page and forbids re-deriving them. This order forbids push, so a new heading could not have been honestly re-measured. Version 0.1.1: pyproject.toml, uv.lock self-entry, CHANGELOG - 3 of 3. No version badge in README, no constant in src. v0.1.0 stands as released. Order 20260920T131502Z-7496226791-from-.claude. The older D7 mirroring order 20260913T053840Z-9473220509 is retired unexecuted: po closes at v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.5 KiB
portfolio-optimiser-claude
Kontekst
Søsken-implementasjon (beslutning D7) av portfolio-optimiser-metoden på Claude Agent SDK:
generisk, åpent Python-rammeverk som finner kostnadsbesparelser INNI hvert prosjekt i en
portefølje. Bygges på metode-spec-en (shared/method-spec.md) som normativ kilde.
Åpen konkurranse med MAF-søskenet (~/repos/portfolio-optimiser, operatørbeslutning
2026-08-09): alle tilgjengelige midler er lov for å nå den beste løsningen, inkludert å lese
søskenets kode, og utvekslingen kan gå begge veier. Grensen er hensikten, ikke kilden:
kopiér kun der det faktisk gir mening for løsningen — aldri som snarvei. De reelle
begrensningene er hva MAF og Claude Agent SDK hver for seg tilbyr, og det er nettopp den
forskjellen sammenligningen skal måle. Fram til 2026-08-09 ble repoet bygget fra speccen
alene; det er datert i README og skal ikke påstås om arbeid etter den datoen. Program-planer
(sesjonsplan S5–S12, sammenligningsprotokoll, frossent målbilde) bor i hovedrepoets
docs/plan/. Løpende state: STATE.md (local-only).
Stack
Python ≥3.10. Claude Agent SDK (claude-agent-sdk ≥0.2, kildeverifisert 0.2.139 per
2026-08-18 — pinnen >=0.2.111,<0.3 er en TILLATELSE, _VERIFIED_THROUGH i
tests/test_sdk_version_guard.py er beviset og går rødt på en ulest nyere build. Per
2026-07-03: MIT-lisens, bundler Claude Code CLI — ingen separat installasjon; import og
konstruksjon fungerer uten API-nøkkel, nøkkel trengs kun ved faktisk query()).
Pakkehåndtering: uv. Pydantic for kontrakts-validering.
Konvensjoner
- Type hints overalt;
mypy --strict.rufffor lint+format.pytestfor test. - Testsuiten kjører uten API-nøkkel og uten nettverk (invariant fra S5; håndhevet ved at all kontrakts-/kontekst-logikk er ren config/fil-lag).
- Modell-valg som konfig:
src/portfolio_optimiser_claude/data/model_map.json(rolle→Claude-modell-id per backend-profil, hver profil kreverdefault; validert avcontracts.py). Modell-id-er verifiseres mot offisiell doc før endring. - SDK-primitiver (verifisert 2026-07-03):
query()/ClaudeSDKClient,ClaudeAgentOptions(max_turns,max_budget_usd— førsteklasses USD-tak),AgentDefinition(subagenter; sub-felt er camelCase),@tool+create_sdk_mcp_server(in-process MCP), hooks (PreToolUsemedpermissionDecision: "deny"for deterministisk gating). MERK:SessionStart/SessionEndfinnes IKKE som Python-callback-hooks. shared/er en git subtree avportfolio-optimiser-commons(source of truth; remotecommons). Synk er PULL-ONLY: endringer committes i commons og hentes medgit subtree pull --prefix=shared commons main --squash. ALDRIgit subtree pushfra konsument — re-split lekker hele konsument-historikken inn i commons (observert + opprydd i MAF-repoet 2026-07-03). Seshared/README.md.
Kommandoer
- Sync:
uv sync - Test:
uv run pytest - Lint:
uv run ruff check .+uv run ruff format . - Type:
uv run mypy src scripts - Bevis en søm:
uv run python scripts/mutation_harness.py --help(grønn-før/rød-etter)
Arbeidsflyt (invarianter)
- Rent teknisk rammeverk: deployer eier DPIA/ROS/behandlingsformål — kun tekniske forutsetninger (lokal-only, provenance, ingen stille egress) + disclaimer.
- Metode-spec-en er normativ (RFC 2119). Ærlighets-regelen (§1) er ufravikelig: ingen artefakt påstår mer enn implementasjonen gjør; skriptede stand-ins merkes som det.
- Deterministisk validator obligatorisk + blokkerende (§3 steg 4); golden-suiten
(
shared/examples/bygg-energi-mikro/golden.json) er ENESTE fasit (§7). - Mutasjonsharnesset er verktøyet, ikke en huskeregel (beslutning 2026-08-25, etter ti
håndbygg):
scripts/mutation_harness.pykjører grønn-før/rød-etter mot det EKTE treet og håndhever de fire kontrollene som ellers svikter stille — unikt anker, node-id-en faktisk kjørt (pytest rc=4 er IKKE rødt), restaurering sha256-verifisert fra disk, og rødhet på en pinnet linje. Tre roller, og rekkefølgen følger av hva hver måler:--redgrønn FØR (måleapparatets positivkontroll) og rød ETTER (selve målingen, ikke besvarbar før mutasjonen finnes),--greengrønn i BEGGE (treffer den, landet mutasjonen for bredt). Verktøyet ligger bevisst UTENFORtests/— suiten kjører hver økt, og en fixture som skriver til disk gjør enhver avbrutt kjøring til et mutert tre. Mutasjon av en KOPI i minnet hører derimot hjemme i suiten og er der alt (test_guard_red_when_*). - Load-bearing tester (§11): hver søm bevises med en test som blir RØD når sømmen detaches — grønn-men-død er feilmoden regelen finnes for.
- Stoppkriterier + budsjett-tak påkrevd ved oppstart (§8;
contracts.pyfail-fast). På kjørestien kommer USD-taket i tillegg viaClaudeAgentOptions.max_budget_usd. - Kostnadsdisiplin (D6): alt utvikles offline/deterministisk; den ENE ekte API-kjøringen i programmet er S10 (mikro-bundle, billigste egnede modell — per model_map Haiku 4.5 — harde tak, kost loggføres).
- Kun egen API-nøkkel (v0.1.1):
ANTHROPIC_API_KEYer ENESTE godtatte legitimasjon. Et abonnements-token (CLAUDE_CODE_OAUTH_TOKEN) AVVISES av preflight og av kjøre- inngangen, som ender i exit ≠ 0 — aldri et stille tilbakefall på CLI-ens innlogging. - Uttalt unntak (ikke skjult): repoet er bygget på Claude Agent SDK, som selv starter den medfølgende Claude Code-CLI-en som underprosess. Det er Anthropics tiltenkte bruk MED API-nøkkel, og står som et UTTALT unntak fra eierens regel om at egen kode ikke starter Claude Code. Omskriving til rene API-kall er vurdert og valgt bort — poenget med D7 er å måle hva Agent SDK-en tilbyr. Repoet er AVSLUTTET som eksempel: ingen videre utvikling er planlagt.
- STATE.md er local-only (gitignored — repoet skal publiseres; STATE når aldri et offentlig speil).
Communication patterns
Linking to local files
When pointing to local files in responses, always use markdown link syntax with a descriptive name:
- Use
[Human-friendly name](file:///absolute/path)— never barefile:///...URLs or autolinks<file://...>. - Always use absolute paths. Never
~/or relative paths. - For multiple files, render as a bullet list of named markdown links.
Why: bare file:// URLs only render the first as clickable across multiple lines. Named markdown links make each entry independently clickable and look cleaner.