&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Alle indlæg · build.golfnext.com</title>
    <link>https://build.golfnext.com/da/posts/</link>
    <description>Engineering, operations, and infrastructure at a small, ambitious team.</description>
    <language>da-DK</language>
    <copyright>© 2026 GolfNext ApS · Ry, Danmark</copyright><lastBuildDate>Thu, 27 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://build.golfnext.com/da/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Slet opsætningsguiden</title>
      <link>https://build.golfnext.com/da/posts/delete-the-setup-guide/</link>
      <pubDate>Thu, 27 Aug 2026 00:00:00 +0000</pubDate><dc:creator>Johann Böhler</dc:creator>
      <guid>https://build.golfnext.com/da/posts/delete-the-setup-guide/</guid>
      <description>Ethvert repository udleverer instruktioner, ingen holder ved lige, og en .env.example, som nogen skal udfylde ud fra en Slack-besked. To værktøjer — mise til værktøjskæden, fnox til hemmelighederne — flytter begge opgaver ud af README&#39;en og ind i repoet. Med et ærligt regnskab over, hvad det koster, og hvad det ikke løser.</description>
      <content:encoded><![CDATA[<p>Tænk på sidste gang du klonede et repository, du aldrig havde arbejdet i før.</p>
<p>Du læste README&rsquo;en. Den bad dig installere en JDK, og du havde den forkerte, så du gik ud og fandt den rigtige. Den bad dig installere Node, og versionsfilen sagde 18, mens lockfilen tydeligvis ville have 20. Et sted nede i bunden stod der <code>cp .env.example .env</code> og så, med mindre skrift, <em>udfyld værdierne</em>. Udfyld dem hvorfra? Fra en kollega. Så du spurgte på Slack, nogen indsatte en blok <code>KEY=value</code>-linjer til dig, og nu ligger et produktionsnært login i en chathistorik for altid: søgbart, backet op, eksporteret til hvilket compliance-værktøj virksomheden nu købte sidste år.</p>
<p>Ingen har sat sig for at bygge det. Det er, hvad der sker, når instruktionerne bor i et dokument i stedet for i repoet.</p>
<p>Jeg er tech lead hos GolfNext, en dansk virksomhed der bygger software til golfklubber, og mit første indlæg på denne blog var en opgørelse over, hvad jeg var trådt ind i. Otteogtres repositories, fire produktlinjer, tretten års akkumulerede teknologivalg: Java og Maven og Spring, Kotlin og Gradle, Node, Python, PHP, JavaFX, React Native. At komme i gang i et af dem fra kold start tog mellem to og seks timer afhængigt af, hvilket man valgte, og jeg satte mig selv et mål om fem minutter fra klon til kørende kode.</p>
<p>Det her er det første seriøse forsøg på det. To værktøjer, begge fra samme person, jdx. <a href="https://mise.jdx.dev/">mise</a> lægger de rigtige værktøjer på din PATH, <a href="https://fnox.jdx.dev/">fnox</a> lægger de rigtige hemmeligheder i dit miljø, og det er de to halvdele af ét problem. mise er kedeligt og bredt anvendt. fnox er ingen af delene endnu, og det vender jeg tilbage til. Det interessante er, hvad der falder væk, når begge er på plads, og hvad det gør ved de kodeagenter, jeg har kørende i de her repositories hele dagen.</p>
<p>Det er <em>mise</em> som i <em>mise en place</em>, det fra køkkenet. Udtales &ldquo;meez&rdquo;, /miːz/, én stavelse, uden <em>-uh</em> til sidst. Det vil du gerne have styr på, inden du siger det højt til et møde. fnox er nemmere: Fort Knox med de fleste bogstaver banket af, altså &ldquo;eff-nox&rdquo;, og navnet fortæller dig, hvad det er til.</p>
<h2 id="halvdel-et-værktøjerne">Halvdel et: værktøjerne<a class="gn-heading-anchor" href="#halvdel-et-v%c3%a6rkt%c3%b8jerne" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Har du brugt nvm eller pyenv, kender du formen: erklær en version, <code>cd</code> ind, få den version. Forskellen er, at mise er ligeglad med, hvad versionen er <em>af</em>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># .mise.toml</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tools</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">java</span> <span class="p">=</span> <span class="s2">&#34;21&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">maven</span> <span class="p">=</span> <span class="s2">&#34;latest&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">node</span> <span class="p">=</span> <span class="s2">&#34;lts&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">python</span> <span class="p">=</span> <span class="s2">&#34;latest&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">gitleaks</span> <span class="p">=</span> <span class="s2">&#34;latest&#34;</span>
</span></span></code></pre></div><p>Det er en JVM, et build-værktøj, en JavaScript-runtime, en Python og en hemmelighedsscanner (<a href="https://github.com/gitleaks/gitleaks">gitleaks</a>) i én fil, installeret med én kommando, på din PATH kun inde i dette katalogtræ. Tilføj <code>mise activate</code> til din shell-profil, og selv kommandoen forsvinder: <code>cd</code> ind i repoet, og værktøjerne er der, <code>cd</code> ud, og det er de ikke. Den sætter også de miljøvariable, der hører til, så <code>JAVA_HOME</code> peger på den JDK, dette projekt har fastlåst, i stedet for hvad dit styresystem sidst besluttede.</p>
<p>&ldquo;Versionsstyring&rdquo; underspiller det her, og jeg vil gerne være konkret om hvorfor. Det dyre ved en polyglot kodebase er skiftet, ikke den enkelte teknologi. Du bruger en formiddag i en Spring-service, hopper så til et Android-repo, så et lille Python-værktøj, og hvert hop er en chance for, at din shell er forkert på en måde, der giver en forvirrende fejl frem for en tydelig. I et af vores repositories fejler en nyere system-JDK ikke højlydt. Den ødelægger et mocking-bibliotek under test, adskillige lag nede, med en besked der intet har med Java-versioner at gøre. Det er en time af nogens liv, og du betaler den igen, hver gang en ny person starter, eller en computer bliver skiftet ud.</p>
<p>Et sprogspecifikt værktøj kan ikke hjælpe, fordi problemet ligger <em>mellem</em> sprogene.</p>
<p>En detalje, du lige så godt kan stjæle som opdage selv. Vi lader bevidst nogle af versionerne flyde: <code>latest</code> til scanneren, <code>lts</code> til Node. Hvad et <em>build</em> er lavet af, er fastlåst i CI-workflowet, og det er den fastlåsning, dependency-automatikken holder øje med og laver pull requests på. Fastlåste vi de samme versioner i <code>.mise.toml</code> også, ville vi have to steder at holde i sync, og det ene ville stille og roligt sakke bagud. Det ved vi, fordi det allerede var sket: en <code>.mise.toml</code> der påstod Node 22, mens alle fire workflows sagde 24. Kun én fil får lov at være den autoritative, og det er workflowet.</p>
<p>JDK&rsquo;en er undtagelsen, fastlåst hårdt begge steder med vilje. En forkert major-version dér er præcis den mocking-fejl ovenfor, og jeg holder hellere to filer i sync end genopdager den fejl klokken fire om eftermiddagen. Alt andet flyder.</p>
<h2 id="den-del-jeg-ikke-havde-regnet-med-at-holde-af">Den del, jeg ikke havde regnet med at holde af<a class="gn-heading-anchor" href="#den-del-jeg-ikke-havde-regnet-med-at-holde-af" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Ethvert repository har en håndfuld ting, man gør ved det: bygger det, tester det, kører det, formaterer det. I et Maven-projekt er det <code>mvn spring-boot:run -Dspring-boot.run.profiles=local</code>. I et Gradle-projekt er det <code>./gradlew</code> og et task-navn med fire ord klistret sammen. I et Node-projekt er det, hvad <code>scripts</code>-blokken nu siger i denne uge. Intet af det er svært. Alt af det er noget, man skal slå op, når man er i et ukendt repo, hvilket er præcis dér, hvor man har mindst tålmodighed til at slå noget op.</p>
<p>mise lader dig lægge de udsagnsord i samme fil som værktøjerne:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Package the artifact&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;mvn clean package -B&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">test</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Full test suite (integration tests need Docker)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;mvn clean test -B&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">serve</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Run the API on http://127.0.0.1:5001 (starts the stack first)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">depends</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;stack:up&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;mvn spring-boot:run -Dspring-boot.run.profiles=local&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="s2">&#34;stack:up&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Start the local database and the mocks. Idempotent&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;docker compose up -d --wait&#34;</span>
</span></span></code></pre></div><p>Så bliver grænsefladen <code>mise run build</code>, <code>mise run test</code>, <code>mise run serve</code>. I Android-repoet ved siden af ligger de samme udsagnsord oven på Gradle i stedet, og et af dem pakker et task-navn på fire ord, jeg stadig ikke kan skrive udenad. Lige meget. <code>mise tasks</code> viser, hvad et repository kan, med beskrivelser, og svaret ligger i repoet frem for på en wiki-side, der holdt op med at være sand i 2023.</p>
<p>To små konventioner har gjort sig fortjent. Et bart udsagnsord er det almindelige tilfælde, og <code>verb:qualifier</code> indsnævrer det: <code>test</code> og <code>test:signer</code>, <code>stack:up</code> og <code>stack:down</code> og <code>stack:clean</code>. Der er også altid en nødudgang, som pakker det underliggende build-værktøj direkte, så alt, den kuraterede liste ikke dækker, stadig er én kommando væk:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">mvn</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Any Maven command, with this repo&#39;s JDK and environment&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;mvn&#34;</span>
</span></span></code></pre></div><p>Den nødudgang får betydning i anden halvdel af historien.</p>
<h2 id="halvdel-to-hemmelighederne">Halvdel to: hemmelighederne<a class="gn-heading-anchor" href="#halvdel-to-hemmelighederne" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Det her er den del, jeg faktisk ville til livs.</p>
<p><code>.env.example</code>-mønstret er så udbredt, at det læses som best practice. Det er en konvention, der gav op. Se på, hvad den beder et team om.</p>
<p>En ny udvikler kopierer en fil med tomme nøgler og skal så skaffe rigtige værdier fra et sted, repoet ikke kan fortælle dem om. I praksis betyder det en person, hvilket betyder en chatbesked, og nu findes en hemmelighed i endnu et system med andre opbevaringsregler og anden adgangsstyring end det, man med vilje havde valgt. Når en værdi roteres, er enhver computer med den gamle stille og roligt forkert, indtil nogen opdager det, og fejlen dukker som regel op som en forvirrende 401 frem for en besked der siger &ldquo;dit login er fire måneder gammelt&rdquo;. Når en ny hemmelighed tilføjes, er alle andres <code>.env</code> ufuldstændig, og ingen får det at vide. Og filen selv ligger i dit arbejdskatalog i klartekst, ét uheldigt <code>git add -A</code> fra at blive committet, hvilket er grunden til, at ethvert repository med dette mønster også har en <code>.gitignore</code>-linje, nogen kunne slette.</p>
<p>Alternativet er ikke kompliceret. Læg værdierne i et hemmelighedslager, du allerede stoler på, læg <em>referencer</em> til dem i repoet, og lad den enkelte udviklers egen identitet afgøre, om de kan slås op.</p>
<p>Det er fnox. Her er stage-konfigurationen for en service, committet, uden værdier nogen steder:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># fnox.stage.toml</span>
</span></span><span class="line"><span class="cl"><span class="nx">env</span> <span class="p">=</span> <span class="s2">&#34;exec&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">providers</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">store</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">type</span> <span class="p">=</span> <span class="s2">&#34;…&#34;</span><span class="p">,</span> <span class="nx">prefix</span> <span class="p">=</span> <span class="s2">&#34;/apps/checkout/stage/&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">secrets</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">DB_PASSWORD</span>    <span class="p">=</span> <span class="p">{</span> <span class="nx">provider</span> <span class="p">=</span> <span class="s2">&#34;store&#34;</span><span class="p">,</span> <span class="nx">value</span> <span class="p">=</span> <span class="s2">&#34;DB_PASSWORD&#34;</span><span class="p">,</span>    <span class="nx">if_missing</span> <span class="p">=</span> <span class="s2">&#34;error&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">PAYMENT_API_KEY</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">provider</span> <span class="p">=</span> <span class="s2">&#34;store&#34;</span><span class="p">,</span> <span class="nx">value</span> <span class="p">=</span> <span class="s2">&#34;PAYMENT_API_KEY&#34;</span><span class="p">,</span> <span class="nx">if_missing</span> <span class="p">=</span> <span class="s2">&#34;error&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">SIGNING_KEY</span>    <span class="p">=</span> <span class="p">{</span> <span class="nx">provider</span> <span class="p">=</span> <span class="s2">&#34;store&#34;</span><span class="p">,</span> <span class="nx">value</span> <span class="p">=</span> <span class="s2">&#34;SIGNING_KEY&#34;</span><span class="p">,</span>    <span class="nx">if_missing</span> <span class="p">=</span> <span class="s2">&#34;error&#34;</span> <span class="p">}</span>
</span></span></code></pre></div><p><code>type</code> er den eneste linje, der er specifik for dig, og jeg har ladet den stå tom med vilje, fordi hvilket lager man bruger, er den mindst interessante beslutning i hele opsætningen. fnox taler med <a href="https://developer.hashicorp.com/vault">Vault</a>, <a href="https://developer.1password.com/">1Password</a>, <a href="https://github.com/getsops/sops">SOPS</a> og de store cloud-udbyderes egne hemmelighedslagre. Vi tog det, vores udbyder allerede gav os, af den kedelige grund at vi betalte for det, og det allerede havde en adgangsstyring, vi stolede på. Alt andet i filen er det samme, uanset hvad man vælger.</p>
<p>Hvad den fil køber:</p>
<p>Repoet dokumenterer sine egne hemmeligheder. Hvert navn, en service har brug for, står ét sted, i git, med reviewhistorik. Ny hemmelighed? Den står i filen, den kommer med <code>git pull</code>, og næste kørsel finder den enten eller fejler højlydt på grund af det <code>if_missing = &quot;error&quot;</code>. Ingen skal have noget at vide.</p>
<p>Rotation holder op med at være en begivenhed. Skift værdien i lageret; enhver maskine samler den op ved næste kørsel. Der er ingen flåde af forældede kopier.</p>
<p>Adgang bliver igen et autorisationsspørgsmål. Om du kan køre mod et givent miljø, afgøres af din identitet i hemmelighedslageret, ikke af om nogen huskede at sende dig en fil. At fjerne nogens adgang bliver en rettighedsændring i stedet for et håb om, at de sletter en fil.</p>
<p>Og der er en tørkørsel. <code>fnox check</code> fortæller dig, om hver erklæret hemmelighed kan slås op af dig, uden at udskrive nogen af dem. Det er det hurtigst mulige svar på &ldquo;er det mig eller er det i stykker&rdquo;, hvilket er det spørgsmål, der æder onboarding-dage.</p>
<p>Så én bevidst tilbageholdenhed. Den <code>env = &quot;exec&quot;</code>-linje betyder, at hemmeligheder aldrig injiceres i en interaktiv shell, kun i miljøet for en underproces, fnox starter. fnox vil med glæde gøre det modsatte; der findes en tilstand, som lægger alt ind i din shell i det øjeblik, du træder ind i kataloget. Vi bruger den ikke. Jeg vil ikke have, at enhver proces startet fra det katalog arver produktionsnære credentials, og det gælder også editorer, og det gælder de kodeagenter, jeg har kørende derinde det meste af dagen. At begrænse hemmeligheder til <code>fnox exec -- &lt;kommando&gt;</code> er hele forskellen mellem &ldquo;buildet kan se signeringsnøglen&rdquo; og &ldquo;alt på min maskine kan se signeringsnøglen&rdquo;.</p>
<p>Netop den del havde jeg undervurderet. Med intet på disken og intet i shellen findes en hemmelighed kun inde i en proces, som noget bevidst har startet, hvilket gør &ldquo;hvad kan agenten nå&rdquo; til en helt almindelig rettighedsbeslutning. Nægt den <code>fnox</code> og den håndfuld tasks, der kalder det, tillad den alt andet, og den kan læse hele repoet, bygge, teste og formatere, uden at en credential nogensinde havner i dens kontekst.</p>
<p>Så hullet, som jeg hellere selv vil sætte navn på, end at få det gjort for mig. fnox læser lageret som <em>mig</em>, med credentials der ligger på min maskine alligevel. En agent med en shell har slet ikke brug for fnox; den kan spørge lageret med udbyderens egen CLI. At nægte <code>fnox</code> alene lukker hoveddøren i en bygning uden vægge. Det, der gør det egentlige arbejde, er, at den samme tilladelsesliste heller ikke indeholder den CLI, og at det, der er inden for rækkevidde fra en udviklermaskine, er stage frem for produktion. En agent, der kan redigere <code>.mise.toml</code>, kan også skrive sig selv en task, der kalder hvad som helst. Nægtelsen er et hegn, ikke et bevis.</p>
<p>Men hold det op mod det, den erstatter: en <code>.env</code>-fil i arbejdskataloget i klartekst, læsbar for alt, der kan kalde <code>cat</code>, hvilket er enhver agent, nogen nogensinde har kørt. Injektion udelukkende ved kørsel plus en nægtelsesregel er, så vidt jeg kan se, omtrent så godt, som det bliver lige nu.</p>
<h2 id="hvor-de-to-halvdele-mødes">Hvor de to halvdele mødes<a class="gn-heading-anchor" href="#hvor-de-to-halvdele-m%c3%b8des" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Hver for sig er det to udmærkede værktøjer. Sammen fjerner de en hel kategori af manuelle skridt.</p>
<p>Fordi mise-tasks bare er kommandoer, kan en task være <code>fnox exec -- noget</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">gradle</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;The escape hatch from earlier, now with the build secrets loaded&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;fnox exec -- ./gradlew&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">secrets</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Check that every build secret resolves for you&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;fnox check&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="s2">&#34;serve:stage&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Run locally against stage secrets (needs cloud access)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">depends</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;stack:up&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;fnox -c fnox.stage.toml exec -- mvn spring-boot:run&#34;</span>
</span></span></code></pre></div><p>Nu er <code>mise run gradle :app:assembleRelease</code> et signeret release-build, og den, der kører det, skriver aldrig ordet fnox, lærer aldrig hvilke af buildets input der er hemmelige, og har aldrig en kopi af nogen af dem. Ingen skal huske, hvilke kommandoer der kræver hemmeligheder. Det gør tasken.</p>
<p>Der ligger også en til dem, der lever i en IDE:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">ide</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Launch the IDE with the build secrets in its environment (macOS)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s1">&#39;fnox exec -- open -na &#34;Android Studio&#34; --args &#34;$PWD&#34;&#39;</span>
</span></span></code></pre></div><p>En IDE ved intet om noget af det her, så et build startet indefra den kan ikke signere. Start IDE&rsquo;en <em>gennem</em> fnox, og dens Gradle arver miljøet. Det er én linje, og den er forskellen på et fungerende release-build og en signeringsfejl, ingen kan forklare, så den bliver i filen. Jeg griber ikke selv ud efter den ret tit: det meste af min dag er en terminal, en prompt og en agent, der skriver. Det er en præference, ikke en anbefaling, og tasken ligger der til den, der vil det modsatte.</p>
<p>Så er der opdelingen efter miljø. Én fil hver, de samme variabelnavne i dem alle, forskellige værdier bag navnene:</p>
<pre tabindex="0"><code>fnox.toml         # local
fnox.stage.toml   # stage
fnox.prod.toml    # production
</code></pre><p>En task peger på den fil, den har brug for. Alt, der hører til et miljø, ligger i det miljøs fil, så ingen kan komme til at parre stage-credentials med lokale indstillinger.</p>
<p>Den lokale er den fil, jeg ville pege på, hvis du kun læste én. Den erklærer næsten ingen hemmeligheder, fordi alt, hvad en lokal kørsel har brug for, allerede har en harmløs standardværdi committet i repoet: en lokal database, en mock foran alt eksternt, åbenlyst falske nøgler til de betalte tjenester. En frisk klon booter og svarer for en, der slet ikke har adgang til hemmelighedslageret, og alt, der ville nå en rigtig udbyder, fejler autentificering frem for at gøre noget dyrt. Adgang køber dig <em>stage</em>. Det er ikke prisen for at komme ind.</p>
<h2 id="at-committe-referencer-betyder-at-scanne-efter-værdier">At committe referencer betyder at scanne efter værdier<a class="gn-heading-anchor" href="#at-committe-referencer-betyder-at-scanne-efter-v%c3%a6rdier" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>At committe referencer til hemmeligheder virker kun, hvis du er sikker på, at intet ægte nogensinde smutter med. Så den samme opsætning bærer en scanner rundt, og mise installerer den, uden at nogen bliver bedt om det:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">tools</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">gitleaks</span> <span class="p">=</span> <span class="s2">&#34;latest&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">hooks</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">postinstall</span> <span class="p">=</span> <span class="s2">&#34;git config core.hooksPath .githooks&#34;</span>
</span></span></code></pre></div><p><code>mise trust</code> er det første, nogen kører, og den første <code>mise run</code> derefter installerer værktøjskæden, hvilket også er det, der udløser postinstall-hooket og peger git på et katalog med en pre-commit-scanning. Den kommer som en sidegevinst ved det skridt, de alligevel skulle tage, frem for som en separat instruktion, folk springer over.</p>
<p>Hooket scanner kun staged indhold, så det er hurtigt, og hvis mise ikke er på PATH, springer det over med en høj advarsel i stedet for at fejle. En grafisk git-klients miljø er ofte minimalt, og at blokere nogens commit på grund af et PATH-problem er en god måde at lære dem at bruge <code>--no-verify</code> for altid. CI scanner de samme commits igen efter push.</p>
<h2 id="og-deployments">Og deployments<a class="gn-heading-anchor" href="#og-deployments" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Folk går som regel ud fra, at netop denne del virker anderledes, end den gør.</p>
<p>Vores deployede miljøer kører ikke fnox. Platformen, de kører på, læser selv de samme værdier fra det samme lager og giver dem til processen som ganske almindelige miljøvariable. Ingen applikationskode ved noget om det. Intet i runtimen importerer et bibliotek, og intet build skal have at vide, hvilket miljø det er til.</p>
<p>Det er med vilje. Lageret er sandheden. fnox er, hvordan et <em>menneske</em> læser den sandhed, med sine egne credentials, på sin egen maskine, og deploymentet læser samme sandhed med maskinens identitet. De to kan ikke komme fra hinanden, og applikationen ser på begge sider bare miljøvariable, som den altid har gjort.</p>
<h2 id="hvad-det-ikke-løser">Hvad det ikke løser<a class="gn-heading-anchor" href="#hvad-det-ikke-l%c3%b8ser" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Den ærlige version, så.</p>
<p>Du skal stadig installere mise og lægge <code>mise activate</code> i din shell-profil. Det er et rigtigt opsætningsskridt, og det vil jeg ikke lade som om ikke findes. Det er ét skridt, det er det samme skridt i alle otteogtres repositories, og det bliver aldrig forældet, hvilket er tre ting, det afsnit det erstattede ikke kunne sige.</p>
<p>Docker er stadig dit eget. mise installerer værktøjer, ikke dæmoner. Vores lokale stacks kræver containere, og førstegangsoplevelsen for enhver uden Docker sat op er stadig et manuelt skridt og en download målt i gigabyte. Det samme gælder Xcode, som fortsat er sit eget vejrsystem.</p>
<p>At få værktøjskæden rigtigt fjerner den dumme del af en koldstart. Det gør intet ved den svære del, som er at forstå, hvad servicen egentlig laver. Det er stadig at læse kode, og det tager stadig dage.</p>
<p>Du lægger to afhængigheder oven i enhver udviklers hverdag. mise er modent og bredt anvendt. fnox er ungt. Vi har lagt rigtige arbejdsgange over i det, og går det i stå, står vi med en migrering. Jeg synes, det er et rimeligt væddemål: filen er deklarativ, værdierne ligger i et lager, vi ville beholde alligevel, og faldbacken er et shell-script, der læser de samme parametre. Men det <em>er</em> et væddemål, og at lade som om noget andet ville være uærligt.</p>
<p>Én linje i den konfiguration fortjener et grundigt review. fnox kan få besked på at køre en kommando, der logger dig ind i lageret, når din session er udløbet, hvilket er oprigtigt praktisk og samtidig betyder, at en committet konfigurationsfil nu bestemmer noget, der eksekverer på enhver reviewers computer. Læs den linje, som du ville læse et CI-workflow.</p>
<p>Der er også en fejltilstand med en vildledende besked. Peg udbyderen på den forkerte konto, og det, der kommer tilbage, er &ldquo;not found&rdquo;, hvilket læses som en manglende hemmelighed frem for en forkert identitet, og sender folk på jagt det helt forkerte sted. Begge vores filer bærer en kommentar, der siger præcis hvilken konto credentials skal slå op i.</p>
<h2 id="hvor-vi-faktisk-er">Hvor vi faktisk er<a class="gn-heading-anchor" href="#hvor-vi-faktisk-er" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>To repositories helt på fnox. En håndfuld mere på mise. Tres og lidt til tilbage, hvoraf de fleste aldrig får brug for hemmelighedshalvdelen.</p>
<p>Jeg har ikke taget tid på det med et stopur, og jeg vil ikke opfinde et tal, jeg ikke har målt. Formen på svaret er alligevel klar nok. På en maskine, der har været igennem det her én gang før, med mise installeret og værktøjskæden i cachen og Docker kørende, er en klon til en svarende API under et minut. Rigeligt. Det, der er tilbage af de gamle to til seks timer, er alt sammen første-gang-på-denne-computer: at hente en JDK, trække images, installere mise selv. Den betaler man én gang frem for én gang per repository, og netop den anden del er hele forskellen fra, hvor vi startede.</p>
<p>I de repositories, hvor det her er landet, gik README&rsquo;ens opsætningsafsnit fra en liste over ting at installere og et afsnit om at spørge en kollega ned til det her:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mise trust          <span class="c1"># once, to allow this repository&#39;s config</span>
</span></span><span class="line"><span class="cl">mise run serve
</span></span></code></pre></div><p>To linjer, og den anden er den, du ville have alligevel. Den første er mise, der spørger, om du nu også stoler på en konfigurationsfil, som er ved at installere software og sætte miljøvariable, hvilket, i betragtning af at den fil nu kan køre kommandoer på din maskine, er en dialog jeg er glad for findes.</p>
<p>Mønstret generaliserer et godt stykke ud over vores egen teknologiske virkelighed, og derfor synes jeg, det er værd at skrive ned frem for at arkivere som en intern pligtopgave.</p>
<p>Slet så opsætningsguiden, for nu lyver den om, hvor meget arbejde der er.</p>
]]></content:encoded>
      <category>Teknik</category>
      <category>Infrastruktur</category>
    </item>
    <item>
      <title>Fire produkter, 68 repositories</title>
      <link>https://build.golfnext.com/da/posts/four-products-68-repos/</link>
      <pubDate>Wed, 12 Aug 2026 00:00:00 +0000</pubDate><dc:creator>Johann Böhler</dc:creator>
      <guid>https://build.golfnext.com/da/posts/four-products-68-repos/</guid>
      <description>To uger inde hos GolfNext. Hvad vi rent faktisk bygger, hvordan tretten års software ser ud under det, de integrationer vi startede i uge to, og et ærligt kig på de mål, jeg satte mig selv, før jeg havde skrevet under på noget.</description>
      <content:encoded><![CDATA[<p>Jeg startede hos GolfNext mandag den 3. august klokken 8:00. Jeg havde forestillet mig et stille kontor og tyve minutters ventetid. Størstedelen af teamet var der allerede og i godt humør. Jeg havde en cheesecake med, som vi spiste efter frokost, for jeg har været i Danmark længe nok til at vide, at det der med kage ikke rigtig er valgfrit.</p>
<p>Peter, der startede virksomheden i 2012, havde Claude åben, før jeg havde fundet mit skrivebord, og var i gang med de konti og rettigheder, jeg skulle bruge. En lille ting at lægge mærke til på en første morgen. Den fortalte mig mere end et onboarding-deck ville have gjort.</p>
<p>Det første indlæg på denne blog var et brev, jeg skrev til mig selv, før jeg havde skrevet under på noget, fyldt med forudsigelser om en virksomhed, jeg havde besøgt én gang. Her holder jeg det op mod det, der faktisk er her. Det meste af det følgende handler dog ikke om mig. Det er virksomheden, jeg gerne vil beskrive: hvad vi bygger, hvad der ligger under det efter tretten år, og hvad vi har tænkt os at gøre ved det.</p>
<h2 id="hvad-golfnext-bygger">Hvad GolfNext bygger<a class="gn-heading-anchor" href="#hvad-golfnext-bygger" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Hvis du aldrig har tænkt over, hvilken software en golfklub kører på, ser det sådan her ud. En klub er en lille virksomhed med flere kasketter på én gang. Der er en proshop, en driving range, medlemskaber, et bookingsystem og i stigende grad en simulator eller to. Bookingsystemet håndterer tee times, altså de starttider, hvor en gruppe er booket til at slå ud på første hul. Hele dagen i en klub er bygget op omkring det gitter.</p>
<p>Historisk kom hver af de kasketter som sin egen kasse, fra sin egen leverandør, med sin egen software vedhæftet. Køb automaten, få automatproducentens system, uanset om det passer til den måde, du driver din klub på.</p>
<p>Vores position er den modsatte. Jeres hardware og infrastruktur forbliver jeres, og I er frie til at vælge den software, der passer. Vi vil være åbne over for enhver integration, så uanset hvilket managementsystem en klub allerede bruger til sine tee times, er målet at spille sammen med det i stedet for at bede dem om at smide det ud. Frihed til at vælge, kraft til at forbinde.</p>
<p><strong>Selvbetjeningskiosker.</strong> En digital reception, der kører døgnet rundt. Greenfee, udlejning og køb i proshoppen, uden at nogen skal stå bag en disk. Så vidt vi ved er den den eneste af sin slags i golf. En klub får færre bemandede timer og tjener fortsat penge uden for åbningstiden. En spiller får den samme bekvemmelighed, som vedkommende allerede har alle andre steder i sit liv.</p>
<p><strong>Betaling på driving rangen.</strong> Betaling på rangen, der virker med alle mærker af boldautomater. Den kompatibilitet er produktet. Automaten tilhører klubben, eller den der solgte den til dem, og at kunne ligge oven på dem alle sammen er hele pointen. Kort, apps, wallets, pinkoder, med analytics bagved i realtid. Mange steder kører rangen stadig på kontanter, og det er her, efterspørgslen vokser hurtigst lige nu. Mere end 500 millioner bolde om året går igennem det. Det bliver til en hel del slicede slag.</p>
<p><strong>Voucher-håndtering.</strong> Udstedelse, distribution og indløsning, alt sammen digitalt, fra en enkelt billet til en hel sponsorpakke.</p>
<p><strong>Simulator-håndtering.</strong> Automatiseret adgangskontrol, så en simulator kun kører i en betalt session. Ikke mere tidstyveri, bedre udnyttelse.</p>
<p>Hvad det løber op i i dag: 400+ anlæg, 18 lande, 350.000+ aktive brugere. Grundlagt i Danmark i 2012 og stadig meget dansk, hvilket også er grunden til, at jeg gik hjem fra min første dag fast besluttet på at tage mit danske alvorligt.</p>
<h2 id="stakken-uden-pynt">Stakken, uden pynt<a class="gn-heading-anchor" href="#stakken-uden-pynt" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Otteogtres repositories. JavaFX på desktopsiden, PHP, Vue, React, React Native, Spring, Java, Kotlin. Det hele på AWS.</p>
<p>Skrevet ned på den måde ligner det ubeslutsomhed, og det er det ikke. Tretten år, fire produktlinjer, hardware ude i marken, og i det meste af den historie blev softwaren bygget af eksterne partnere, der var gode til deres arbejde. Hvert projekt valgte det, der gav mening den dag, det startede, og de fleste af de valg var fornuftige. Det, der aldrig skete, er, at nogen gik tilbage bagefter og fik valgene til at stemme overens.</p>
<p>Så, den ærlige tilstand, for jeg skrev i brevet, at jeg ville skrive den slags ned i stedet for at pudse det af.</p>
<p>En pæn del af de otteogtres repositories var ikke blevet rørt i årevis og blev aldrig arkiveret. Forks, spikes, engangsværktøjer, der overlevede deres formål. Jeg begyndte at arkivere i den første uge, langsomt, for de fleste af dem vidste jeg endnu ikke, hvad jeg kiggede på, og ikke at vide er en langt bedre grund til at lade noget være end til at slette det.</p>
<p>I koden kan man læse generationerne. Flere pakkestrukturer inden for det samme sprog, ingen af dem forkerte, alle sammen forskellige. En <code>AGENTS.md</code> skrevet til Codex og en <code>CLAUDE.md</code> skrevet til Claude, der ligger i det samme repository, beskriver det samme projekt og er uenige om det. En <code>README.md</code>, der dokumenterer en version af systemet, som ikke findes længere. En runtime, der gik end of life for et stykke tid siden og stadig kører. Og en release-proces, der er rigtig god for nogle af vores værktøjer og en snowflake for andre, hvor en del af den ligger i en pipeline og en del af den ligger i hovedet på nogen.</p>
<p>Sikkerhed ligger øverst i bunken. Af de niogtyve punkter om engineering practices, jeg har skrevet ned indtil videre, er fem markeret med høj prioritet, og alle fem handler om sikkerhed. Det er ikke en opdagelse om GolfNext specifikt. Det er sådan tretten års levering plejer at se ud alle steder, hvor ingen for nylig er blevet betalt for at gå og kigge efter.</p>
<p>Da jeg lavede en forudsigelse om det hele udefra, skylder jeg dig resultatet. Det ligger nogenlunde, hvor jeg regnede med, og det mener jeg som et fair udfald snarere end en klage. Det her er tretten års kontinuerlig levering fra folk, der sendte funktioner ud, som kunderne rent faktisk havde brug for, på deadlines, med de prioriteter de fik stukket i hånden. Man ville finde nogenlunde det samme billede i de fleste virksomheder på den alder, og jeg vil hellere være den, der rydder op, end den, der sukker over det. Den ene forudsigelse, der ramte præcist, var den kedelige: jeg gættede udefra på, at repoerne ville være underautomatiserede, og det er de. Det er min åbning.</p>
<h2 id="det-jeg-tog-fejl-af">Det, jeg tog fejl af<a class="gn-heading-anchor" href="#det-jeg-tog-fejl-af" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Jeg havde gjort mig klar til, at produkterne ville være det svage punkt. Virksomheder med en stak som vores har ofte &ldquo;sendt-alligevel&rdquo;-software ovenpå, den slags hvor man kan mærke deadlinen i brugerfladen.</p>
<p>Sådan er det ikke her. Produkterne er gode. Der er plads til forbedring i dem alle sammen, selvfølgelig. Men det, jeg bliver ved med at lægge mærke til i møder, er, at alle tænker fra kundens side først, også folk uden nogen teknisk baggrund overhovedet. Hvad oplever spilleren, der står ved kiosken klokken halv seks om morgenen. Hvad skal en klubmanager forklare et medlem, når noget går galt.</p>
<p>Det instinkt er svært at eftermontere i et team. Pakkestrukturer kan refaktoreres og README-filer kan skrives om, og begge dele er bare arbejde. At lære en gruppe mennesker at bekymre sig om personen i den anden ende er et problem af en helt anden slags, og det har vi ikke.</p>
<h2 id="det-vi-startede-i-uge-to">Det vi startede i uge to<a class="gn-heading-anchor" href="#det-vi-startede-i-uge-to" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>To integrationer landede næsten med det samme, og de er det samme væddemål i forskelligt tøj. Platformen er kun åben, hvis den rent faktisk forbinder til de systemer, klubberne allerede kører.</p>
<p><strong>teeControl</strong> er et managementsystem, som mange klubber bruger, og vi integrerer det, så det virker gennem vores kiosker. Førstehåndsindtrykket er godt. Førstehåndsindtryk er også billige, og der er et stykke vej herfra. Vi har brug for ordentlige testsystemer. Vi kommer sandsynligvis til at skulle justere nogle af vores egne flows, så de passer til deres. Der er stadig beslutninger, der skal falde på plads i begge ender. Det bliver en proces snarere end en sprint, og det siger jeg hellere nu end at skrive et triumferende indlæg om det i uge to.</p>
<p><strong>Lightspeed</strong> er en spillervendt app, som vi gerne vil understøtte som betalingsmulighed. Peter havde allerede bygget en første version, før jeg kom, og at overtage den var min anledning til at få fingrene i rigtig kode her for første gang. Ti tusind linjer, læst fra ende til anden, og derefter kørt og rodet med, indtil jeg forstod, hvad den lavede. Det gav en liste af fejl og en stak opfølgende stories, hvilket er det, der kommer ud af at læse hvad som helst grundigt. Det mere nyttige resultat er, at den nu også ligger i mit hoved og ikke kun i hans.</p>
<p>Og <strong>Verifone</strong>-terminaler på skrivebordet, som er den jævnlige påmindelse om, at en stor del af vores software ender i en fysisk genstand, som nogen skal røre ved.</p>
<p>Den tilgang, John og jeg landede på, er at springe den store forudgående specifikation over. John er den anden udvikler, selvom det ikke yder ham retfærdighed, for han er lige så meget løsningsarkitekt og projektleder som ingeniør. Få en testinstans, byg op mod den, lad virkeligheden skrive dokumentationen. To uger inde kender jeg ikke systemerne godt nok til at skrive en god specifikation, og at lade som om ville bare producere et dokument, vi stille og roligt ville holde op med at læse i september.</p>
<p>En lille ting, der er værd at give videre til enhver, der overvejer at arbejde et sted som her. Jeg brugte mine første par dage på at være tålmodig omkring adgange, ud fra en antagelse om, at der var en proces, jeg skulle vente på. Så sagde jeg højt, at det her stort set var det, jeg var blevet ansat til, og om jeg måtte få dem nu. En halv time senere havde jeg AWS. Ventetiden var udelukkende min egen opfindelse.</p>
<h2 id="hvordan-vi-udruller">Hvordan vi udruller<a class="gn-heading-anchor" href="#hvordan-vi-udruller" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Det mest interessante problem i de første to uger lå ikke inde i et repository. Det var at finde ud af, hvordan ændringer kommer ud til kunderne.</p>
<p>Jeg brugte en fredag på ordentligt detektivarbejde. CloudWatch-logs på den ene skærm, et downloadet database-snapshot på den anden, og transaktionstilstande stillet op mod det, koden mener burde være sket. Et sted derinde fandt jeg fyrre commits, der aldrig var blevet pushet nogen steder. Det er den ucharmerende halvdel af at hente viden hjem. Adgange er den nemme del. At genopbygge en forståelse af, hvad systemet rent faktisk gør, tager meget længere tid.</p>
<p>Vi har ikke nogen gradvis udrulning i dag. Det, vi har, er kunder, der er fysiske steder, og en håndfuld af dem ligger en kort køretur fra kontoret. Så den første ring er geografi. Nogle få nærliggende klubber får den nye version først, og hvis det går galt, er redningsplanen tyve minutter i bil med en reserveterminal i bagagerummet. Det tog omkring fem minutters samtale at nå frem til, for når man først lægger mærke til, hvor kunderne er, er det et ret oplagt træk. Det er ikke det, jeg ville tegne på en tavle. Det er tilgængeligt i denne uge, og det køber tillid nok til at blive ved med at levere, mens det ordentlige bliver bygget.</p>
<h2 id="at-lære-spillet-og-ordene-for-det">At lære spillet og ordene for det<a class="gn-heading-anchor" href="#at-l%c3%a6re-spillet-og-ordene-for-det" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>På dag tre spillede jeg den første golfrunde i mit liv, sammen med kolleger. Syv eller otte slag per hul. Aldrig ti. Vi mistede ikke en eneste bold hele runden, hvilket jeg får at vide er den mere imponerende statistik for en begynder. Ingen skrev en score ned.</p>
<p>Jeg vil anbefale den generelle version af det til enhver, der starter i en virksomhed, hvis domæne de ikke kender. Ikke golf specifikt. At gå ud og gøre det, softwaren handler om.</p>
<p>Ugen efter satte John og jeg os ned til det, der blev de mest nyttige to timer i uge to: at blive enige om, hvad vi kalder tingene, før tre systemer hver opfinder deres eget ord for det samme begreb. Ni huller er en loop. Stroke index rangerer hullerne efter sværhedsgrad. Der er rating category. Intet af det er eksotisk, hvis man spiller golf, og det er alt sammen usynligt for en udvikler, der aldrig har stået på et teested, hvilket for to uger siden var mig.</p>
<p>At få ordforrådet rigtigt tidligt koster næsten ingenting. At få det forkert koster dig tre services, tre navne for én ting og et oversættelseslag, ingen havde tænkt sig at bygge. Jeg kørte ét tjek, som jeg vil anbefale enhver, der gør det her: jeg fik Claude til at verificere &ldquo;loop&rdquo; på egen hånd, fordi det lugtede for mig af et ord, udviklere opfinder og derefter overbeviser sig selv om er domænesprog. Det kom tilbage som rigtig golf. At bruge modellen til at tjekke vores eget ordforråd, i stedet for til at skrive kode, er en af de bedre anvendelser, jeg har fundet til den indtil videre.</p>
<h2 id="niogtyve-punkter-og-hvor-det-her-er-på-vej-hen">Niogtyve punkter, og hvor det her er på vej hen<a class="gn-heading-anchor" href="#niogtyve-punkter-og-hvor-det-her-er-p%c3%a5-vej-hen" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Forbedringslisten ligger i ClickUp nu, mest så jeg kan holde op med at bære rundt på den i hovedet. Niogtyve punkter, alle stadig i backloggen, groft grupperet i repository-standarder og konventioner, sikkerhed og hemmeligheder, CI/CD og test og afhængigheder, infrastruktur og data, arkitekturretning, og AI og agenter.</p>
<p>Det første, vi rent faktisk har rullet ud, er det mindst spændende punkt på hele listen. <a href="https://mise.jdx.dev/">mise</a>, som fastlåser og installerer de rigtige sprogversioner per projekt, så man kloner et repo, og den korrekte Java dukker op af sig selv. Med så mange teknologier spredt over så mange repositories er den skat, man betaler hele dagen, at skifte mellem dem, og netop den skat kan undgås.</p>
<p>Målet har ikke ændret sig siden brevet. Byg et landskab, hvor kontekst er billig at samle op. Hvis en ny udvikler, eller en frisk AI-session, hurtigt kan forstå en service, kan arbejdet gå i gang med det samme, af hvem der end tager fat. Hvis de ikke kan, starter hver opgave med arkæologi. Alt på den liste med niogtyve punkter ligger nedstrøms af den ene sætning.</p>
<h2 id="scorekortet">Scorekortet<a class="gn-heading-anchor" href="#scorekortet" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Hvilket bringer mig tilbage til brevet og løftet om at rette mine egne opgaver offentligt.</p>
<p><strong>Bliv ved tasterne.</strong> Holdt. To terminalrettelser sendt ud, pull requests reviewet, og jeg har overtaget ejerskabet af en af apps&rsquo;ene. Jeg bygger stadig, hvilket var den betingelse, jeg satte, før jeg takkede ja til jobbet.</p>
<p><strong>Genopbyg forståelsen, ikke bare adgangen.</strong> For det meste. CloudWatch-eftermiddagen, database-snapshottet, Lightspeed-overdragelsen: det er den rigtige form for arbejde. Det, jeg endnu ikke kan påstå, er at kunne frigive hvad som helst i stakken uden at spørge nogen.</p>
<p><strong>Åbenhed i det åbne.</strong> Holdt, og lettere end jeg havde regnet med, mest fordi rummet her gør det let.</p>
<p>Nu den anden halvdel.</p>
<p><strong>Mål nummer et skred.</strong> &ldquo;Gør arbejdet synligt&rdquo; stod øverst på listen, og denne blog var måden. Hvert eneste indlæg før det her blev skrevet, før jeg havde skrevet under på kontrakten, hvilket viser sig at være det nemmeste tidspunkt overhovedet at skrive om en virksomhed. Så startede jeg rent faktisk, og i to uger var der ting foran mig, der betød mere end et blogindlæg. Jeg vil ikke kalde det en krise. Jeg skrev, at hvis der blev stille, skulle jeg tage det som et advarselstegn, så: noteret.</p>
<p><strong>Koldstarten er milevidt fra fem minutter.</strong> Jeg satte mig selv fem minutter fra klon til kørende kode og bad fremtidige mig om at tage tid på det. Jeg har ikke taget ordentligt tid på det, hvilket er sin egen lille fiasko. Jeg har klonet nok af de her repositories efterhånden til at give et fair skøn. Et sted mellem to og seks timer afhængigt af hvilket, man vælger, lad os sige fire i gennemsnit. At få iOS-buildet til at køre tog det meste af en dag med Xcode, pods og emulatorer. mise er det første rigtige skridt mod at lukke det hul, og et skridt er ikke et tal.</p>
<p><strong>Det fælles AI-kit er punkt niogtyve ud af niogtyve, og den del er med vilje.</strong> I brevet kaldte jeg det den ting, der gør &ldquo;AI-first&rdquo; fra et slogan til rigtigt fælles værktøj. To uger inde kan jeg se, hvorfor det ikke kan komme først. Et fælles kit bygget oven på otteogtres inkonsistente repositories ville mest af alt bare indkode inkonsistensen. Fundament først, kit bagefter, og den rækkefølge vil jeg forsvare. Det, jeg ikke vil lade som om, er, at rækkefølgen er gratis. Indtil vi når dertil, får jeg en masse gearing ud af et setup, jeg har brugt et år på at finpudse, og som ingen andre her har endnu.</p>
<p><strong>Og dagene åd pladsen til at tænke.</strong> Jeg skrev, at den kreative, legende tid ikke er en belønning, man optjener, når det seriøse arbejde er gjort, at den <em>er</em> det seriøse arbejde. To uger inde er det det første, kalenderen tager. Én ting hjælper her, og den er specifik for den her branche: golf er sæsonbetonet. Klubberne har travlt nu, så vi har travlt nu. Platformen bliver stille hen over vinteren, og det er der, værktøjskassen til 2027-sæsonen bliver bygget. At have en form på sit år er mere nyttigt, end jeg havde regnet med.</p>
<p>Det, jeg tog mest fejl af, er også det, jeg er gladest for. Jeg mødte op forberedt på modstand. Jeg regnede med at skulle gøre mig fortjent til adgange, argumentere for omfang og føre sag for hver eneste ændring. Næsten intet af det var der, og den modstand, jeg mødte i de første fjorten dage, havde jeg selv taget med.</p>
<p>To uger er ikke længe nok til at konkludere ret meget. Spørg mig igen til vinter, når klubberne bliver stille, og vi finder ud af, hvad vi rent faktisk gør med tiden.</p>
]]></content:encoded>
      <category>Teknik</category>
      <category>Produkt</category>
      <category>Kultur</category>
    </item>
    <item>
      <title>Få Claude Code til at tie stille (og andre tilpasninger, der har gjort sig fortjent)</title>
      <link>https://build.golfnext.com/da/posts/tuning-claude-code/</link>
      <pubDate>Wed, 24 Jun 2026 10:00:00 +0000</pubDate><dc:creator>Johann Böhler</dc:creator>
      <guid>https://build.golfnext.com/da/posts/tuning-claude-code/</guid>
      <description>Det første, jeg gjorde med Claude Code, var at få den til at tale mindre. De små, delbare tilpasninger, der overlevede mit eget filter — kortfattet output, en statuslinje, de rigtige hooks — plus dem, jeg bevidst nægter at automatisere, og hvorfor det at styre kontekstvinduet slet ikke burde være en færdighed.</description>
      <content:encoded><![CDATA[<p>Det første, jeg gjorde med <a href="https://code.claude.com/docs">Claude Code</a>, var at få den til at tale mindre.</p>
<p>Claude Code er Anthropics terminal-kodeassistent — du taler med den, og den læser, skriver og kører kode i dit projekt. Ud af boksen er den høflig. &ldquo;Certainly! I&rsquo;d be happy to help you with that. The issue you&rsquo;re seeing is likely caused by&hellip;&rdquo; Jeg læser hurtigt, jeg betaler per token, og jeg har ikke brug for opvarmningen. Det slog jeg fra. Jeg kører et community-plugin, <a href="https://github.com/JuliusBrussee/caveman">caveman</a>, der skærer svarene ind til benet: ingen artikler, ingen høfligheder, hver eneste tekniske detalje intakt. &ldquo;Bug in auth middleware. Token expiry check uses <code>&lt;</code> not <code>&lt;=</code>. Fix:&rdquo; — og så rettelsen.</p>
<p>Det kræver tilvænning. Nogle af de komprimerede sætninger får dig til at stoppe op og læse dem to gange; et fragment kan bære mere mening, end det først lader ane, og du bruger det sparede sekund på at afkode det. Men byttet falder klart ud til min fordel. Jeg kommer hurtigere til sagen, kontekstvinduet fyldes langsommere, og hvis en linje er decideret uklar, er der altid nødudgangen: bed den om at udfolde den. Den brokker sig aldrig.</p>
<p>Den tilstand er det mindst mulige eksempel på det, hele dette indlæg handler om. Claude Code ud af boksen er generisk. Den er bygget til at være fornuftig for alle, hvilket betyder, at den er optimal for ingen. Gevinsten ligger ikke i modellen selv — du kan ikke ændre, hvor god den er, kun hvilken én du kører. Gevinsten ligger i at forme værktøjet om din egen arbejdsgang.</p>
<h2 id="fælden">Fælden<a class="gn-heading-anchor" href="#f%c3%a6lden" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Tilpasning er ikke gratis, og jeg vil være ærlig om regningen. Du kan hælde uger i et setup, ingen anden kan læse — heller ikke dit fremtidige jeg.</p>
<p>De værktøjer, der drev konfigurerbarhed længst, er dem, alle hylder: vim og git. Dybe, kraftfulde, uendeligt indstillelige. Og, vil jeg forsigtigt påstå, ikke just særligt tilgængelige. Dybden er både pointen og prisen. Vim er det eneste værktøj, jeg kender, med et meme om, hvordan man overhovedet lukker det — det burde sige os noget.</p>
<p>Jeg hører indvendingen oftest om git: &ldquo;men git er da så <em>nemt</em>.&rdquo; Er det nu det? Det er nemt — lige indtil din første rigtige merge-konflikt, eller eftermiddagen, hvor du rebaser det forkerte og stille skriver din egen historik om uden nogen ren vej tilbage. Ja, du kan godt se, hvor du prøver at komme hen. Det kan ham, der sidder med æsken til et puslespil på halvtreds tusind brikker, også — med det færdige billede trykt lige der på låget. At se målet er ikke det samme som, at vejen er tilgængelig. Det er to forskellige påstande, og vi bliver ved med at blande dem sammen.</p>
<p>Her er overbevisningen, sagt rent ud i stedet for som en klage: et godt værktøj bør være så tilgængeligt, som det overhovedet kan være — og <em>derefter</em>, når det har gjort sig fortjent, tilbyde uendelig dybde til enhver, der vil grave. Den tilgængelige del er hele opgaven, ikke en trøstepræmie til begyndere. Dybden er bonussen, du lægger ovenpå.</p>
<p>Min målestok for enhver Claude Code-tilpasning er ét spørgsmål: <em>ville jeg kunne række det til en kollega uden en times briefing?</em> Hvis svaret er nej, overlever det sjældent. Min stak holdes bevidst simpel og dum. Her er, hvad der er tilbage efter det filter.</p>
<h2 id="hvad-fortjente-en-plads">Hvad fortjente en plads<a class="gn-heading-anchor" href="#hvad-fortjente-en-plads" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p><strong>Den kortfattede output-tilstand.</strong> Dækket ovenfor. Den enkeltvis mest virkningsfulde ændring, fordi den rører hvert eneste svar. Mindre at læse, færre tokens brændt, længere levetid for konteksten.</p>
<p><strong>Statuslinjen.</strong> Den betyder mere, end den lyder til. Jeg har som regel flere ting i gang på én gang og mister overblikket over, hvor jeg er — hvilket repo, hvilken opgave, hvor meget budget jeg har tilbage. Min statuslinje viser det hele i én stribe: modellen, jeg kører på, hvor fyldt kontekstvinduet er, hvor jeg står i forhold til min 5-timers forbrugsgrænse (Claude Code sætter et loft over, hvor meget du kan køre inden for et rullende fem-timers vindue), og min nuværende mappe. Ingen klik, ingen gætteri. Jeg kigger ned og kender min situation. For enhver, der jonglerer mere end én ting ad gangen, er det den billigste sindsro, jeg har købt.</p>
<p><strong>Indstillingslaget.</strong> Det er her, det bliver personligt, og hvor mulighederne aldrig hører op. Min konfiguration spiller en lyd, når Claude er færdig, så jeg kan kigge væk og blive kaldt tilbage. Den sender OS-notifikationer. Du kan få den til at logge formålet med hver session, holde styr på tokens og pris, hvad end du beslutter er værd at vide. Der findes ingen &ldquo;rigtig&rdquo; opsætning — pointen er, at hooks&rsquo;ene findes (små kommandoer, værktøjet kører automatisk på bestemte tidspunkter, for eksempel når det er færdigt), og at du former dem til <em>dit</em> formål. Mit er &ldquo;sig til, når du er færdig.&rdquo; Dit kunne være noget helt andet.</p>
<p>Ingen af dem er smarte. Det er netop pointen. Hver enkelt fjerner en lille, daglig friktion, og ingen af dem kræver en forklaring.</p>
<h3 id="selve-ledningsføringen">Selve ledningsføringen<a class="gn-heading-anchor" href="#selve-ledningsf%c3%b8ringen" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h3>
<p>Lyden er et <code>Stop</code>-hook i <code>settings.json</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;hooks&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Stop&#34;</span><span class="p">:</span> <span class="p">[{</span> <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">[{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;afplay ~/.claude/sounds/done.mp3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;async&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">}]}]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>async: true</code>, så den aldrig blokerer sessionen, og <code>afplay</code> er bare den indbyggede macOS-afspiller — skift den ud med hvad du vil.</p>
<p>Statuslinjen er knap nok mere end det. Claude rækker dit script en klump JSON ved hver gengivelse; scriptet printer en linje. Du peger på det én gang:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;statusLine&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;bash ~/.claude/statusline.sh&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>og scriptet læser de felter, det er interesseret i, fra stdin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">input</span><span class="o">=</span><span class="k">$(</span>cat<span class="k">)</span>                                                  <span class="c1"># session state, as JSON</span>
</span></span><span class="line"><span class="cl"><span class="nv">model</span><span class="o">=</span><span class="k">$(</span>jq -r <span class="s1">&#39;.model.display_name&#39;</span>                          <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$input</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">ctx</span><span class="o">=</span><span class="k">$(</span>  jq -r <span class="s1">&#39;.context_window.used_percentage // 0 | floor&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$input</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">fh</span><span class="o">=</span><span class="k">$(</span>   jq -r <span class="s1">&#39;.rate_limits.five_hour.used_percentage // 0 | floor&#39;</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$input</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">dir</span><span class="o">=</span><span class="k">$(</span>  jq -r <span class="s1">&#39;.workspace.current_dir&#39;</span>                       <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$input</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span></code></pre></div><p>Derfra er det bare at tegne strenge. Min tegner en lille farvet bjælke for hver procentdel — grøn, så gul, så rød, efterhånden som den fyldes — og højrejusterer stien, så striben ender med at se sådan ud:</p>
<pre tabindex="0"><code>Opus 4.8  xhigh  · CTX ████░░░░░░ 38%  · 5h ██░░░░░░░░ 21%        ~/dev/golfnext
</code></pre><p>(<code>xhigh</code> der er bare det niveau af ræsonneringsindsats, jeg holder modellen på.) Det er hele molevitten. Ingen framework, ingen afhængighed at holde opdateret. Et shell-script og en klump JSON.</p>
<h2 id="hvad-jeg-ikke-automatiserer">Hvad jeg ikke automatiserer<a class="gn-heading-anchor" href="#hvad-jeg-ikke-automatiserer" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Mere automatisering er ikke altid en gevinst. Noget af det flytter bare omkostningen hen et sted, du ikke kan se, og dem lader jeg være slået fra.</p>
<p>Du kan få Claude til at linte og formatere hver fil i samme øjeblik, den redigerer den. Lyder ryddeligt. Det er det modsatte. Hvis et hook omformaterer en fil lige efter, modellen har rørt den, er modellens billede af den fil nu forældet — så næste gang den arbejder der, må den læse hele filen igen for at følge med. Du har byttet et rent diff for en langsommere, mere token-sulten session. Jeg vil hellere have, at den bare kører linteren én gang, til allersidst, når tænkningen er færdig.</p>
<p>Samme logik gælder automatisk model-fallback. Du kan få den til stille at skifte til en mindre model, når dit første valg er optaget. Det vil jeg ikke have. Jeg vælger en model bevidst, som regel fordi opgaven kræver den større ræsonneringsmodel — den, der standser op og arbejder et problem igennem, før den svarer. Hvis den er overbelastet, vil jeg hellere komme tilbage om to timer og skrive &ldquo;tag den her op igen&rdquo; end at lade noget stille række mit svære problem videre til en model, der ikke kan bære det. Bekvemmelighed er ikke et dårligere svar værd, som jeg ikke opdagede, jeg fik.</p>
<h2 id="vinduet-bør-være-en-ikke-begivenhed">Vinduet bør være en ikke-begivenhed<a class="gn-heading-anchor" href="#vinduet-b%c3%b8r-v%c3%a6re-en-ikke-begivenhed" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Folk taler meget om at styre kontekstvinduet — komprimere det (få værktøjet til at opsummere samtalen indtil videre for at frigøre plads), holde øje med det, pleje det. Jeg prøver aldrig at være i den samtale.</p>
<p>Måden, jeg undgår det på, er strukturel: skub det tunge arbejde over på subagenter — separate hjælpe-instanser af Claude Code, hver med sin egen private kontekst — og hold hovedtråden ren. En subagent tager af sted, brænder sin egen kontekst af på at læse og søge, og rækker det korte svar tilbage. Hovedsessionen forbliver let. Gjort rigtigt rækker jeg næsten aldrig ud efter komprimering, fordi jeg aldrig lader vinduet blive fyldt i første omgang.</p>
<p>Men her er den ærlige del: jeg synes, det er en lugt i sig selv, at nogen af os overhovedet skal vide det her. Ingen burde <em>behøve</em> at forstå kontekstvinduer for at begynde at være produktiv med værktøjet. Det er en nuværende begrænsning forklædt som en færdighed. Om et par år vil jeg vædde på, at det er abstraheret helt væk, og at vi taler om det, som vi taler om at styre vores egen RAM — hvilket vil sige, at det gør vi ikke. Jeg optimerer omkring det i dag, og jeg prøver ikke at forveksle det at arbejde uden om en begrænsning med noget, der er værd at lære fra sig.</p>
<p>Kan jeg bevise det? Ikke med tal. Jeg har ikke et rent før-og-efter, og jeg er skeptisk over for enhver, der citerer en pæn procentdel for det her. Hvad jeg har, er en fornemmelse: vinduet fyldes langsommere, budgettet rækker længere, sessionerne forbliver lette længere. Det er subjektivt, og jeg ville mærke det i samme øjeblik, det var væk.</p>
<h2 id="sådan-finder-du-dine-egne">Sådan finder du dine egne<a class="gn-heading-anchor" href="#s%c3%a5dan-finder-du-dine-egne" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Den svære del er ikke at konfigurere noget. Det er at vide, hvad der overhovedet er muligt.</p>
<p>Claude Code bevæger sig hurtigt — det føles som ti nye kommandoer om ugen. Så hurtigt, at modellen selv ikke kan følge med. Jeg spørger den om en rigtig slash-kommando — &ldquo;hvad gør <code>/goal</code>, kan du forklare det?&rdquo; — og den aner ikke, at kommandoen findes. Der er noget komisk over det: det mest kapable, jeg arbejder med, kan ofte ikke fortælle dig om funktioner, der er kommet til efter dens træningsgrænse. Den er bagud på sig selv, helt bogstaveligt. Du kan ikke bare spørge modellen, hvad den kan. Du er nødt til at gå og kigge — læs changelogen, skim en guide, peg den ud på det åbne web, hvor det friske faktisk lever.</p>
<p>Den næste ting, jeg vil prøve, er slet ikke en indstilling. Jeg har et halvt dusin projekter i gang, så jeg vil bygge ét lille meta-repo: ét sted, der peger på dem alle, og som enhver — ikke kun jeg — kan navigere i. Samme instinkt som alt andet her: hvis det kun virker for mig, virker det ikke rigtigt.</p>
<p>Det er hele filteret, når det kommer til stykket. Tag de to-tre ting, der dræber en rigtig, gentagen friktion. Lad resten ligge, uanset hvor smart det ser ud. Målet var aldrig det mest avancerede setup. Det var det, du stadig ville være glad for at række videre til en anden.</p>
]]></content:encoded>
      <category>AI</category>
      <category>Teknik</category>
    </item>
    <item>
      <title>Loop-debatten er et dårligt ord for autonomi</title>
      <link>https://build.golfnext.com/da/posts/loops-autonomy/</link>
      <pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Johann Böhler</dc:creator>
      <guid>https://build.golfnext.com/da/posts/loops-autonomy/</guid>
      <description>Alle diskuterer &#34;Claude Code loops.&#34; Efter at have målt en måneds rigtige opgaver — hver token, hver time det ville have kostet i hånden — er jeg ret sikker på, at det meste af diskussionen handler om det forkerte ord. Det er ikke loops, det er autonomi. Og ligesom microservices for ti år siden ankommer det én verificerbar opgave ad gangen, begrænset af et fundament, ingen har bygget endnu.</description>
      <content:encoded><![CDATA[<p>Der er en genre af indlæg, der florerer lige nu. Du har sikkert set dem. &ldquo;Jeg skriver ikke prompts længere — jeg designer loops.&rdquo; &ldquo;Loop-engineering er det nye prompt-engineering.&rdquo; Jeg læser dem grundigt — jeg prøver at lære det her lige så hurtigt som alle andre, og dem, der skriver dem, er som regel langt foran mig. Men jeg sidder hver gang tilbage med den samme kløe: jeg kan ikke helt få ordet til at passe på det, jeg faktisk laver hele dagen.</p>
<p>Jeg bruger Claude Code de fleste dage. For dem, der ikke har mødt det: Claude Code er Anthropics kodeagent — du giver den en opgave i terminalen, og den læser filer, skriver kode, kører tests og itererer, i stedet for bare at række dig et stykke kode, du selv skal indsætte. Evnen er reel, og jeg læner mig hårdt op ad den. Det, der spænder ben for mig, er ordforrådet, så jeg prøvede at jorde det.</p>
<p>Jeg gjorde det kedelige: jeg målte alt. Hver rigtig opgave, jeg kørte gennem Claude Code, fik to tal ved siden af sig — hvad den kostede i tokens, og et groft estimat af, hvad det samme arbejde ville have kostet i hånden, i tid. En måneds rækker. Det er det mest uglamourøse artefakt, man kan forestille sig, og det er det, der til sidst fik &ldquo;loop&rdquo;-debatten til at falde på plads for mig.</p>
<p>Det, der faldt på plads — i hvert fald for mig — er, at en stor del af diskussionen handler om det forkerte ord. &ldquo;Loop&rdquo; er ikke sagen. Autonomi er. Og autonomi kommer ikke som en kontakt, du vipper; det kommer én opgave ad gangen — hvilket, hvis du var med under sidste store arkitektur-diskussion, burde føles besynderligt bekendt.</p>
<h2 id="loop-er-fire-forskellige-ting">&ldquo;Loop&rdquo; er fire forskellige ting<a class="gn-heading-anchor" href="#loop-er-fire-forskellige-ting" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>En del af grunden til, at debatten føles grumset, er, at &ldquo;loop&rdquo; udfylder mindst fire roller på én gang, og folk taler forbi hinanden, fordi de hver især holder en forskellig en.</p>
<p>Der er <strong>agent-loopet</strong> — tænk, handl, observér, gentag. Det er bare sådan en agent virker. Den læser situationen, foretager en handling, ser på resultatet, beslutter hvad der er næste skridt. Det har altid eksisteret. Det er ikke en teknik, det er definitionen.</p>
<p>Der er <strong>Ralph</strong> — bash-hacket. Det er fællesskabets navn for det groveste tænkelige loop: pak agenten ind i et bogstaveligt <code>while</code>-loop i din shell, og lad den køre den samme prompt igen og igen, indtil den selv erklærer sig færdig. (Navnet er bare folklore; teknikken er pointen.) Groft, til tider effektivt — autonomiens gaffatape.</p>
<p>Der er <strong>funktionerne</strong> — slash-kommandoer som <code>/loop</code> og <code>/goal</code>, indbygget i værktøjer som Claude Code. De pakker den samme idé ind i noget mere kontrolleret end en bash-onelinjer.</p>
<p>Og der er <strong>&ldquo;loop-engineering&rdquo;</strong> — filosofien. Diskursen. Indlæggene.</p>
<p>Fire forskellige højder, ét ord. Ikke så sært, at det forvirrer.</p>
<p>Men her er det, de alle deler, sætningen hver af dem gentager på sin egen dialekt: <strong>mål, verifikation, stopbetingelse.</strong> Den trio er den egentlige substans. Alt andet er bare et spørgsmål om, hvilket lag den enkelte tilfældigvis stirrer på.</p>
<p>Lad os gøre det konkret. Tag den mest kedelige nyttige ting, jeg automatiserer: at adressere review-kommentarer på en pull request.</p>
<ul>
<li><strong>Mål:</strong> løs alle review-kommentarer på denne PR.</li>
<li><strong>Verifikation:</strong> CI er grøn, og hver kommentar-tråd er reelt adresseret — ikke &ldquo;jeg tror, jeg fiksede det,&rdquo; men checket består, og tråden er lukket.</li>
<li><strong>Stopbetingelse:</strong> ingen åbne kommentarer tilbage, eller den rammer et iterationsloft og giver bolden tilbage til mig.</li>
</ul>
<p>Det er et loop. Det er også bare en opgave med en klar definition af færdig og en regel for, hvornår man stopper. Trioen er hele spillet.</p>
<p>Og læg mærke til, hvad der får eksemplet til at virke: en maskine, ikke mig, kan afgøre, om målet er nået — checket er grønt, eller det er ikke. Det er det, jeg fremover vil kalde <em>verifikation</em> — et automatisk, dømmefrit signal om, at arbejdet faktisk er gjort. Hold fast i ordet. Hvor meget af det en opgave har, er det, der afgør alt det senere.</p>
<h2 id="det-er-ikke-loops-det-er-autonomi">Det er ikke loops. Det er autonomi.<a class="gn-heading-anchor" href="#det-er-ikke-loops-det-er-autonomi" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Her er den del, det tog mig et stykke tid at se. Læs de tre kriterier igen — mål, verifikation, stopbetingelse — og spørg: ændrer noget af det sig, alt efter om <em>jeg</em> trykker enter mellem hvert skridt, eller en harness (scriptet eller værktøjet, der omslutter agenten) gør det for mig? Nej. Intet. Målet er målet, verifikationen er verifikationen, stopbetingelsen er stopbetingelsen; de er identiske, uanset om et menneske rykker hver iteration frem, eller et script gør det. Den eneste variabel er <strong>hvem der drejer håndsvinget mellem skridtene</strong> — og det er slet ikke et spørgsmål om loops. Det er et spørgsmål om autonomi: hvor meget du stoler på, at tingen kører videre uden dig i stolen.</p>
<p>Hvilket giver mening, når man ser, hvor lidt der egentlig er ved et loop — nogenlunde sådan her:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">while</span> <span class="ow">not</span> <span class="n">done</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">observe</span><span class="p">()</span>              <span class="c1"># look at where things stand</span>
</span></span><span class="line"><span class="cl">    <span class="n">act_toward</span><span class="p">(</span><span class="n">goal</span><span class="p">)</span>       <span class="c1"># take the next step</span>
</span></span><span class="line"><span class="cl">    <span class="n">done</span> <span class="o">=</span> <span class="n">verified</span><span class="p">()</span>      <span class="c1"># the stop condition: is the goal actually met?</span>
</span></span></code></pre></div><p>Det er det hele. Så &ldquo;bør jeg designe loops&rdquo; er lidt af en kategorifejl — loopet er trivielt, et <code>while</code> og et <code>if</code>. De svære dele gemmer sig bag ordet, og der er to af dem. Den første: hvor langt er du villig til at træde væk fra en kørende proces? Det er tillid. Den anden, den jeg næsten aldrig ser nævnt: når du <em>først</em> træder væk, hvor kører tingen så rent faktisk, og har du råd til at køre den der? Det er infrastruktur — og i modsætning til tillid er det ikke rigtig op til dig endnu, fordi det billige, standardiserede sted at køre autonome agenter ikke findes.</p>
<h2 id="hvad-arbejdet-faktisk-koster">Hvad arbejdet faktisk koster<a class="gn-heading-anchor" href="#hvad-arbejdet-faktisk-koster" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Det andet spørgsmål — har du råd til at køre det — er det, ingen lader til at blogge om, sandsynligvis fordi næsten ingen tænker på at måle det. Så det gjorde jeg. Min hverdag med Claude Code er overvåget — jeg kører det på min egen maskine, med mig selv i stolen. Men det tal, jeg var ude efter, var det andet: hvad det samme arbejde ville koste <em>målt pr. forbrug</em>, betaling pr. token, sådan som du ville være nødt til at betale for at køre det i skyen uden mig som barnepige. Det er den dyre bane, og det er den, der afgør, om uovervåget overhovedet er på bordet. Så i en måned loggede jeg den forbrugsmålte pris for hver rigtig opgave, ved siden af et ærligt estimat af, hvor lang tid det ville have taget mig i hånden.</p>
<p>Tohundrede og tyve opgaver. <strong>Omkring $1.360 i forbrugsmålt pris — og, efter mit eget estimat, tæt på 540 timers tilsvarende håndarbejde.</strong> Femhundrede og fyrre timer er den bedre del af tre måneders fuldtids-ingeniørarbejde. Det røg ud ad døren på én.</p>
<p>Sæt et hvilket som helst rimeligt tal på en ingeniørtime, og de forbrugsmålte $1.360 står over for arbejde til <strong>tyve til fyrre gange</strong> så meget — omkring tredive til en normal fuldt belastet timepris, hvor man tæller løn plus overhead og ikke bare det, der ryger på kontoen. Multiplikatoren behøver ikke være præcis: halvér mine timeestimater, hvis du synes, de er rundhåndede, og det er stadig en størrelsesorden.</p>
<p>Formen på forbruget betyder lige så meget som størrelsen. Median-opgaven kostede <strong>$2,45</strong>. To tredjedele af alt lå under fem dollars — bugfixes, små features, skrøbelig CI-konfig, dependency-bumps. De dyre kørsler er sjældne, og det er de tunge af slagsen: en hel feature, bygget fra ende til anden, koster nogle tiere af dollars, og de største enkeltrækker er features i <strong>$40–55</strong>-lejet. Læg flere runder review oven på en af dem, og en komplet ændrings-livscyklus kan runde $100. Det er undtagelserne, ikke normen.</p>
<p>Den del, der afgør, hvor man skal starte: jeg markerede de rækker, der er rent review-og-fix-arbejde — at adressere PR-kommentarer, fikse et fejlende build, løse merge-konflikter. <strong>Hele den kategori lå under en tiendedel af det samlede forbrug</strong> — de fleste opgaver fra et par cents til et par dollars, halvdelen under en dollar. Og den krævede sjældent meget af mig. Kommentarerne er afgrænsede, fiksen er som regel lokal, og verifikationen er allerede sat op: buildet er grønt, eller det er ikke. Det er den mest checkbare, mindst dømmekrævende del af cyklussen — hvilket gør den til det oplagte første sted at lade noget køre på egen hånd.</p>
<p>Den reelle pengelækage er ikke prisen på den enkelte opgave. Det er churn — kørsler, der brændte krudt af på noget, der ikke havde brug for det. En ændring, der burde have kostet tyve dollars, kostede hundrede, fordi jeg lod den male løs på et problem, jeg bare burde have styret den igennem. Det spild, ikke prisen pr. opgave, er det, der er værd at lave ingeniørarbejde imod.</p>
<h2 id="det-skræmmende-tal-er-mest-af-alt-en-måle-artefakt">Det skræmmende tal er mest af alt en måle-artefakt<a class="gn-heading-anchor" href="#det-skr%c3%a6mmende-tal-er-mest-af-alt-en-m%c3%a5le-artefakt" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Der findes en version af det her, hvor nogen ser den månedlige total og får et chok. &ldquo;Brugte du <em>hvor meget</em> på en AI?&rdquo; Og jeg forstår det godt — $1.360 om måneden er et oprigtigt alarmerende tal.</p>
<p>Men det er mest af alt en artefakt af, hvordan det opgøres, ikke af, hvad arbejdet er værd — og det er ikke engang det, jeg betaler. De $1.360 er det forbrugsmålte tal, jeg målte. Min faktiske regning er det faste abonnement, jeg kører det hele på: en &ldquo;Max&rdquo;-plan i mellemlaget, brugt i et normalt tempo — ikke et hamr-alt-igennem-modellen, lad-mig-lige-google-det-med-Claude-også-tempo, bare støt rigtigt arbejde — som løber op i omkring €110 om måneden, lad os sige $120. Samme tastetryk, samme output, samme måneds arbejde; det forbrugsmålte tal er cirka ti gange abonnementet, udelukkende på grund af hvordan det afregnes.</p>
<p>Og her er den del, der stille og roligt afvæbner hele pris-panikken: du arbejder <em>overvåget</em> alligevel. Du sidder der. Du styrer. Det dyre, ubegrænsede &ldquo;åh gud, hvad laver den&rdquo;-scenarie er det <strong>uovervågede</strong> — agenten, der maler løs på egen hånd, mens måleren spinner. Hvilket er præcis det, du er mindst klar til at stole på i første omgang.</p>
<p>Så de to store bekymringer — &ldquo;det er for dyrt&rdquo; og &ldquo;jeg kan ikke lade den køre selv endnu&rdquo; — viser sig at være den samme grænse set fra to sider.</p>
<h2 id="så-hvad-bygger-man-egentlig">Så hvad bygger man egentlig?<a class="gn-heading-anchor" href="#s%c3%a5-hvad-bygger-man-egentlig" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Ikke et autonomt loop — ikke endnu, og ikke til det meste af det. Det, jeg bygger i stedet, er en <em>menneske-udløst kæde af skills</em>, hvor &ldquo;skill&rdquo; bare betyder en navngiven, genbrugelig opgave, du kan række agenten, som en gemt kommando. En af mine passer pull requests: når jeg har åbnet en PR, sætter jeg den i gang, og den holder øje med de ting, der kræver en reaktion — en ny runde review-kommentarer, et fejlende build — og arbejder dem, indtil PR&rsquo;en er grøn, eller den rammer noget, den ikke kan håndtere, og prikker mig på skulderen. Jeg starter den; den starter ikke sig selv.</p>
<p>Det interessante er, hvad der <em>ikke</em> kører modellen. Overvågningen er et almindeligt shell-loop, der poller GitHub hvert par minutter for en håndfuld billige signaler — åbne, uafklarede kommentar-tråde, CI-status, om PR&rsquo;en er blevet godkendt — og den vækker kun agenten, når det sæt rent faktisk ændrer sig:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># the watcher is dumb and free — no model in this loop</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> pr_is_open<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nv">signals</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>unresolved_threads<span class="k">)</span><span class="s2">::</span><span class="k">$(</span>failing_ci<span class="k">)</span><span class="s2">&#34;</span>   <span class="c1"># cheap GitHub calls</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$signals</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$signals</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="nv">$last</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        wake_agent <span class="s2">&#34;</span><span class="nv">$signals</span><span class="s2">&#34;</span>     <span class="c1"># the only line that spends tokens</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="nv">last</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$signals</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    sleep <span class="m">600</span>                     <span class="c1"># 10 min; an idle PR costs nothing</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span></code></pre></div><p>Agenten spinder kun op, når sættet ændrer sig, og selv da gør den det afgrænsede — læser de nye tråde, fikser de værdige i arbejdstræet, kender forskel på et &ldquo;min kode er i stykker&rdquo;-build og et &ldquo;infrastrukturen væltede&rdquo;-build — og lægger sig så til at sove igen. Den dyre del, modellen, sidder bag den billige del, et script der ved, hvordan man læser en status. Den gating er det meste af tricket til at holde de her ting overkommelige.</p>
<p>Min er den pragmatiske udgave, dog, ikke evangeliet. Peg den mod en andens repo, og den har brug for rigtige guardrails. En PR-kommentar er upålideligt input, hvilket gør en åben watcher til en prompt-injection-flade: nogen kan efterlade en kommentar, der styrer agenten mod noget, der intet har med PR&rsquo;en at gøre, eller bare spamme kommentarer for at blive ved med at vække den og stille og roligt drive regningen op. Forbi legetøjsstadiet vil du have en classifier, der afgør, hvad der overhovedet er værd at reagere på, og en eskaleringsvej — en OS-notifikation, et Slack-ping, en ticket — til de ting, den ikke selv bør røre. Men man kommer overraskende langt på de rå signaler alene; &ldquo;åbne, uafklarede kommentarer plus CI-status&rdquo; er allerede nok til at være reelt nyttigt.</p>
<p>Og formen kan generaliseres. En anden, jeg bliver ved med at ville gøre færdig: et ugentligt job, der scanner de åbne Dependabot- og Renovate-pull requests, merger og rydder op i de kedelige, hvor intet reelt skal ændres, og eskalerer resten — de bumps, der kræver en egentlig refaktorering, eller hvor et API kan brække under dig. Samme skelet hver gang: en billig trigger, et afgrænset job, og en klar linje, hvor det giver bolden tilbage til et menneske. At bygge de loops, og de små toolkits omkring dem, er stille og roligt ved at blive sin egen slags software engineering — mindre &ldquo;prompt modellen,&rdquo; mere &ldquo;design den harness, modellen kører inde i.&rdquo;</p>
<p>To regler, jeg ville hugge i sten, før jeg byggede noget af det. For det første: <strong>release er ikke et job for modellen — det er din CI/CD.</strong> Lær ikke en agent at gøre det, din pipeline allerede gør gratis, deterministisk, med et revisionsspor; det er ikke autonomi; det er at genopfinde et løst problem. For det andet: <strong>monitorering er ikke et job for modellen — det er Sentry og Grafana.</strong> Betal ikke en hjerne for at glo på et dashboard; kobl i stedet alarmen tilbage til en trigger, så agenten kun får problemet i hænderne, når noget rent faktisk er gået i stykker. Og uanset hvad du bygger, så sæt et loft på fiks-loopet — en hård iterationsgrænse plus en menneskelig &ldquo;godt nok, send det&rdquo;-port — ellers bliver den $20-fiks stille og roligt til en $100-fiks, mens du er til frokost.</p>
<p>Intet af det er forsigtighed. Menneske-i-loopet er ganske enkelt måden, man <em>starter</em> på: du løsner snoren én iteration ad gangen, efterhånden som dine egne evner skærpes og — vigtigere — efterhånden som din verifikation bliver god nok til, at du ville stole på resultatet uden at stå og kigge over skulderen.</p>
<h2 id="vi-har-haft-præcis-den-her-diskussion-før">Vi har haft præcis den her diskussion før<a class="gn-heading-anchor" href="#vi-har-haft-pr%c3%a6cis-den-her-diskussion-f%c3%b8r" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Hvis alt det her føles bekendt, er det, fordi vi spillede hele stykket igennem for ti år siden. Det hed microservices, og jeg var med. Hurtig genopfriskning, hvis du gik glip af den æra: omkring 2014 begyndte teams at hakke store, enkeltstående applikationer — &ldquo;monolitter&rdquo; — op i mange små, uafhængigt deploybare services. Den højlydte diskussion gik på, hvor <em>små</em> de skulle være.</p>
<p>Frådet er identisk. Dengang var det ubesvarlige spørgsmål &ldquo;hvor lille er en microservice — kan den være 200 linjer?&rdquo; I dag er det &ldquo;Ralph eller <code>/goal</code>, og hvor mange agenter?&rdquo; I begge tilfælde er det et fællesskab, der størrelses-diskuterer sig igennem en ting, før nogen overhovedet ved, hvad der faktisk betyder noget.</p>
<p>Den gode praksis kom også før navnet. Vi opdelte systemer efter domæne — rene snit, ejede grænser — længe før nogen sagde &ldquo;microservices.&rdquo; Og agent-loopet plus en verifikations-harness fandtes længe, før nogen brandede det som &ldquo;loop-engineering.&rdquo; Navnet dukkede op og tog æren for arbejde, folk allerede lavede.</p>
<p>Og så lagde det sig. Det ærlige tilbageblik på microservices er ikke &ldquo;vi revolutionerede alt.&rdquo; Det er &ldquo;vi byggede stort set videre, som vi allerede byggede, bare med renere snit.&rdquo; De fleste teams gik fra en monolit til to eller fem services, ikke fra én til to hundrede natten over. Forandringen var reel, og den var udramatisk, fordi det meste af det, vi byggede, allerede var småt.</p>
<p>Men her er korrektionen, der faktisk betyder noget, den del størrelses-diskussionen overså dengang. For microservices var den reelle forskel aldrig mønsteret. Det var <strong>fundamentet.</strong> Containere, så skyen, så Kubernetes gjorde små, uafhængigt deploybare services billige og standardiserede at køre. Mønsteret lå nedstrøms for infrastrukturen — og infrastrukturen dukkede op for at møde det.</p>
<p>For loops er vi kun halvvejs. Kapacitets-fundamentet er ankommet, og det er <strong>modellen.</strong> En enkelt række på mit ark er en feature, jeg ville have sat to dages håndarbejde af til, leveret for omkring halvtreds dollars. Det var fuldstændig umuligt for to år siden, til enhver pris, med ethvert loop. Loopet ændrede sig ikke; det, der sad inde i det, blev godt nok til, at man kan betro det rigtigt arbejde. Det er container-øjeblikket — den rå kapacitet, der lander.</p>
<p>Det, der ikke er ankommet, er resten af stakken. Der findes ikke et billigt, standardiseret, sikkert sted til rent faktisk at køre autonomi uovervåget — ingen pendant til den managed sky og Kubernetes, der forvandlede &ldquo;jeg har en lille service&rdquo; til &ldquo;og her er det oplagte, overkommelige sted at køre den.&rdquo; Intet siger endnu &ldquo;lad denne agent male løs på egen hånd natten over, uden at det koster en formue eller løber af sporet.&rdquo;</p>
<p>Så: modellen er containerne; Kubernetes er ikke bygget endnu. Og &ldquo;loops&rdquo; — filosofien, størrelseskrigene — er <em>nanoservices</em>-frådet om igen: &ldquo;hvor absurd småt kan vi gøre det?&quot;-overdrivelsen, der udspiller sig på en stak, der stadig mangler sin nederste halvdel.</p>
<h2 id="autonomi-er-gradvist-ikke-en-kontakt">Autonomi er gradvist, ikke en kontakt<a class="gn-heading-anchor" href="#autonomi-er-gradvist-ikke-en-kontakt" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Hvilket bringer mig tilbage til, hvor jeg startede. Du vipper ikke fra &ldquo;jeg holder øje med alt&rdquo; til &ldquo;den kører selv.&rdquo; Ingen gik fra én service til to hundrede på en weekend, og ingen ved deres fulde fem går fra overvåget-alt til autonom-alt heller.</p>
<p>Snoren løsnes én verificerbar opgaveklasse ad gangen, begrænset af præcis én ting: hvor checkbar verifikationen er. Det sker allerede. En PR-watcher, der auto-fikser fejlede builds, er snoren allerede løsnet — på en snæver, afgrænset, maskin-checkbar klasse, hvor prisen for at tage fejl er lav, og et grønt check fortæller sandheden. Imens forbliver feature-design og produktionsændringer i snor længe, måske for altid, fordi verifikationen er dømmekraft, og dømmekraft passer ikke ind i et CI-check.</p>
<p>Så når du læser &ldquo;jeg designer loops, ikke prompts,&rdquo; så forstå, hvor det kommer fra. Det er ikke en løgn. Men læg mærke til, hvem der plejer at sige det: folk i de store AI-labs — dem, der bygger modellerne — eller med reelt ubegrænsede token-budgetter, eller begge dele. For dem mangler det manglende fundament ikke; måleren gælder ikke på samme måde, som den gælder for dig. &ldquo;Bare kør loops&rdquo; er en fuldstændig ærlig beskrivelse af <em>deres</em> virkelighed. Det er rapportering fra forkanten: sandt der, hvor det rapporteres fra, med det bedste værktøj og de mest checkbare problemer, solgt som en universel recept, det ikke er endnu — ikke for nogen, der arbejder mod et reelt budget.</p>
<p>Så jeg melder mig ikke ud af debatten — jeg melder mig ud af ordet. Jeg bygger den overvågede kæde, skærper mine egne evner og min verifikation, og lader hver opgaveklasse fortjene sit slæk ét check ad gangen. Det er den del, jeg kan handle på i dag, mens resten bliver diskuteret færdigt.</p>
<p>Hvilket er det samme træk, der viste sig at være rigtigt med microservices: spot frådet, lad størrelseskrigene rase, og bliv ved med at sende den kedelige udgave, der rent faktisk virker.</p>
]]></content:encoded>
      <category>AI</category>
    </item>
    <item>
      <title>En ren maskine</title>
      <link>https://build.golfnext.com/da/posts/clean-machine/</link>
      <pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Johann Böhler</dc:creator>
      <guid>https://build.golfnext.com/da/posts/clean-machine/</guid>
      <description>To år efter at en kollegas dotfiles dukkede op i mit GitHub-feed tvang en ny bærbar mig endelig til det. Hvordan jeg genopbyggede min Mac som et repo med chezmoi, kørte mit årlige værktøjs-review som en samtale med Claude, og automatiserede de dele, jeg plejede at klikke mig igennem.</description>
      <content:encoded><![CDATA[<p>I sidste indlæg satte jeg mig et mål for de første halvfems dage: en koldstart på fem minutter. Fra frisk bærbar til kørende kode på under fem minutter — klon projektet, kør det lokalt, ram et live-endpoint, bestå testene, alt sammen før kaffen bliver kold. Den korteste ærlige vej fra en ny medarbejders første morgen til deres første rigtige bidrag. Det er den on-ramp, jeg vil bygge for teamet hos GolfNext, den danske golf-tech-virksomhed, jeg er på vej til at starte hos.</p>
<p>Før jeg kan række det videre til nogen anden, skylder jeg det til min egen maskine. Et nyt job betyder, at en ny MacBook er på vej, så jeg rettede det samme mål mod mig selv først.</p>
<h2 id="det-to-år-gamle-bogmærke">Det to år gamle bogmærke<a class="gn-heading-anchor" href="#det-to-%c3%a5r-gamle-bogm%c3%a6rke" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>For omkring to år siden rullede et <a href="https://github.com/brandstetterm/dotfiles">dotfiles-repo</a> forbi i min GitHub-timeline — det feed, der viser, hvad de folk, man følger, arbejder på. Det tilhørte Manuel Brandstetter, en tidligere kollega og en af de bedste udviklere, jeg har arbejdet sammen med. Han overtog som lead på <a href="https://scrumlr.io">scrumlr.io</a>, det open source-retrospektivværktøj, jeg var med til at bygge, efter at jeg forlod inovex, det tyske konsulenthus, hvor vi havde arbejdet sammen. Så da hans setup rullede forbi, stoppede jeg op og kiggede. Ingen stor pitch, bare der i forbifarten: alt det, der gør en bærbar til <em>hans</em> bærbar, versionsstyret ét sted. Jeg husker, at jeg tænkte, klart og højt: &ldquo;Det burde jeg gøre.&rdquo; Så lod jeg være. I to år.</p>
<p>Hvis du aldrig har holdt dotfiles: det er de små konfigurationsfiler, der stille former din maskine. Din shell-opsætning, dine editor-indstillinger, din git-konfiguration — de dusinvis af små præferencer, du ellers ville bygge op i hånden, fra hukommelsen, hver gang du får en ny computer. Et dotfiles-repo lægger dem under versionsstyring, så maskinen bliver reproducerbar i stedet for håndlavet og glemt.</p>
<p>Grunden til, at jeg aldrig fik gjort det, er den kedelige: det føltes altid som en eftermiddag, jeg kunne bruge på noget mere presserende. Men det smarte ved en helt ny bærbar er, at den fjerner undskyldningen. Du skal sætte maskinen op alligevel. Så kan du lige så godt sætte den op <em>én gang</em>, ordentligt, på en måde du aldrig skal gentage.</p>
<h2 id="det-review-jeg-kører-hver-december">Det review, jeg kører hver december<a class="gn-heading-anchor" href="#det-review-jeg-k%c3%b8rer-hver-december" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Det er en vane, jeg har holdt i årevis. Et eller andet sted i den stille uge omkring jul sætter jeg mig ned og gennemgår mine værktøjer. Bruger jeg stadig det bedste til hver opgave, eller bare det, jeg tilfældigvis valgte for tre år siden og aldrig satte spørgsmålstegn ved? Det er som regel en eftermiddag alene — med changelogs, halvt huskede Hacker News-tråde, en smule &ldquo;hvad bruger alle andre nu&rdquo;.</p>
<p>I år havde et forspring. Et par uger forinden var jeg gået på jagt efter noget i stil med nvm, men til <em>alle</em> runtimes, ikke kun Node: Python, Go, Java, det hele. Jeg fandt <a href="https://mise.jdx.dev">mise</a>, som gør præcis det, og at bytte en skuffe fuld af sprog-specifikke versionsstyringsværktøjer ud med ét enkelt var det mest tilfredsstillende, jeg havde gjort ved mit setup i månedsvis. Den lille sejr er nok det, der stille og roligt skubbede et to år gammelt &ldquo;det burde jeg gøre&rdquo; over i &ldquo;gør det nu&rdquo;.</p>
<p>Så i år kørte jeg gennemgangen som en samtale i stedet. Jeg pegede Claude — Anthropics AI-assistent, den samme jeg skal bygge med på arbejdet — mod mit nuværende setup og stillede den skarpe version af spørgsmålet: hvad her er forældet, hvad mangler, hvad ville du ændre, og hvorfor. Så lod jeg den researche og argumentere for sin sag.</p>
<p>Jeg holdt resultatet ærligt ved at gøre det til en diff. Det gamle setup blev på min <code>main</code>-branch; det reviderede gik på en branch, jeg kaldte <code>cleanup</code>. Så hver anbefaling var en linje, jeg kunne læse, acceptere eller afvise, ikke en sort boks, der omskrev min maskine, mens jeg ikke kiggede. Den ramme betød mere, end jeg havde regnet med. Et review, man kan læse linje for linje, er et review, man rent faktisk stoler på.</p>
<p>To ting kom ud af det: en måde at behandle hele maskinen som ét repo, og en kort liste over værktøjer, jeg havde været dum at springe over.</p>
<h2 id="chezmoi-maskinen-som-et-repo">chezmoi: maskinen som et repo<a class="gn-heading-anchor" href="#chezmoi-maskinen-som-et-repo" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Værktøjet er <a href="https://chezmoi.io">chezmoi</a>. Det holder dine dotfiles i et git-repo og renderer dem ud på enhver maskine, du ejer — <em>chez moi</em>, passende nok, fransk for &ldquo;hjemme hos mig&rdquo;.</p>
<p>Det, der solgte mig frem for en almindelig mappe fuld af symlinks, er modellen. Filerne i repoet er kilden til sandheden; du <code>apply</code>&lsquo;er dem for at skrive de rigtige filer ind i din home-mappe. Rediger, se diff&rsquo;en, apply, commit. Det understøtter templating (én fil kan tilpasse sig en arbejdsmaskine kontra en privat), og det holder hemmeligheder helt ude af repoet — mine SSH-nøgler bliver inde i <a href="https://developer.1password.com/docs/ssh/">1Password</a>, udleveret til git efter behov, aldrig skrevet til en fil, chezmoi ved et uheld kunne committe.</p>
<p>Den del, der endelig indfriede det to år gamle løfte, er bootstrappet. På en ren Mac kommer hele maskinen tilbage med tre kommandoer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">brew install gh chezmoi
</span></span><span class="line"><span class="cl">gh auth login                              <span class="c1"># sign in to GitHub in the browser</span>
</span></span><span class="line"><span class="cl">chezmoi init --apply bitionaire/dotfiles   <span class="c1"># clone + render everything into place</span>
</span></span></code></pre></div><p>Den sidste linje kloner repoet, installerer mit shell-framework, lægger hver konfigurationsfil på plads og kører et sæt førstegangs-opsætningsscripts. Én navngivningskonvention klarer det meste af magien: en fil ved navn <code>dot_zshrc</code> i repoet bliver til <code>~/.zshrc</code> på maskinen. <code>dot_config/ghostty/config</code> bliver til <code>~/.config/ghostty/config</code>. <code>dot_</code>-præfikset er bare chezmois måde at skrive et indledende punktum uden at forvirre din filbrowser. Når først det falder på plads, læser repoet som et spejl af din home-mappe.</p>
<h2 id="de-værktøjer-gennemgangen-faktisk-ændrede">De værktøjer, gennemgangen faktisk ændrede<a class="gn-heading-anchor" href="#de-v%c3%a6rkt%c3%b8jer-gennemgangen-faktisk-%c3%a6ndrede" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Jeg kørte allerede <a href="https://ohmyz.sh">oh-my-zsh</a> med <a href="https://github.com/romkatv/powerlevel10k">powerlevel10k</a>-prompten. Gennemgangen pressede hårdt på for tre tilføjelser, og havde ret i alle tre:</p>
<ul>
<li><strong><a href="https://ghostty.org">Ghostty</a></strong> — en hurtig, moderne terminal. Jeg havde holdt mig til macOS-standarden i årevis af ren træghed. Ghostty er hurtigere, kan temasættes i en almindelig tekstfil (hvilket betyder, chezmoi kan spore den), og renderer de powerline-glyffer, min prompt bruger, uden en separat Nerd Font-installation.</li>
<li><strong><a href="https://github.com/junegunn/fzf">fzf</a></strong> — en fuzzy finder, og den, der ændrede min hverdag mest. <code>Ctrl-R</code> forvandler shell-historik fra en blind pil-op-jagt til en levende fuzzy-søgning. <code>Ctrl-T</code> lægger en filsti ind i det, du er ved at skrive. Pipe hvad som helst ind i den — <code>git branch | fzf</code> — og du får en interaktiv vælger gratis.</li>
<li><strong><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a></strong> — et smartere <code>cd</code>. Den lærer de mapper, du faktisk bruger, og lader dig hoppe efter et fragment af navnet. <code>z dot</code> lander mig i mit dotfiles-repo fra hvor som helst på maskinen, uanset hvor dybt det er begravet.</li>
</ul>
<p>Ingen af dem er eksotiske — det er værktøjer, mange terminal-tunge folk allerede sværger til, og jeg havde bare aldrig sat mig ned for at tilføje dem. At koble dem på er nogle få linjer shell-konfiguration:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># fzf — fuzzy finder (Ctrl-R history search, Ctrl-T file picker)</span>
</span></span><span class="line"><span class="cl"><span class="nb">command</span> -v fzf &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>fzf --zsh<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># zoxide — smarter cd (`z &lt;dir&gt;` jumps by frequency + recency)</span>
</span></span><span class="line"><span class="cl"><span class="nb">command</span> -v zoxide &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>zoxide init zsh<span class="k">)</span><span class="s2">&#34;</span>
</span></span></code></pre></div><h2 id="automatisering-af-de-dele-jeg-plejede-at-klikke-mig-igennem">Automatisering af de dele, jeg plejede at klikke mig igennem<a class="gn-heading-anchor" href="#automatisering-af-de-dele-jeg-plejede-at-klikke-mig-igennem" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>En frisk Mac er ikke bare dotfiles. Det er en eftermiddag med at downloade apps, trække dem til Programmer, og klikke sig igennem de samme Systemindstillinger, du har sat på hver Mac, du nogensinde har ejet. Det er den del, gennemgangen automatiserede, og den del, jeg er mest stolt af.</p>
<p>Apps kommer fra en enkelt <code>Brewfile</code> — et manifest, <a href="https://brew.sh">Homebrew</a> læser for at installere alt i ét hug:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">brew bundle --file<span class="o">=</span>Brewfile   <span class="c1"># installs every CLI tool, app, and App Store entry</span>
</span></span></code></pre></div><p>Én fil opremser kommandolinjeværktøjerne, desktop-apps&rsquo;ene (1Password, Firefox, Ghostty, Docker, editoren) og endda Mac App Store-køb. Ny maskine, én kommando, og en snes apps installerer sig selv.</p>
<p>Så er der macOS-indstillingerne. chezmoi kører opsætningsscripts ved første apply, og et af dem er bare en liste af <code>defaults write</code>-kommandoer — den scriptbare side af Systemindstillinger. En smagsprøve:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">defaults write -g KeyRepeat -int <span class="m">2</span>                            <span class="c1"># fast key repeat</span>
</span></span><span class="line"><span class="cl">defaults write -g ApplePressAndHoldEnabled -bool <span class="nb">false</span>        <span class="c1"># hold a key to repeat it</span>
</span></span><span class="line"><span class="cl">defaults write com.apple.finder AppleShowAllFiles -bool <span class="nb">true</span>  <span class="c1"># show hidden files</span>
</span></span><span class="line"><span class="cl">defaults write com.apple.finder _FXSortFoldersFirst -bool <span class="nb">true</span>  <span class="c1"># folders on top</span>
</span></span></code></pre></div><p>Jeg havde sat hver eneste af dem i hånden på hver Mac i et årti og aldrig tænkt på at skrive dem ned — nu er de en fil. Det er ikke totalt. Apple blokerer bevidst for at scripte de sikkerhedsfølsomme dele, så at give tilgængelighedstilladelser og logge ind på iCloud forbliver manuelt. Men de dusinvis af små knapper, der plejede at æde en eftermiddag, tager nu sekunder, og jeg skal aldrig huske dem igen.</p>
<h2 id="et-godt-review-skærer-lige-så-meget-væk-som-det-tilføjer">Et godt review skærer lige så meget væk, som det tilføjer<a class="gn-heading-anchor" href="#et-godt-review-sk%c3%a6rer-lige-s%c3%a5-meget-v%c3%a6k-som-det-tilf%c3%b8jer" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Overraskelsen var, hvor meget samtalen talte mig <em>fra</em>. Et værktøjs-review handler ikke kun om nyt legetøj; det handler om at lægge mærke til det skidt, man har slæbt rundt på.</p>
<p>Så <code>cleanup</code>-branchen slettede ting. De ældre versionsstyringsværktøjer, jeg havde samlet sammen, plus en håndinstalleret Python, blev alle foldet ind i den ene mise, jeg havde taget i brug et par uger forinden. En genvej, jeg havde lavet for at omgå en gammel Docker-finurlighed, der siden er rettet — væk. Et virvar af arbejde-kontra-privat git-konfiguration, jeg havde samlet på tværs af job, fladet ud til én identitet. Hver af dem var en lille beslutning, jeg havde taget for år tilbage af en god grund, og så aldrig genbesøgt, da grunden var udløbet.</p>
<p>Det er den egentlige værdi i at gøre det med en sparringspartner. Overladt til mig selv har jeg en tendens til at tilføje. Bedt om at forsvare hver enkelt del højt, fik jeg endelig fjernet.</p>
<h2 id="hvad-jeg-tager-med-mig">Hvad jeg tager med mig<a class="gn-heading-anchor" href="#hvad-jeg-tager-med-mig" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Det imponerende var ikke, at en AI kan skrive en konfigurationsfil — det ved alle, den kan nu. Det var gennemgangen. Den rakte mig ikke et generisk &ldquo;bedste setup&rdquo;; den kiggede på <em>mit</em>, spurgte, hvad jeg faktisk laver hele dagen, og argumenterede for ændringer, der passede, med begrundelsen vedhæftet, så jeg kunne skyde igen, hvor den tog fejl. Mindre et værktøj end det december-review, jeg allerede kører, med en kyndig person på den anden side af bordet.</p>
<p>Og det lukkede en cirkel. Den fem-minutters koldstart, jeg vil give GolfNext-teamet, er præcis det samme, skaleret op: en ny person, en ny bærbar, og en kort vej til rigtigt arbejde uden en uges opsætnings-skat. Jeg byggede bare den personlige version først.</p>
<p>Så hvis du har dit eget &ldquo;det burde jeg gøre&rdquo;-bogmærke, der samler støv — et repo, der engang rullede forbi i din timeline, måske — så er det her dit tegn. Vent på den næste rene maskine. Og sæt den så op én gang. To år for sent slår stadig aldrig.</p>
<h2 id="referencer">Referencer<a class="gn-heading-anchor" href="#referencer" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Værktøjerne og dokumentationen, det er værd at bogmærke, nogenlunde i den rækkefølge, de optræder ovenfor:</p>
<ul>
<li><strong><a href="https://chezmoi.io">chezmoi</a></strong> — dotfiles-manager. Start med <a href="https://www.chezmoi.io/quick-start/">quick start</a> og <a href="https://www.chezmoi.io/user-guide/setup/">user guide</a>.</li>
<li><strong><a href="https://brew.sh">Homebrew</a></strong> og <strong><a href="https://github.com/Homebrew/homebrew-bundle">Brewfile / <code>brew bundle</code></a></strong> — installer apps og CLI-værktøjer fra ét manifest.</li>
<li><strong><a href="https://ohmyz.sh">oh-my-zsh</a></strong> — Z shell-framework, med <strong><a href="https://github.com/romkatv/powerlevel10k">powerlevel10k</a></strong>-prompttemaet.</li>
<li><strong><a href="https://ghostty.org">Ghostty</a></strong> — hurtig terminal med konfiguration som tekst.</li>
<li><strong><a href="https://github.com/junegunn/fzf">fzf</a></strong> — fuzzy finder til kommandolinjen.</li>
<li><strong><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a></strong> — et smartere <code>cd</code>, der lærer dine vaner.</li>
<li><strong><a href="https://mise.jdx.dev">mise</a></strong> — én versionsstyring til node, python, go og resten.</li>
<li><strong><a href="https://developer.1password.com/docs/ssh/">1Password SSH-agent</a></strong> — hold private nøgler ude af repoet og væk fra disken.</li>
<li>Repoet, der startede det hele: <strong><a href="https://github.com/brandstetterm/dotfiles">Manuel Brandstetters dotfiles</a></strong>.</li>
</ul>
<h2 id="ps--et-dock-trick-der-intet-har-med-dotfiles-at-gøre">P.S. — et Dock-trick, der intet har med dotfiles at gøre<a class="gn-heading-anchor" href="#ps--et-dock-trick-der-intet-har-med-dotfiles-at-g%c3%b8re" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>En gratis godbid til de Mac-brugere, der nåede så langt. Det har intet med noget af ovenstående at gøre, men det er den slags lille ting, der stille har glædet mig i årevis: du kan smide blanke mellemrum (spacers) ind i Docken for at dele dine apps op i klynger. Samme <code>defaults write</code>-trick som resten af indlægget. Kør det én gang per spacer, du vil have:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># full-height spacer (run again for each extra one)</span>
</span></span><span class="line"><span class="cl">defaults write com.apple.dock persistent-apps -array-add <span class="s1">&#39;{tile-data={}; tile-type=&#34;spacer-tile&#34;;}&#39;</span> <span class="o">&amp;&amp;</span> killall Dock
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># half-height spacer (run again for each extra one)</span>
</span></span><span class="line"><span class="cl">defaults write com.apple.dock persistent-apps -array-add <span class="s1">&#39;{&#34;tile-type&#34;=&#34;small-spacer-tile&#34;;}&#39;</span> <span class="o">&amp;&amp;</span> killall Dock
</span></span></code></pre></div><p>Træk i mellemrummene som ethvert andet Dock-ikon, eller træk et ud af Docken for at fjerne det. Bittelille ting. Får en proppet Dock til at føles organiseret.</p>
]]></content:encoded>
      <category>Teknik</category>
      <category>Infrastruktur</category>
    </item>
    <item>
      <title>Et brev til mig selv, før dag ét</title>
      <link>https://build.golfnext.com/da/posts/letter-day-one/</link>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Johann Böhler</dc:creator>
      <guid>https://build.golfnext.com/da/posts/letter-day-one/</guid>
      <description>Før min første dag hos GolfNext — og før jeg overhovedet har skrevet under — hvad jeg går ind til, hvorfor jeg forlader et job jeg godt kunne lide, de mål jeg sætter mig for de første halvfems dage, og det kompas jeg tager med.</description>
      <content:encoded><![CDATA[<p><em>&ldquo;I may have something of interest to you, Johann. Would be great to connect 👍🏼.&rdquo;</em></p>
<p>Det er hele beskeden. Den landede på LinkedIn ud af det blå, fra en rekrutterer ved navn Joe — hvilket, i betragtning af hvor ofte den slags kommer ind under et lånt fornavn, måske endda er hans rigtige. Ingen virksomhed, ingen rolle, ingen detaljer. Den slags besked, man normalt arkiverer uden at tænke nærmere over det.</p>
<p>Denne her ender med, at jeg forlader et job, jeg godt kan lide, i en virksomhed jeg ikke har ét ondt ord at sige om, for en virksomhed hvis navn Joe ikke engang ville fortælle mig. Det måtte jeg selv finde ud af. Nå ja — mig selv og en AI.</p>
<p>Jeg skriver det her før min første arbejdsdag. Jeg har ikke skrevet under på kontrakten endnu; jeg kender ikke engang min startdato. At skrive virksomhedens blog, før jeg officielt har jobbet, lyder bagvendt. Måske er det også det. Jeg gør det alligevel, fordi jeg går rundt med flere tanker, end jeg kan rumme, og fordi jeg vil have et nedslag — noget at læse igen om seks måneder og holde op mod det, der rent faktisk skete.</p>
<p>Så det her er to ting på én gang. Historien om, hvordan en besked på to linjer blev til et job, og et brev til en fremtidig udgave af mig selv.</p>
<p>En tilståelse, før vi begynder: Jeg har aldrig skrevet et blogindlæg i mit liv. Jeg begynder nu med vilje — det at skrive fastholder idéer, der ellers bare er damp. Så her er jeg, og tænker højt.</p>
<h2 id="fra-tre-brødkrummer-til-en-virksomhed">Fra tre brødkrummer til en virksomhed<a class="gn-heading-anchor" href="#fra-tre-br%c3%b8dkrummer-til-en-virksomhed" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Jeg spurgte Joe, hvad hans besked handlede om. Det viste sig at være en rolle som Lead Engineer i en lille, men veletableret virksomhed: den første hjørnesten i et AI-first-udviklingsteam — et team, der bygger software mest ved at styre AI i stedet for at taste hver linje i hånden. Joe ville ikke nævne virksomheden. I stedet gav han mig brødkrummer — dansk sportstech, eksisteret siden 2012, 400+ anlæg i 18 lande.</p>
<p>Så jeg rakte brødkrummerne til Claude, Anthropics AI-assistent. På sekunder navngav den virksomheden: GolfNext, i Ry, en kort køretur fra hvor jeg bor i Danmark. De bygger det selvbetjeningsudstyr, golfklubber kører på — mere om det senere.</p>
<p>Den lille øvelse er mere eller mindre jobbeskrivelsen. Hvis man kan pege en AI mod tre vage spor og få den til at rekonstruere virksomheden bag dem, så er det jo arbejdet. Så jeg svarede med en fuldt ud AI-genereret besked, åbent mærket:</p>
<p><em>&ldquo;Full disclosure, Joe: this reply is AI-generated. Felt fitting for an AI-first pitch — and yes, I let the agent do the detective work too. 😄&rdquo;</em></p>
<p>De skrev tilbage. Et par dage senere kørte jeg ud til Ry og tilbragte en time på GolfNext-kontoret, og da jeg gik derfra, vidste jeg, at jeg ville have det. Tegnet var det første, de sagde, da jeg satte mig: <em>&ldquo;Let&rsquo;s just have a chat here and talk openly.&rdquo;</em> Intet teater.</p>
<p>Før jeg bandt mig til noget, ville jeg vide præcis, hvad jeg gik ind til, så jeg spurgte. Thomas Herskind, der står for produktet hos GolfNext, sendte en oversigt over de systemer, de driver i dag. Jeg læste den og skrev tilbage med alt det, jeg mente var værd at tage fat på først, punkt for punkt. Det blev langt. Meget langt.</p>
<p>Thomas&rsquo; svar var på seks ord. <em>&ldquo;Congrats on your first novel, Johann!&rdquo;</em></p>
<p>Jeg grinede højt. Man lærer meget om et sted af, hvordan det driller en, før man overhovedet har skrevet under.</p>
<h2 id="de-70-idéer-før-denne-her">De 70 idéer før denne her<a class="gn-heading-anchor" href="#de-70-id%c3%a9er-f%c3%b8r-denne-her" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Intet af det forklarer, hvorfor en besked på to linjer uden detaljer overhovedet fangede min opmærksomhed. Det kræver, at jeg spoler et par måneder tilbage — og først fortæller, hvem der skriver, nu det her er internettet.</p>
<p>Jeg er generalist. En full-stack-udvikler, der aldrig har haft noget imod at lære, hvad et projekt nu krævede. Seksten år i faget: en masse backend, en masse webapps, et par år som teamleder, og medskaber af scrumlr, et open source-værktøj, teams bruger til at holde retrospektiver — de møder, hvor man kigger tilbage på, hvad der gik godt, og hvad der ikke gjorde. Jeg har aldrig været den udvikler, der elsker teknologi for teknologiens skyld. Det, der trækker i mig, er den anden ende: brugeren, designet, værdien, det der gør nogens dag en smule lettere. Og at nå dertil uden alt for mange omveje. Det er CV&rsquo;et på ét afsnit. Kløen er den del, det ikke kan bære.</p>
<p>Jeg havde længe haft en kløe efter at bygge noget af mit eget. Hvad som helst, der måske kunne vokse til en forretning — og en undskyldning for at skubbe Claude, så langt den kunne, og finde ud af, hvor meget rigtig software den egentlig kunne bygge. Det første projekt udsprang af en irritation, jeg havde slæbt med på tværs af job. Release notes — den &ldquo;hvad er ændret&rdquo;-liste, der er ment til dem, der bruger softwaren — bliver ved med at havne i commit-beskeder, de korte noter udviklere efterlader til hinanden. <em>&ldquo;Upgrade ESLint to version 10.&rdquo;</em> Hvem er det til? Så jeg byggede et lille produkt, der lader dig skrive ordentlige ændringsnoter i almindeligt sprog, lige ved siden af koden, og række dem til de produktansvarlige, der burde eje dem. Bygget fra bunden på cirka to uger — det brugerne ser, backenden bag det, pipelinen der sender opdateringer ud af sig selv. Det hele, med AI bag det meste af tastaturet.</p>
<p>Det føltes godt nok til, at jeg blev grådig og rakte ud efter noget meget større: en agent, der holder softwareprojekter i live. Vedligeholder dem, udvider dem, mest på egen hånd, med release-notes-værktøjet som sin første patient. Det var der, jeg ramte en mur, og muren lærte mig mere end de fleste af mine færdige projekter.</p>
<p>Noget af det var omfang. Jeg havde pakket et helt produkt rundt om noget, der burde være startet som et lille eksperiment. Men den dybere lektie handlede om værktøjet. Nutidens AI er virkelig fremragende på en blank side, og til små, velafgrænsede ændringer. Den bliver usikker, i samme øjeblik man beder den rive fat i store stykker kernelogik. Den begynder at hallucinere — selvsikkert at opfinde ting, der ikke er der. Den rydder aldrig helt op efter sig selv. Gammel kontekst hænger ved og lister sig ind i næste forsøg. Et så ambitiøst byggeri kræver en klar plan for <em>hvordan</em> man kommer derhen, før man skriver en eneste linje, og at holde den plan sammen på egen hånd, i stjålne aftener og weekender, var bare ikke noget, jeg kunne magte. Det projekt er parkeret, ikke begravet.</p>
<p>Så jeg skiftede taktik. Jeg satte Claude til at være sparringspartner på mindre ting, den slags jeg kunne gøre færdig på et par uger. Jeg pitchede en idé; den gik ud og undersøgte markedet og kom som regel tilbage med det samme svar — overfyldt, allerede finansieret, lad være. Surt. Også nyttigt.</p>
<p>Efter omkring 70 af dem holdt jeg op med at pitche og stillede det åbenlyse spørgsmål: hvad <em>burde</em> jeg så lave? Svaret var forfriskende ligetil. <em>Hold op med at jage et smart produkt. Find nogen med et rigtigt problem, og hjælp dem med at bygge en softwaredrevet proces rundt om det, der sparer dem alvorligt meget tid.</em> Og da den kender min måde at arbejde på, skitserede den nogenlunde, hvordan det kunne se ud. Et par måneder senere landede Joes to linjer i min indbakke — og rollen bag dem viste sig, da jeg først spurgte, at være et næsten perfekt match til den skitse. Beskeden uden detaljer var præcis det, jeg havde fået besked på at gå ud og finde.</p>
<h2 id="hvad-det-egentlig-handler-om">Hvad det egentlig handler om<a class="gn-heading-anchor" href="#hvad-det-egentlig-handler-om" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>På høje tid, jeg fortæller, hvad det egentlig var, jeg havde fundet. GolfNext bygger selvbetjeningsværktøjer og hardware til golfklubber — forestil dig en boldautomat, medlemmerne selv betjener på driving rangen, eller et system, der tager den fummelfingrede, fejlbehæftede opgave med at sælge og indløse vouchere og gør den rolig. I årevis har dygtige eksterne partnere bygget det meste af den software, og det har bragt virksomheden langt. Skiftet nu, som de lagde det frem for mig, er, at GolfNext vil have ingeniørvidenen til at bo inde i virksomheden: et team, der forstår hele landskabet indefra, til den slags stabile, langsigtede vækst, der kommer af at eje det, man afhænger af. Ingen dom over dem, der har bygget det indtil nu — bare det naturlige næste skridt for en virksomhed, der regner med at være her længe.</p>
<p>Jeg er en af de første brikker i det. Opgaven: suge landskabet til mig — det samme, som Thomas gav mig et forhåndsblik af i den oversigt — overtage udviklingen bid for bid, og før længe være med til at bygge et team op omkring det.</p>
<p>Her er en tilståelse, der lyder underligt i en ingeniørblog: Jeg har knap skrevet kode i hånden i månedsvis. Kald det et halvt år på mine egne projekter, et par måneder på arbejdet. Ikke fordi jeg holdt op med at bygge. Byggeriet rykkede bare et niveau op. Mere af min dag går nu med at styre AI, gennemgå det, den giver tilbage, og designe de værn, der holder dens output sikkert og korrekt. Det er der, faget er på vej hen — og det er præcis det team, GolfNext beder mig bygge. Ingen kan ærligt fortælle dig, hvor det hele lander; jorden flytter sig med få ugers mellemrum. Men retningen er klar nok til at pege på, og at pege på den er jobbet.</p>
<p>Det er <em>hvad</em>. Før <em>hvordan</em> er der et farvel, jeg skylder.</p>
<h2 id="at-forlade-noget-godt">At forlade noget godt<a class="gn-heading-anchor" href="#at-forlade-noget-godt" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Først den ærlige del, ingen nyder at skrive. At sige ja her betyder at forlade Salling Group — en af Danmarks største detailvirksomheder — og jeg vil ikke have et eneste ord af det her læst som en klage. Salling var det perfekte første skridt, da jeg landede i Danmark: en ægte kultur, mennesker der virkelig passer på hinanden, arbejde jeg er stolt af. Jeg leverede ting, der betød noget, under deadlines uden plads til fejl, ved siden af ingeniører, jeg lærte en hel del af. Jeg fik gode venner der. En del af mig er ked af at gå.</p>
<p>Grunden til, at jeg flytter mig, er ikke en fejl ved stedet. Det er en forskel i, hvad jeg er skruet sammen til. I en virksomhed af den størrelse bliver hver beslutning vejet af mange omtænksomme mennesker, hvilket er præcis sådan, en stor detailvirksomhed gør sig fortjent til og fastholder folks tillid. Men det betyder, at det er sjældent at være tæt på rattet, og endnu sjældnere at styre hurtigt. Jeg vil have rattet. Jeg vil pege på en retning og bevæge mig. Den kløe er størstedelen af grunden til, at jeg begyndte at bygge ting ved siden af i første omgang, og det er præcis det, denne nye rolle rækker mig. At forlade noget godt af den rigtige grund er stadig at forlade noget godt. Begge dele er sande på én gang.</p>
<h2 id="de-mål-jeg-sætter-mig">De mål, jeg sætter mig<a class="gn-heading-anchor" href="#de-m%c3%a5l-jeg-s%c3%a6tter-mig" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Nu til <em>hvordan</em>. Den mur, jeg ramte på sideprojektet, er hele grunden til, at målene herunder ser ud, som de gør. Hvis AI er stærkest inden for klare grænser, er svaret ikke at prompte hårdere. Det er at bygge et landskab, hvor grænserne er tydelige — hvor kontekst er billig at række videre, hvor en ændring er nem at skære lille, hvor værnene fanger det, modellen tager fejl af, før et menneske skal. For det er der, jeg tror, det hele er på vej hen: den knappe ressource i software bliver, hvor hurtigt hvem som helst — menneske eller AI — kan sætte sig ind i en kontekst, de aldrig har set. Hvis en ny udvikler og en frisk AI-session begge kan forstå en service hurtigt, kan arbejdet gå i gang med det samme, af den der griber det. Hvis de ikke kan, starter hver opgave med arkæologi. Det meste af det følgende handler i virkeligheden om den ene sætning.</p>
<p>Ét forbehold, der betyder noget: Jeg er ikke begyndt endnu. Det her er de standarder, jeg vil holde mig selv til, ikke beslutninger jeg træffer alene — rækkefølgen og prioriteterne bliver til sammen med de mennesker, der har drevet systemerne i årevis.</p>
<p><strong>Gør arbejdet synligt.</strong> Denne blog er mål nummer ét. Når man bygger viden op inde i et team, er det værste, man kan gøre, at låse den inde i private kanaler og enkelte hoveder. At skrive det ned i det åbne tvinger klarhed frem, trækker gode folk til, holder alle ærlige. Så den findes nu. Fremtidige mig: hvis disse indlæg gik i stå, så tag det som et advarselstegn.</p>
<p><strong>Genopbyg forståelsen, ikke bare adgangen.</strong> At overtage fra et eksternt team handler ikke rigtig om at samle adgange og kodeord. Det handler om at genopbygge forståelsen indefra. Så: hver konto tjekket og sat i mit navn, hvert dokument læst og skærpet, indtil jeg kan drive og release hvad som helst i stakken uden at ringe til en ven. Det er den uglamourøse halvdel af de halvfems dage, og det er den halvdel, alt andet venter på.</p>
<p><strong>Byg et landskab, alle kan bygge i.</strong> Det er det, jeg holder mest af. Konkret:</p>
<ul>
<li><strong>En fem-minutters koldstart.</strong> Frisk laptop til kørende kode: forstå projektet, kør det lokalt, ram et live-endpoint, få testsuiten igennem, alt sammen på under fem minutter. Det er rampen ind til at bygge og afprøve, ikke en regelbog for, hvordan man skriver kode. Fremtidige mig, den her kan måles — gå ud og tag tid.</li>
<li><strong>Dokumentation, der bor lige ved siden af koden</strong>, så kontekst er én mappe væk i stedet for én Slack-besked væk.</li>
<li><strong>Automatisering af alt det, vi ellers ville lave i hånden to gange.</strong></li>
<li><strong>Teknisk gæld betalt af, dér hvor den reelt står i vejen</strong> — ikke en hovedrengøring, bare det der spærrer.</li>
<li><strong>Et fælles sæt prompts og presets til AI&rsquo;en</strong>, så alle starter fra den samme gennemprøvede opsætning i stedet for at bygge den op forfra hver gang. Det er det, der gør &ldquo;AI-first&rdquo; til reelt fælles værktøj i stedet for en floskel.</li>
</ul>
<p>Pointen er ikke at være de eneste, der kan bygge. Det er at være kvalitetsporten, der gør en rå idé til noget, man kan stole på, og den hjælpende hånd, der gør det sikkert og let for alle andre at bygge.</p>
<p><strong>Bliv ved tasterne.</strong> Det sagde jeg til Joe, før jeg havde sagt det til nogen hos GolfNext, fordi det ville spare os begge tid. Jeg har ledet et team før, fireogtyve mennesker, og jeg trådte tilbage til at bygge med vilje. <em>&ldquo;If &lsquo;Lead&rsquo; means setting technical direction while still building, I&rsquo;m very in. If it means drifting away from the code, I&rsquo;m the wrong guy.&rdquo;</em> Den linje begyndte som et filter for jobbet. Nu er den et mål. Fremtidige mig: hvis du er drevet væk fra koden, ved du, hvad du skal gøre ved det.</p>
<p><strong>Ansæt fremragende folk, og stol så på dem.</strong> Ikke i de første halvfems dage, og ikke min beslutning alene, men det er den vej, det her går, og det former de tidlige valg. Et lille team af fremragende folk har næsten ikke brug for proces. Hold barren høj, også når I er underbemandede. Den del er i det mindste ikke mig, der projicerer min læseliste over på dem: da ansættelser kom på bordet i Ry, fortalte de mig, at de hellere ville køre underbemandet i et par måneder end tage den forkerte person ind bare for at få mere kapacitet. Det fortalte mig mere om stedet, end noget pitch kunne have gjort. Giv folk kontekst i stedet for godkendelser. Bedøm arbejdet, ikke larmen. Sig som udgangspunkt ja, og stop kun for at diskutere ved de døre, man ikke kan gå tilbage igennem. Og lad der være plads — rigtig plads — til at brainstorme, til at skændes om idéer, til at bygge ting, der måske ikke virker. Den kreative, legende tid er ikke en belønning, man tjener ind, når det seriøse arbejde er gjort. I dette fag <em>er</em> den det seriøse arbejde.</p>
<h2 id="et-kompas-ikke-en-regelbog">Et kompas, ikke en regelbog<a class="gn-heading-anchor" href="#et-kompas-ikke-en-regelbog" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Målene er planen. Under planen ligger et kompas, og det er værd at vise frem, for det vil forme, hvordan jeg styrer.</p>
<p>Det, jeg ikke møder op med, er et manifest. Jeg tror ikke på, at en kultur kan skrives til live. Den vokser ud af sit miljø og menneskene i det, dag for dag, i tusind små øjeblikke ingen skriver ned — og derfor kan den kun ledes ved eksempel, aldrig ved håndhævelse. Hvad GolfNexts værdier faktisk er, finder vi ud af sammen, i arbejdet. Og jeg mener <em>sammen</em> i begge retninger. Jeg ankommer med overbevisninger — det her indlæg er fuldt af dem — men jeg regner med at blive forandret mindst lige så meget, som jeg forandrer noget, og at tage arbejdsmåder til mig, jeg ikke engang har overvejet endnu.</p>
<p>En del af det skylder jeg inovex, det tyske konsulenthus, hvor jeg tilbragte syv år — den længste strækning af de seksten. De havde en trekant — Medarbejdere, Kunder, Økonomi — og de mente hvert hjørne af den: fair løn, friheden til at vælge de rigtige værktøjer til opgaven, fornuftige og agile arbejdsmåder, ægte omsorg for de mennesker, der laver arbejdet. Det meste af det holder jeg stadig fast i. Min egen vægtning ligger bare en smule anderledes. Hvor deres trekant sætter medarbejdertilfredshed, sætter min <em>Craft</em> — håndværket. Ikke fordi tilfredshed ikke betyder noget, men fordi jeg er kommet til at tro, at den vokser ud af at lave godt arbejde sammen, snarere end at være noget, man kan sigte direkte efter.</p>
<p>Så de tre punkter, jeg styrer efter, er <em>Kunden</em> (sigtet — at løse det problem, folk virkelig ville savne), <em>Craft</em> (svinget — at bygge det godt, og blive bedre ved at bygge det) og <em>Kapital</em> (det lange spil — at holde sig stærk nok til at spille runden færdig, og holde viden inde i huset, så intet vigtigt bor i ét enkelt hoved). Læn dig for hårdt op ad én af dem, og du slicer; få alle tre til at svinge sammen, og bolden falder i.</p>
<h2 id="hvad-jeg-har-læst-og-hvad-jeg-tog-med">Hvad jeg har læst, og hvad jeg tog med<a class="gn-heading-anchor" href="#hvad-jeg-har-l%c3%a6st-og-hvad-jeg-tog-med" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>En god del af målene ovenfor — den høje bar, tilliden, den næsten fraværende proces — har jeg fra <em>No Rules Rules</em>, Reed Hastings&rsquo; og Erin Meyers bog om, hvordan Netflix bliver drevet. Argumentet, komprimeret: skru talenttætheden op, til du har et team af fremragende folk, brug åbenhjertighed til at holde dem ærlige over for hinanden, og begynd så at fjerne reglerne — for de fleste regler findes for at forhindre dårlige udfald fra middelmådige præstationer, og har man ikke dem, koster reglerne mere, end de sparer.</p>
<p>Den del, jeg vender tilbage til igen og igen, er udgiftspolitikken. Den koger ned til én enkelt instruks: <em>handl i virksomhedens bedste interesse.</em> Det ligner en politik om kvitteringer. Det er det ikke. Det er en erklæring om, hvem der har ansvaret. En regelbog rækker dig et svar, en anden har regnet ud engang, til en situation, der måske ikke længere er din. Én instruks rækker dig <em>målet</em> og lader dig selv finde svaret til den situation, du faktisk står i. Det er mere vægt at bære. Det er også det, der gør dig til en reel del af det hele i stedet for en, der eksekverer andres beslutninger — og det er der, kreativitet og initiativ kommer fra. Man får ikke en uventet god idé ud af en, der følger trin.</p>
<p><strong>Den del, der skal trænes.</strong> Intet af det virker uden åbenhjertighed, og åbenhjertighed kommer ikke gratis — for nogen. Trækket mod at glatte tingene ud er universelt, og at holde af nogen uden at sige tingene er den fejltype, der føles venlig og ikke er det. Fjern reglerne og behold høfligheden, og du får ikke frihed. Du får et team, der stille driver, mens alle gætter.</p>
<p>Så jeg læste også <em>Radical Candor</em>, Kim Scotts bog om præcis det her — brugbar feedback kræver begge halvdele på én gang: man skal faktisk holde af mennesket, og man skal faktisk sige tingene. Deraf dette løfte, på skrift, hvor enhver kan holde mig fast på det: forvent, at jeg er meget klar omkring, hvad jeg mener, hvad jeg har besluttet, og hvorfor — også når svaret er, at jeg tog fejl.</p>
<p><strong>Og alligevel — ikke en kultur, man kan kopiere.</strong> Ét forbehold over det hele. Jeg vil ikke importere Netflix&rsquo; kultur en bloc. Den voksede ud af en bestemt virksomhed, i et bestemt marked, i en bestemt størrelse, og meget af det, der får den til at virke dér, rejser ikke godt. Bogens berømte skarpe kant rejser dårligst af det hele: keeper-testen, hvor enhver, en leder ikke ville kæmpe for at beholde, bliver sendt afsted med en generøs fratrædelse. Den del ville jeg lade blive helt og aldeles. Talenttæthed tjent ved at ansætte omhyggeligt er noget andet end talenttæthed vedligeholdt ved at skære fra — og en så hård kant har meget amerikanske rødder. Det er ikke sådan, jeg ville ønske, et lille team i Danmark skulle føles. Der er mindst lige så meget at hente i, hvordan dygtige folk i virksomheder som Google og Apple har bygget og drevet deres teams — og de kulturer er uenige med Netflix&rsquo; på flere punkter, med vilje. Vores bliver sin egen — formet i Ry, ikke kopieret ind fra Californien. Den virkelige test af alt det her er, hvad alle gør, når ingen håndhæver noget.</p>
<p>Hvilket lander tilbage, hvor udgiftspolitikken startede. Overtag ikke andres regelbog. Overtag vanen med at spørge, hvad der reelt er bedst her, lige nu, med de mennesker vi faktisk har — og bliv ved med at være villig til at ændre svaret, når situationen gør.</p>
<h2 id="en-note-til-fremtidige-mig">En note til fremtidige mig<a class="gn-heading-anchor" href="#en-note-til-fremtidige-mig" aria-label="Link til dette afsnit"><span class="gn-ico" style="width:15px;height:15px"><!-- @license lucide-static v1.21.0 - ISC -->
<svg
  class="lucide lucide-link"
  xmlns="http://www.w3.org/2000/svg"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
>
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</span></a>
</h2>
<p>Når du læser det her, ved du alt det, jeg ikke gør. Startdatoen. Om de første halvfems dage gik efter planen. Hvilke af disse mål der overlevede mødet med virkeligheden, og hvilke der var sødt naive. Noget af det vil se indlysende ud bagefter. Noget af det vil se sjovt ud.</p>
<p>Jeg ville have det skrevet ned, før jobbet kunne begynde at forme mine svar — mens det hele stadig er overbevisning, før det størkner til erfaring.</p>
<p>Thomas spøgte allerede med, at mit første lange svar til ham læste som en roman. Betragt dette indlæg som efterfølgeren. Længere, og denne gang offentlig. Lad os se, hvor tæt jeg ramte.</p>
]]></content:encoded>
      <category>Kultur</category>
      <category>AI</category>
    </item>
  </channel>
</rss>
