← Blog

AI agents

Lokale coding agent op macOS: playbook voor een studio van 14

Dinsdagochtend 09:14 in Haarlem. Een niet-technische oprichter opent het dashboard en leest twaalf groen, twee oranje. Dit is het playbook achter dat scherm.

Jacob Molkenboer· Oprichter · A Brand New Company· 14 jun 2026· 9 min
Koperen schakelbord met stoffen kabels, papier met groene sticky note, donkergroen leren onderlegger, rode lakzegel op ivoren bureau.

Dinsdag, 09:14. Het Spaarne ligt nog spiegelglad voor het raam van de studio in Haarlem, de espressomachine is uitgepiept en Marije (geen engineer, oprichter) opent het dashboard op haar iPad. Veertien rijen. Twaalf groen. Twee oranje. De nightly eval van één developer faalde om 03:22 tegen de monorepo omdat een nieuwe Swift Package Manager pin de Xcode-bridge sloopte. Ze stuurt het met één zin door naar de lead engineer en gaat verder met de standup.

Dat dashboard, en de fleet erachter, kostte ons zes weken om uit te rollen. Dit is het playbook.

Naar lokaal, en het NDA dat het afdwong

Een studio van veertien mensen met betaalde cloud-agent seats op team-tier privacy is een prima regel op de begroting. Wat niet prima was, was het antwoord op "waar gaat deze code eigenlijk heen." De NDA's van klanten bij deze studio dekken drie banken, één luchtvaartmaatschappij en een aanbesteding bij de overheid. De compliance officer bij de luchtvaartmaatschappij vroeg in maart, schriftelijk, om een garantie dat geen enkele developer-prompt of completion bij een derde-partij LLM-provider terecht zou komen. Dat konden we op een cloud seat niet leveren zonder enterprise legal werk dat de projectdeadline ver voorbij zou trekken.

Het verkeer op Hacker News rond dit onderwerp is niet bepaald subtiel. "Open source AI must win" haalde de voorpagina met 903 punten in de week dat we de scope aan het schrijven waren, en een tutorial getiteld "How to setup a local coding agent on macOS" landde een dag later op 372 punten. Twee derde van de vragen in die threads waren varianten van hoe krijg ik dit aan de praat voor een team, niet alleen op mijn eigen laptop. Dat is het gat dat dit playbook dicht.

De fleet, de stack, de beperking

Fleet: veertien MacBooks. Elf M3 Pro met 36GB unified memory, drie M4 Max met 64GB.

Stack: Ollama 0.5.x als lokale runtime, Qwen3-Coder 30B op Q4_K_M quant voor inline completion, Qwen3-Coder 72B (Q4) op de twee M4 Max machines voor chat-mode refactors, Continue.dev 1.x als IDE-bridge, een kleine Go-binary die we zelf schreven met de naam evalctl voor de nightly harness, en een statische pagina op het intranet van de studio die de output van die harness uitleest.

De beperking: de oprichter moet groen of rood kunnen lezen zonder een engineer aan haar bureau te roepen. Die ene regel bepaalde elke vervolgkeuze.

De Ollama-installatie, fleet edition

Je klikt Ollama in negentig seconden door op één Mac. Dat veertien keer doen, met model pulls van 18 tot 40GB per stuk, over residentieel glasvezel, is het stuk waar niemand over schrijft.

We deden het in twee passes. Pass één, een launchd-job die Ollama via Homebrew installeert en alleen het inline-completion model pullt, draaide om 23:00 lokaal zodat de uplink op kantoor vrij was. Pass twee, het chat-model, achter een hardware-check zodat het alleen op de M4 Max machines landde.

Het bootstrap-script dat we uitrolden staat in /usr/local/sbin/abn-localagent-bootstrap.sh:

#!/usr/bin/env bash
set -euo pipefail

# 1. Ollama via Homebrew (idempotent)
if ! command -v ollama >/dev/null 2>&1; then
  /opt/homebrew/bin/brew install ollama
