feat(m2): add append-only decision log and beslutning skill

This commit is contained in:
Kjell Tore Guttormsen 2026-09-05 22:00:13 +02:00
commit 06b45e9168
4 changed files with 761 additions and 0 deletions

102
skills/beslutning/SKILL.md Normal file
View file

@ -0,0 +1,102 @@
---
name: beslutning
description: "Registrerer ja eller nei paa en vurdert stilling i den append-only beslutningsloggen, med begrunnelse fra en lukket enum og scoren slik den stod foer mennesket bestemte seg. Paa ja overleveres saken til sak-skillen. Utloeses av registrer en beslutning, ja på denne stillingen, ja pa denne stillingen, nei på denne stillingen, nei pa denne stillingen og korriger en beslutning."
triggers:
- registrer en beslutning
- ja på denne stillingen
- ja pa denne stillingen
- nei på denne stillingen
- nei pa denne stillingen
- korriger en beslutning
---
# beslutning
Skriver én linje til `beslutninger.jsonl` per stilling operatøren har tatt
stilling til. Loggen er append-only og **kan ikke migreres** — det som skrives
feil i dag, står feil for alltid, og kan bare rettes av en ny linje.
All operatørrettet tekst er norsk bokmål.
## Rekkefølgen
1. Stillingen er allerede vurdert av `kandidatvurdering`. Ta vare på `score`,
`delscore`, `vekter` og `vekt_hash` fra den kjøringen — de skal inn i
posten.
2. Spør operatøren rett ut: **ja eller nei?** Ikke anbefal ut fra scoren. Et
tall er et innspill til beslutningen, ikke beslutningen.
3. Be om én til tre årsaker fra den lukkede enumen, og et notat på **høyst
femten ord**.
4. Skriv posten.
5. Er svaret `ja`, overlever til `sak`.
## Skriv posten
```
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/beslutninger.py \
--workspace <arbeidsmappa> --json '<posten som JSON>'
```
På Windows heter tolken `python`, ikke `python3`.
Feltene er build-brief 5.4: `id`, `dato`, `kilde`, `url`, `tittel`,
`arbeidsgiver`, `beslutning`, `arsak`, `notat`, `score_da`, `delscore`,
`vekter`, `vekt_hash`, `korrigerer`. Scriptet legger til `type` og `skjema`.
`score_da` er scoren **slik den stod før mennesket bestemte seg**. Den er hele
læringssignalet: en høy score paret med et nei er nøyaktig det avviket M6 leter
etter. Regn den aldri ut på nytt, og rund den ikke av.
`dato` bærer alltid UTC-forskyvning, som ellers i denne pluginen.
Blir posten avvist, navngir avvisningen feltet. Rett feltet og skriv på nytt —
en avvist post ligger ikke halvveis på disken, valideringen kjører før fila
åpnes.
## Årsakene er tolv, og ikke flere
Hele enumen med norsk forklaring står i
`${CLAUDE_PLUGIN_ROOT}/skills/beslutning/references/arsak.md`. Les den før du
foreslår en årsak. Passer ingen, er svaret `annet` pluss et presist notat —
ikke en trettende årsak. En årsak utenfor enumen er en kategori `laering` aldri
kan telle, verken for eller mot.
## På `ja`: overlever til sak
Et `ja` skaper en sak; et `nei` skaper ingenting utover loggposten.
Sak-id-en utledes av beslutningen, ikke av skjønn: måneden i `dato`,
arbeidsgiveren og tittelen, foldet til ASCII. `sak_id_for(posten)` i
`${CLAUDE_PLUGIN_ROOT}/scripts/beslutninger.py` gir den.
Beslutningen speiles så inn i saksloggen med `speil_i_sakslogg(...)` fra samme
modul. **Dette steget hoppes aldri over.** Uten den speilede posten står saken
fast i `vurderer` for alltid — det er den ene posten som tar den til `soker`.
Deretter overtar `sak`: mappa, saksfila fra malen, og oppdateringen av
hurtigbufferen.
## En retting er en ny linje
Rediger aldri en linje som er skrevet. En korrigering er en egen post som
navngir beslutningen den retter:
```
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/beslutninger.py \
--workspace <arbeidsmappa> --korriger --json '<korrigeringen som JSON>'
```
Den kan rette kilde, url, tittel, arbeidsgiver, beslutning, årsak og notat.
Den kan **ikke** rette `score_da`: det tallet er en måling av hva scoren var da
valget ble tatt, og en måling endres ikke av at man senere mener noe annet.
Begge linjene blir liggende. Det operatøren først mente er en del av
materialet, og en logg som slettet det ville vært et dårligere læringssignal
enn en som beholder begge.
## Uten verktøyserveren
Er `jobbsok-tools` ikke tilgjengelig, degraderer skillen i stedet for å stoppe:
be operatøren kjøre kommandoene over fra kommandolinja, og les posten som ble
skrevet ut. Ikke skriv linja for hånd inn i fila — da omgås valideringen, og en
ugyldig linje i en append-only logg kan ingen fjerne.

