Home/Kennis/Artikel

Artikel · Hoe we bouwen

Van work item naar pull request: zo werkt de productielijn.

Het grootste deel van een backlog is niet klaar om te bouwen, en veel van het werk aan de items die wél klaar zijn, is routine. De SaaS Factory draait een vaste, bewaakte lijn van AI-agents die de backlog sorteert, de code en de tests schrijft, de wijziging op een schone rebuild bewijst en het resultaat door een tweede model laat beoordelen. Elke wijziging komt binnen als een geteste pull request die een van onze engineers beoordeelt en goedkeurt. Dit artikel beschrijft die lijn tot op het niveau waarop een CTO of security officer hem kan beoordelen: de rollen, de sandbox, de bewijsregel, de grenzen en de schakelaars.

Enigmatry engineeringHoe we bouwenGepubliceerd 29 oktober 2026Rotterdam · Belgrado · Novi Sad

Waarom een lijn, en geen chatvenster

Het meeste van wat als AI-ondersteund ontwikkelen wordt verkocht, is een developer met een chatassistent. Dat maakt één persoon sneller en laat al het andere zoals het was: de backlog is nog steeds ongesorteerd, de review hangt nog steeds af van wie er tijd heeft, en niemand kan achteraf zeggen wat de assistent wel en niet heeft gecontroleerd. Het draait bovendien op de laptop van die ene developer, met zijn instellingen, zijn login en zijn rechten. Staat die laptop uit, dan gebeurt er niets.

Wij wilden het omgekeerde: een productielijn met rollen, bewijs, isolatie en een audittrail, die op een schema of op verzoek in de cloud draait onder identiteiten die van het platform zijn en niet van een persoon. Gebouwd voor de .NET- en Azure-applicaties die we al voor klanten bouwen en beheren, op dezelfde stack.

Het idee eronder is simpel. Agents nemen de routinestappen over: het item lezen, de code vinden, een test schrijven, de wijziging maken, elke suite draaien. Mensen houden wat alleen mensen moeten doen: beslissen wat er gebouwd wordt, vragen beantwoorden, en elke merge goedkeuren. De lijn zelf is vaste code met guardrails. Modellen oordelen, ze bepalen niet de route.

01Werk komt binnenUit je backlog, op een schema of op verzoek.
02SorterenEen snel model leest het item en de code: klaar, al opgelost, dubbel, of informatie mist.
03ControlerenEen sterker model toetst elk oordeel voordat er iets gebeurt.
04Eén vraag, als iets mistDe agent stelt zijn vraag één keer en wacht. Een mens antwoordt.
05Klaar om te bouwenAlleen items met een duidelijke scope en een getoetst oordeel gaan door.

Van klaar naar samengevoegd

06Bouwen, test eerstIn een schone sandbox schrijft de agent eerst de falende test, dan de code, en draait elke testsuite.
07BewijzenEen aparte job bouwt de wijziging opnieuw op vanaf nul. De nieuwe tests moeten falen vóór en slagen ná.
08Tweede reviewEen model van een andere leverancier beoordeelt de wijziging. Hooguit twee reparatierondes.
09Pull requestEen geteste wijziging in jouw repository, gekoppeld aan het work item, met het bewijs erbij.
10Een engineer keurt goedEen van onze engineers beoordeelt en voltooit elke merge. Geen agent kan dat.

Bij elke twijfel, fout, onenigheid of overschreden budget stopt de lijn en gaat het item naar een mens. Nooit een retry-lus, nooit een merge.

hier handelt een mens

De rollen, en wat elke rol mag

Elke stap in de lijn is een aparte rol met een eigen opdracht, een eigen model (of geen model) en een eigen identiteit met precies de rechten die de stap nodig heeft. Dat laatste is geen detail: wat de identiteit mag, is wat de agent mag, dus elke rol krijgt zijn eigen, krap afgebakende account.