fi

# 2. Start the service
brew services start ollama

# 3. Pull the completion model on every machine
ollama pull qwen3-coder:30b-instruct-q4_K_M

# 4. Pull the chat model only on M4 Max
chip=$(system_profiler SPHardwareDataType | awk -F': ' '/Chip/ {print $2}')
if [[ "$chip" == *"M4 Max"* ]]; then
  ollama pull qwen3-coder:72b-instruct-q4_K_M
fi

# 5. Mark bootstrap done so the dashboard can read it
mkdir -p /var/abn/localagent
date -u +"%Y-%m-%dT%H:%M:%SZ" > /var/abn/localagent/bootstrapped_at

We pushten het via Jamf, dat de studio al gebruikte voor MDM. Heb je geen Jamf, dan werkt een Munki-repo of een Ansible playbook over ssh ook prima. Het punt is: één knop, veertien machines, geen Slack-ping.

Eén detail uit de Ollama-docs dat we pas later op waarde schatten: de daemon bindt standaard aan 127.0.0.1. Laat dat zo. Moet je hem vanaf een andere machine bereiken voor de eval harness, route dan over Tailscale ACL's in plaats van poort 11434 open te zetten naar het kantoor-wifi.

Continue.dev als IDE-bridge

Continue.dev zit het dichtst bij een fatsoenlijke open standaard voor "agent praat met editor." Hij plugt in VS Code, JetBrains en (en dat was voor ons doorslaggevend) hij heeft een echte Xcode-integratie via Apple's editor extension API. De config staat in ~/.continue/config.yaml.

Dit is de studio-config die we uitrolden, klantgevoelige zaken eruit:

models:
  - name: Qwen3 Coder Local
    provider: ollama
    model: qwen3-coder:30b-instruct-q4_K_M
    roles:
      - autocomplete
      - chat
    apiBase: http://127.0.0.1:11434
    defaultCompletionOptions:
      temperature: 0.2
      maxTokens: 1024

  - name: Qwen3 Coder Local Heavy
    provider: ollama
    model: qwen3-coder:72b-instruct-q4_K_M
    roles:
      - chat
      - edit
    apiBase: http://127.0.0.1:11434
    defaultCompletionOptions:
      temperature: 0.1

context:
  - provider: code
  - provider: diff
  - provider: terminal
  - provider: docs
    params:
      sites:
        - https://docs.swift.org
        - https://developer.apple.com/documentation

systemMessage: |
  You write production code for a Dutch product studio.
  Match the conventions of the surrounding file. Do not invent APIs.
  When unsure, return one line of reasoning, then code.

We rolden 'm op dezelfde manier uit als de bootstrap: een launchd-job kopieert bij de eerste login het template naar de home directory van de gebruiker, maar alleen als er nog geen config.yaml staat. Handmatige aanpassingen worden nooit overschreven.

De Xcode-valkuil

Continue.dev in VS Code is een setup van een kwartier. Xcode is de valkuil.

De Xcode-integratie van Continue is een aparte macOS-app die de Editor Extensions API gebruikt. Het eerste wat elke developer met de hand moet doen: Systeeminstellingen openen, dan Inlogonderdelen & Extensies, dan Xcode Source Editor, en de Continue-extensie aanvinken. Dit konden we niet scripten. De TCC-database is signed, en die zonder toestemming van de gebruiker aanpassen is precies wat Apple niet toelaat. Dus bakten we de klik-door in het onboarding-document met een screenshot.

Waarschuwing

Elke macOS point release zet de toggle voor de Xcode source editor extension terug op uit. Pikt je dashboard dat niet op, dan verliest het team stilletjes een week lang Xcode-completion voordat iemand het doorheeft.