View file

@ -0,0 +1,50 @@
# Årsaks-enumen
Referanse for `beslutning`. Build-brief 5.4 lukker lista på tolv verdier.
Implementert som `ARSAKER` i
`${CLAUDE_PLUGIN_ROOT}/scripts/beslutninger.py`; er de to uenige, er scriptet
fasit.
Én til tre årsaker per beslutning. Én er det vanlige. Tre er en beslutning med
nyanse. En fjerde blir avvist, fordi en beslutning med fire likestilte grunner
ikke er en grunn noen kan handle på senere.
| Verdi | Betyr | Typisk formulering |
|---|---|---|
| `lonn` | Lønnen er under gulvet, eller spennet gjør stillingen uaktuell | «under gulvet» |
| `geografi` | Sted, reisevei eller flytting | «for lang reisevei hver dag» |
| `arbeidsform` | Hjemmekontor, kontorplikt, turnus, reisedager | «full kontorplikt» |
| `fagomrade` | Feltet treffer ikke, eller treffer godt | «treffer kjernen» |
| `oppgavetype` | Hva arbeidet faktisk består i | «forvaltning, ikke arkitektur» |
| `teknologi` | Stakk, verktøy, plattform | «hele stacken er egenutviklet» |
| `senioritetsniva` | Rollen ligger over eller under nivået | «for junior rolle» |
| `selskapstype` | Konsulent, byrå, produkt, offentlig, oppstart | «bemanningsbyraa» |
| `arbeidsgiverrykte` | Det som er kjent om arbeidsgiveren som arbeidsplass | «gjentatte oppsigelsesrunder» |
| `tidspunkt` | Tiltredelse, frist, timing i eget løp | «tiltredelse alt for tidlig» |
| `konkurranse` | Vurdert sjanse mot feltet av søkere | «intern kandidat er varslet» |
| `annet` | Ingen av de elleve passer | krever et presist notat |
## Hvordan velge
- **Årsaken er den som faktisk avgjorde**, ikke den som er lettest å skrive.
En stilling som ble avslått på reisevei og *også* hadde lav lønn, føres på
`geografi`. Feil årsak er verre enn ingen: `laering` foreslår først når den
samme årsaken går igjen i minst fire av tjue, og en feilført årsak flytter
den terskelen for begge kategoriene.
- **Positive årsaker gjelder også.** Et `ja` begrunnes med den samme enumen —
`fagomrade` betyr «feltet treffer» like gjerne som «feltet treffer ikke».
Retningen ligger i `beslutning`, ikke i årsaken.
- **`annet` er et gyldig svar, ikke en unnvikelse** — men det krever et notat
som sier hva som faktisk avgjorde. Går `annet` igjen, er det enumen som skal
utvides, og det er en beslutning som tas ett sted: i build-brief 5.4. Ikke
finn opp en trettende verdi her.
## Notatet
Høyst femten ord. Kappet er ikke pynt: feltet er en etikett, ikke en
sekundær brødtekst, og et notat som vokser til et avsnitt er et notat ingen
leser tjue beslutninger senere.
Skriv det som skiller denne beslutningen fra nabobeslutningen med samme årsak.
«under gulvet» sier lite; «gulvet er ikke naadd, spennet stopper 80k under»
sier hva som faktisk skjedde.