De sorteerder. Een snel, goedkoop model leest het work item, de commentaren en de relevante code, met alleen leesrechten. Het oordeel: klaar om te bouwen, waarschijnlijk al opgelost, dubbel, informatie mist, of een besluit nodig van een mens.

De controleur. Een sterker model toetst elk oordeel dat tot een actie zou leiden. Pas als beide het eens zijn, krijgt het item het label dat het klaar is. In de praktijk is het snelle model te optimistisch over een deel van zijn "klaar"- en "al gedaan"-oordelen; de controleur vangt die op voordat er iets gebeurt.

De bouwer. Claude Code, in een eigen sandbox, met een identiteit die branches mag pushen, pull requests mag openen en het work item mag bijwerken, en verder niets. Geen rechten om een policy te omzeilen, geen rechten om een pull request te voltooien.

De verifier. Een job zonder model en zonder credentials. Hij bouwt de wijziging opnieuw op vanaf een schone checkout en controleert dat elke nieuwe test faalt zonder de wijziging en slaagt mét de wijziging.

De reviewer. Codex, een model van een andere leverancier, in een eigen korte sandbox met alleen leesrechten. Hij beoordeelt de wijziging tegen de geschreven definition of done en geeft zijn bevindingen terug aan de bouwer.

De gate. Code plus een klein model, dat op basis van de wijziging en het bewijs bepaalt hoe zwaar de menselijke review moet zijn. Wat de gate nooit bepaalt: of er gemerged wordt. Dat doet altijd een mens.

De engineer. Een van onze engineers beoordeelt de pull request, met het bewijs van de verifier en de bevindingen van de reviewer erbij, en voltooit de merge. Of niet.

Eerst sorteren, dan bouwen

Een item komt de lijn binnen vanuit de backlog, via een automatische melding zodra iemand het als geschikt markeert, via een scan die elk uur controleert of er meldingen gemist zijn, op een vast schema, of op verzoek. Elke route levert hetzelfde bericht op in een wachtrij, en een kleine controller pakt het op. Die controller voorkomt dat twee runs aan hetzelfde item werken, maakt de sandbox aan, start de agent met de juiste instructieset, bewaart het resultaat en ruimt op.

Items waar informatie ontbreekt, krijgen één vraag, één keer gesteld, en wachten dan op een mens. Geen herinneringen; een wekelijks overzicht laat zien welke items al langer wachten. Items die dubbel zijn of waarschijnlijk al zijn opgelost, worden gemarkeerd zodat een mens dat kan bevestigen. De agent sluit nooit iets en kiest bij duplicaten nooit zelf welke blijft. Het nuttige bijeffect is een schonere backlog, waar de meeste teams nooit tijd voor vinden.

Labels als stuur

Iedereen in een team kent de labels in zijn backlogtool, dus labels zijn het stuur. Het platform houdt de echte status in zijn eigen database en spiegelt die als één agentlabel per item. Een mens stuurt door labels te veranderen, en de wijziging van een mens wint altijd. Een label "overslaan" betekent: raak dit item nooit aan; lopend werk wordt afgebroken en zijn resultaten verworpen. Een wachtlabel weghalen betekent: sorteer opnieuw. Het item aan iemand toewijzen betekent: annuleer de run. Zelfs het label "klaar" met de hand toevoegen is geen oordeel maar een verzoek om opnieuw te sorteren.

Achter dat stuur zitten generatienummers en vergrendelingen, zodat een trage of herstarte agent nooit over een nieuwere beslissing van een mens heen kan schrijven. Vragen worden één keer gepost, nooit herhaald.

Bouwen, met de test eerst

Alleen items met een duidelijke scope en een getoetst oordeel worden opgepakt, één tegelijk. De bouwer krijgt een schone sandbox: een kleine virtuele machine per run in Azure Container Apps, in onze Azure-omgeving in West-Europa, met Docker erin. Dat laatste is nodig omdat onze integratietests een database in een container starten; gewone container-jobs kunnen dat niet.