Het tweede Xcode-specifieke probleem is latency. Inline completion in Xcode draait op het SourceEditor-protocol, dat niet streamt. Het lokale model moet de hele completion teruggeven voordat Xcode 'm rendert. Met Qwen3-Coder 30B op Q4 op een M3 Pro zit de first-token latency rond de 180 tot 260 milliseconden en landt een completion van dertig regels in ongeveer 700 milliseconden. Dat is acceptabel. Met het 72B-model is dat het niet, en daarom hielden we de 72B op de M4 Max alleen voor chat-mode.

De nightly eval, tegen de eigen monorepo van de studio

Dit is het stuk dat de meeste local-coding-agent guides overslaan, en het stuk waar we het meest trots op zijn.

Elke nacht om 03:00 lokaal draait een launchd-job op elke developer-machine evalctl. Hij doet drie dingen.

Eén: hij checkt een vast referentie-commit van de monorepo uit. Eén SHA, per kwartaal bevroren, zodat de vergelijking elke nacht appels met appels blijft.

Twee: hij draait tien task prompts die door de senior engineers zijn geschreven. Klein, specifiek, met een bekend goed antwoord. Voorbeelden: gegeven deze protobuf, schrijf de Swift-decoder die past bij het bestaande patroon in Sources/Wire/Decoders/. Deze MySQL-migratie mist een down-step, schrijf 'm. Deze React Native screen lekt de subscription op unmount, fix het. Het bekende goede antwoord per task is een diff die een senior engineer met de hand heeft geschreven.

Drie: hij scoort de model-output tegen het bekende goede antwoord op een rubric met vier onderdelen: compileert de code, matcht 'ie de file-conventies (een regex pass), matcht 'ie de structurele vorm van de verwachte diff (een AST-diff via tree-sitter), en slaagt 'ie voor de relevante test in de monorepo-suite.

type Result struct {
    TaskID     string  `json:"task_id"`
    Compiles   bool    `json:"compiles"`
    StyleMatch float64 `json:"style_match"`
    ShapeMatch float64 `json:"shape_match"`
    TestsPass  bool    `json:"tests_pass"`
    DurationMs int64   `json:"duration_ms"`
}

func score(r Result) string {
    if !r.Compiles || !r.TestsPass {
        return "red"
    }
    if r.StyleMatch < 0.7 || r.ShapeMatch < 0.6 {
        return "amber"
    }
    return "green"
}

De hele run schrijft één JSON-bestand per developer per nacht naar /var/abn/localagent/eval/YYYY-MM-DD/<hostname>.json. Een tweede launchd-job om 07:30 rsynct die bestanden naar de intranet-machine. Het dashboard leest ze. Veertien rijen.

Het dashboard dat de oprichter leest

De briefing van Marije was één zin: "ik wil weten, voordat ik mijn tweede koffie drink, of de agent het team sneller of langzamer maakt."

Dus dat is wat het dashboard doet. Elke developer is een rij. Elke rij heeft de kleur van vandaag, de kleur van gisteren, en een sparkline van zeven dagen. Bovenaan staat één getal: de gemiddelde task-duur over de fleet voor de afgelopen zeven nachten, vergeleken met de zeven nachten daarvoor. Groen als 'ie daalde, rood als 'ie steeg.

Geen chat. Geen completion-tellers. Geen token-rekeningen. Geen model card. Eén vraag, één antwoord.

Kernpunt

Kan een niet-technische oprichter de status van je lokale coding agent niet in één oogopslag lezen, dan heb je geen fleet. Dan heb je veertien losse science projects.

Twee weken erin ving het dashboard iets op dat we niet hadden verwacht. Het 72B-model op één M4 Max begon elke nacht oranje terug te geven voor één specifieke developer. Zijn branch hing al drie weken op de verkeerde base, de protobuf-decoders die hij genereerde matchten het nieuwe wire format niet, en niemand had het door. De eval ving het op omdat de rubric tegen de laatste HEAD van de monorepo draaide, niet tegen zijn branch. We hebben gerebased. Het dashboard ging de volgende ochtend groen.

