Ik heb een systeem gebouwd waarin geen enkele regel code in productie komt zonder dat er een reviewer naar heeft gekeken. Poorten op elke stap. Een bonnetje per handeling. Een audit trail die niet te omzeilen is. Dacht ik.
Begin september ontdekte ik dat er ruim honderd pull requests in productie waren geland zonder dat er ooit een oordeel over was geveld.
Geen enkele poort had gefaald. Ze stonden allemaal groen.
Wat er werkelijk aan de hand was
De keten heeft twee deuren. De eerste laat werk beginnen en schrijft op wat er later beoordeeld moet worden. De tweede laat werk landen en weigert zolang dat oordeel ontbreekt.
Daartussen zit de uitvoering: een agent die code schrijft, een pull request opent, een rapport achterlaat, en een reviewer die daar een oordeel over velt.
Het klinkt sluitend. En elk onderdeel deed precies wat het beloofde. Het probleem zat in de plekken waar ze elkaar de hand moesten geven.
Het eerste lek: een bonnetje zonder nummer
De eerste deur schrijft de verplichting vóórdat de agent begint. Dat is met opzet: je wilt vastleggen dat er iets beoordeeld moet worden, niet pas nadat het al af is.
Maar een agent die een nieuwe pull request maakt, kent dat nummer op dat moment nog niet. Dat ontstaat pas halverwege. Het bonnetje landt dus zonder nummer.
En de tweede deur zoekt op precies dat nummer.
Gemeten over 589 verplichtingen: 285 stonden permanent op "nog niet beoordeeld", niet omdat er geen oordeel was, maar omdat de deur het niet kon vinden. Dertien pull requests met een geldig, geslaagd oordeel kwamen er niet doorheen. De deur weigerde vóórdat hij naar het bewijs keek.
Het bewijs lag er. Het lag alleen in een la die niemand meer kon openen.
Het tweede lek: een redding die nooit vuurde
Er zat al een vangnet in. Als een verplichting geen nummer had, moest het systeem alsnog kijken of er ergens een oordeel op schijf lag dat erbij hoorde. Netjes bedacht, precies voor dit geval.
Dat vangnet had nog nooit gewerkt.
Het zocht op het identificatienummer van de opdracht. Maar een reviewoordeel draagt het nummer van de reviewronde zelf, niet van de opdracht die beoordeeld werd. Twee verschillende dingen, dezelfde veldnaam.
Over 593 verplichtingen was het aantal keren dat dit vangnet iets had gered exact nul. Niet omdat er niets te redden viel: negentien ervan hadden een geslaagd oordeel op schijf liggen, met de juiste commit eraan gebonden.
Het mooiste is dat de code zelf al wist hoe het hoorde. Vijftig regels lager staat in een andere functie letterlijk: reviewresultaten worden opgeslagen per pull request en per poort, nooit per opdracht. Die zin stond er al. Vijftig regels hoger deed de code het andersom.
Het derde lek: een oude fout die nooit meer weggaat
De laatste poort eist dat de tests groen zijn op precies de commit die je gaat mergen. Streng en terecht.
Maar hij eiste dat elke testrun op die commit groen was. En een testhistorie is onveranderlijk.
Stel: je pull request faalt op een bug die ergens anders zit. Iemand repareert die bug. Je draait de tests opnieuw en nu zijn ze groen. De oude rode run staat er nog steeds.
Die pull request komt er nooit meer doorheen. De enige uitweg is de commit veranderen, en dan vervalt het reviewoordeel dat aan die commit hing.
Vier pull requests zaten precies zo vast.
De les eronder
Elk van deze drie defecten heeft dezelfde vorm, en het duurde even voor ik dat zag.
Elke controle toetste een representatie in plaats van het ding zelf.
Het bonnetje bestond, dus er was governance. Het veld heette hetzelfde, dus het ging over hetzelfde. De testrun stond geregistreerd, dus hij zei iets over nu.
Diezelfde dag liep ik er nog een keer in, en toen buiten de code om. Een van mijn eigen scripts controleerde of een werksessie bestond en concludeerde daaruit dat er iets in draaide.
Vijf van de veertien sessies bleken een lege huls: de sessie bestond, er draaide niets meer in. Aankoppelen gaf een leeg scherm.
Bestaan is niet hetzelfde als werken. En dat geldt net zo hard voor een governance-poort als voor een terminalvenster.
Waarom dit voor jou telt, ook als je geen code schrijft
Als je AI-agents inzet die iets doen wat blijvende gevolgen heeft, dan bouw je hier op enig moment een variant van. Een goedkeuringsstap. Een vier-ogen-principe. Een logboek waarin staat wie wat wanneer heeft laten gebeuren.
En dan is dit de vraag die je moet stellen, en het is niet de vraag die de meesten stellen.
De gebruikelijke vraag is: staat er een controle op deze stap? Die kun je afvinken, en dan voelt het geregeld.
De vraag die telt is: wat gebeurt er tussen twee controles, en wie bewaakt dat?
Bij mij was het antwoord dat de eerste poort een bonnetje schreef, de tweede erop zocht, en niemand ooit had gemeten of die twee elkaar konden vinden. Vijf weken lang niet.
Hoe je dit bij jezelf vindt
Drie dingen die ik zelf te laat ben gaan doen.
Tel de uitkomsten, niet de controles. Ik wist dat er poorten waren. Ik wist niet hoe vaak ze een oordeel hadden geveld. Toen ik dat ging tellen, stond de teller op nul. Een controle die nooit aanslaat is niet streng, hij is stuk.
Vraag altijd wat de derde uitkomst is. Geslaagd en afgekeurd zijn er twee. De derde is "niet gemeten", en die is de gevaarlijkste, want zonder eigen categorie glijdt hij vanzelf naar geslaagd. Stilte leest als instemming zodra je hem niet apart benoemt.
Toets de koppelingen, niet de onderdelen. Elk onderdeel afzonderlijk testen geeft je precies het gevoel van veiligheid dat mij vijf weken heeft gekost. De fouten zaten alle drie in de overdracht.
Wat het opleverde
Drie reparaties, alle drie met een test die op de oude code faalt en op de nieuwe slaagt. Niet omdat dat mooi staat in een rapport, maar omdat een test die alleen groen wordt op de nieuwe code niets bewijst.
Het aantal verplichtingen met een echt oordeel eraan ging van 92 naar 127. En dat laatste getal is het enige dat telt, want de rest is administratie.
Wat ik niet heb gedaan is die pull requests alsnog laten beoordelen. Dat kan niet meer: ze zitten in productie, de code is doorgegroeid, een oordeel achteraf is een oordeel over iets anders. Ze staan nu geboekt als "het venster is gesloten en niemand heeft gekeken".
Dat is lelijk om te zien staan. Het is ook precies waarom het er staat.
Lees ook: AI-governance voor het MKB
De uitnodiging
Bouw je aan een agent-opstelling waar governance omheen moet? Stuur me je ketenplaatje, dan wijs ik je de koppelingen aan waar dit soort dingen zich verstopt. Meestal zijn het er twee of drie, en meestal zijn ze in tien minuten aan te wijzen.
Mail: advies@vincentvandeth.nl
Hoe ik dit soort ketens voor klanten toets, lees je op mijn governance-pagina.