De sandbox start van een image dat we zelf bouwen en vastzetten: het Azure-ontwikkelimage van Microsoft met de .NET-SDK, Node en Docker, plus Claude Code op een vaste versie, gecontroleerd tegen het ondertekende releasemanifest van Anthropic, met zelf-updaten en telemetrie uitgeschakeld. Een nieuwe versie van Claude Code betekent een nieuw image met een nieuwe naam, zodat we altijd precies weten wat er heeft gedraaid.

In die sandbox leest de agent het work item en wat erboven hangt, zet het op actief, maakt een branch volgens de conventies van de repository zelf (die staan in de repository, in een bestand dat de agent leest), en schrijft de falende test vóór de wijziging. Daarna bouwt hij, draait hij de unittests, de integratietests en de frontendsuite, pusht hij de branch en opent hij een pull request gekoppeld aan het work item. De agent draait op de achtergrond en de controller vraagt periodiek de status op, dus een lange run hangt niet aan één verbinding. Aan het eind wordt het resultaat bewaard en de sandbox gewist.

Dezelfde runnercode draait voor ontwikkeling op een laptop onder .NET Aspire, in een Linux-container, zodat hij nooit iemands eigen instellingen of login kan oppikken. Wat lokaal werkt, gaat met dezelfde code naar Azure.

Bewijs, geen overeenstemming

Een wijziging telt niet omdat de bouwer zegt dat hij klaar is, en ook niet omdat twee modellen het eens zijn. De verifier, zonder model en zonder credentials, bouwt de wijziging opnieuw op vanaf nul en controleert per acceptatietest dat hij faalt vóór en slaagt ná de wijziging. Dat bewijs hoort bij precies die commit; verandert de code, dan moet het bewijs opnieuw.

Daarna de tweede review. We laten een model van een andere leverancier kijken omdat twee modellen van dezelfde leverancier dezelfde blinde vlekken delen. Codex draait in een eigen korte sandbox met alleen leesrechten, zodat de reviewer en de bouwer nooit credentials delen, en geeft zijn bevindingen terug aan de nog draaiende sandbox van de bouwer. Vindt de review iets, dan krijgt de bouwer hooguit twee reparatierondes. Zijn ze het daarna nog oneens, dan gaat het item naar een mens, niet in een nieuwe lus.

De pull request die een engineer uiteindelijk ziet, heeft dus drie dingen bij zich: het groene bewijs van de verifier voor precies die commit, de bevindingen van de reviewer, en de koppeling naar het work item. Op de repository staan de policies die ook voor mensen gelden: minstens één reviewer die niet de maker is, een gekoppeld work item, en een geslaagde build.

Wat de agent niet kan

De vraag die elke directie stelt: wat als de agent iets doms doet, of iemand plant een instructie in een work item? Het antwoord is lagen die niet afhangen van het gedrag van de agent. Elke laag wordt buiten de agent afgedwongen, dus als er één faalt, houdt de volgende.

Identiteit. De identiteit van de bouwer kan geen policy omzeilen en geen pull request voltooien. Elke rol heeft een eigen sandboxgroep met een eigen identiteit, omdat code in een sandbox het token van zijn eigen groep kan opvragen; dus de rechten van die identiteit zijn de rechten van de agent, en die houden we per rol zo klein mogelijk.

Branchpolicies. Een directe push naar main wordt geweigerd. Op main gelden een minimumaantal reviewers, de stem van de maker telt niet, een gekoppeld work item is verplicht en de build moet slagen.

Netwerk. Dicht, behalve een lijst per rol: de model-API, de repository, de packageregisters. Al het andere wordt geweigerd en gelogd. De agent heeft nooit een API-sleutel: hij stuurt zijn verzoeken met een placeholder en de uitgaande proxy vervangt die door de echte sleutel op weg naar buiten. Code in de sandbox kan die sleutel niet lezen.

Procedure. De tekst van een work item is data, geen instructie. Een geplante opdracht moet als verdachte inhoud terugkomen, niet worden uitgevoerd. Pull requests volgen een vaste vorm, en het label "overslaan" wint altijd.