Wat we de eerste keer fout deden

Een kort lijstje, want eerlijkheid komt verder dan opsmuk.

Quantisatie-keuze. We begonnen met Q5_K_M voor alles. Geheugendruk op de 36GB machines duwde swap aan, swap blies de first-token latency over de drie seconden, developers zetten de extensie uit. We zakten naar Q4_K_M. Latency viel terug onder de 300ms. De extensie bleef aan.

Eval drift. De eerste versie van de rubric scoorde stijl op exact-match regex. Twaalf weken erin werd het team het eens over een nieuwe naming convention voor Swift result types. De rubric had die memo niet gehad. Elke machine ging oranje. Nu staat de rubric in de monorepo en gaan wijzigingen via een PR, net als al het andere.

De Xcode-toggle. Elke macOS-update zet de Xcode-extensietoggle terug op uit. We betrapten het één keer per ongeluk. Nu schrijft het bootstrap-script een checksum van de huidige macOS-build naar een bestand, en als die verandert, zet het dashboard een gele banner neer met "macOS bijgewerkt, vink de Continue-extensie opnieuw aan." Die ene banner heeft een hele dag verwarde Slack-berichten gescheeld.

De audit van vijf minuten die je vandaag kunt draaien

Run je een klein team op cloud-hosted coding agents en zit de privacy-rekening eraan te komen, doe dan dit voor de lunch.

  1. Installeer Ollama op de machine van één developer. Pull qwen3-coder:30b-instruct-q4_K_M.
  2. Installeer de Continue.dev-extensie in VS Code. Wijs 'm naar het lokale model met de YAML hierboven.
  3. Pak drie taken uit je laatste sprint die al gemerged zijn. Laat ze door de lokale agent gaan. Vergelijk de diffs met wat er daadwerkelijk geshipped is.

Zitten twee van de drie dicht genoeg dat de developer ze had geshipped, dan heb je je antwoord. Zitten ze er niet dichtbij, dan weet je waartegen je moet evalueren voordat je het seat-contract voor volgend jaar tekent.

Toen we dit afgelopen voorjaar voor de studio in Haarlem bouwden, was het stuk waar we steeds over struikelden die Xcode-toggle die na elke macOS point release omsloeg. We hebben het opgelost met de build-checksum-banner hierboven, en de hele stack ondergebracht in onze AI-agents-praktijk. De scriptnamen in deze post zijn dezelfde die we daadwerkelijk uitrollen.

Kern

Kan een niet-technische oprichter de status van je lokale coding agent niet in één oogopslag lezen, dan heb je geen fleet, dan heb je veertien losse science projects.

FAQ

Welke hardware heb ik nodig om Qwen3-Coder lokaal op een Mac te draaien?

Een Apple Silicon Mac met 36GB unified memory draait de 30B Q4 quant prima voor inline completion. De 72B Q4 vraagt 64GB en is alleen de moeite waard voor chat-mode refactors, niet voor autocomplete.

Heeft dit zin voor een solo-developer, of alleen voor een team?

Solo is de makkelijkere setup, maar het playbook verdient zich pas echt terug op team-schaal, waar privacy, fleet-uitrol en een nightly eval tegen je eigen code zwaarder gaan wegen dan ruwe completion-snelheid.

Waarom Continue.dev in plaats van zelf een VS Code-extensie schrijven?

Continue.dev regelt autocomplete, chat, edit-mode en een Xcode-bridge al. Dat oppervlak opnieuw bouwen om één configbestand uit te sparen is precies het soort yak-shave waar een klein team nooit aan moet beginnen.

Hoe lang duurde de setup nou echt?

Zes weken doorlooptijd, ongeveer vijftien engineer-dagen aan echt werk. Het meeste daarvan ging naar de eval harness en het dashboard, niet naar de Ollama- of Continue.dev-installs.

ai agentstoolingarchitectureworkflowoperations

Iets bouwen?

Start een project