Budgetten. Elke run heeft een plafond in geld, beurten en tijd, elke fase een dagbudget, en bij de modelleveranciers staan harde bestedingslimieten als laatste vangnet.

Schakelaars. Het hele platform, één fase, één soort schrijfactie of één item kan met één schakelaar stoppen. De bouwfase zet zichzelf uit na een reeks overdrachten aan mensen of na meerdere verlaten pull requests in korte tijd. En het laatste redmiddel is het uitschakelen van de identiteiten zelf.

De standaard bij twijfel is stoppen en overdragen. Fouten, ontbrekend bewijs, onenigheid en opgebruikte budgetten gaan naar een mens, nooit naar een retry-lus en nooit naar een merge.

Elke run gemeten

Elke run schrijft een regel in een grootboek: het item, de rol, de tokens per model, de prijs uit een geversioneerde prijstabel, en de schatting van de tool zelf. Die regels rollen op per item, per samengevoegde pull request en per overdracht aan een mens, en worden maandelijks afgestemd met de facturen van de leveranciers. Zo weten we per work item wat het heeft gekost, iets wat de facturen van de modelleveranciers zelf niet kunnen vertellen. Modeltokens zijn de kostenpost; de rekenkracht van de sandboxes is een fractie daarvan.

Waarom in onze eigen Azure-omgeving

We draaien de lijn op ons eigen Azure, en niet bij een gehoste agentdienst, om vier redenen. Onze repositories staan in Azure DevOps en de lijn moet daar met een eigen identiteit kunnen pushen en pull requests openen. Onze testsuites hebben Docker nodig. We willen een reviewer van een andere leverancier, en dat kan alleen op een platform dat we zelf samenstellen. En het werk blijft in West-Europa, zonder dat er sessietranscripten bij een derde partij worden bewaard. Het kost ons meer beheer: een eigen image, proxyregels, een controller. Dat vinden we de prijs waard.

Wat jij ziet, en hoe je begint

Van buiten is de lijn onzichtbaar. Wat je ziet, is een pull request in je eigen repository: een geteste wijziging, gekoppeld aan het work item, met het bewijs erbij en de goedkeuring van een Enigmatry-engineer erop. Wat je niet ziet, is net zo belangrijk: geen code op iemands laptop, geen secrets in handen van de agent, geen transcripts bij een derde partij, en niemand die merget omdat twee modellen het eens waren.

De lijn is de ene helft van hoe we bouwen; de andere helft is het fundament waar de agents op bouwen, Entry. Samen maken ze de SaaS Factory snel zonder hem slordig te maken. Wat agents bij ons mogen aanraken en wat ze nooit zien, staat ook op de securitypagina, in de taal van je security officer.

Vragen die we hierover krijgen

Merget een agent ooit zelf? Nee. Een engineer voltooit elke merge, en de identiteit van de agent kan het niet eens als hij zou willen.

Ziet een agent productiedata? Nee. Agents werken in dezelfde geanonimiseerde omgevingen als onze developers. Productie is alleen voor beheer, op need-to-know-basis.

Wat als iemand een instructie in een work item zet? De tekst van een item is data. Een instructie erin wordt gemarkeerd als verdachte inhoud en het item gaat naar een mens. En zelfs als dat zou mislukken, houden de identiteit, de branchpolicy en het netwerk.

Kan ik de lijn stoppen? Ja, per item met één label, en aan onze kant per fase, per soort schrijfactie of voor het hele platform. De wijziging van een mens wint altijd.

Welke modellen gebruiken jullie? Een snel model om te sorteren, een sterker model om te controleren en te bouwen, en een model van een andere leverancier om te reviewen. Welke versies precies, verandert met de maanden; de rollen en de grenzen niet.

Hoe we bouwen

Lees het fundament voordat je belt.

Entry is open source onder Apache-2.0. Je architecten kunnen ons vakwerk beoordelen voordat we elkaar gesproken hebben.

Rotterdam · sinds 2006 · ongeveer honderd engineers