deSchouwVloot · het overkoepelende project
Losse projecten, één engineeringstandaard
Elk project dat ik bouw heeft z'n eigen repo, eigen issues, eigen architectuur en eigen workflow, en is los van de rest te ontwikkelen. deSchouwVloot is de laag daarboven: het legt vast hoe er gebouwd, gecontroleerd en vrijgegeven wordt — dezelfde poorten, dezelfde checks, dezelfde drie plekken waar ik zelf beslis, en AI-agents die daarbinnen het grootste deel van het werk doen. Er draaien inmiddels vier repo's op, zonder dat er één regel gedeelde logica is geforkt; deze site is er zelf één van. Elke bewering hieronder is na te trekken in de publieke repo.
Kies een pad om verder te lezen
Of sla het verhaal over en lees de ontwerpkeuzes — de controles, de incidenten en de nulmeting.
De kernzinnen staan er sowieso; deze keuze voegt per stap alleen meer laag toe.
Hoe het op elkaar staat
- BiohackOS
Flutter-app, privérepo, eigen constitution.md en een eigen testset waar de checks op leunen.
eigen repoeigen issueseigen workflow - koenholman.nl
Deze site. Andere taal, andere stack, geen eigen server — en toch dezelfde poort en dezelfde checks.
eigen repoeigen issueseigen workflow - Een privéproject
Privé, en van een heel andere vorm dan waarvoor de vloot ooit gebouwd is. Aangehaakt zonder uitzondering in het contract.
eigen repoeigen issueseigen workflow
01 · In het project
Een issue begint waar het thuishoort
Werk ontstaat in de repo van het project dat het raakt: daar staat de code, daar staan de grenzen van dat project, en daar is te beoordelen of een idee er überhaupt in past. Eén zin is genoeg om te beginnen — uitgebreider mag ook — en wat er daarna mee gebeurt, gebeurt in die repo, langs de stations die het project zelf aanroept.
achtergrondHet issue leeft in de repo van het project zelf en wordt daar behandeld met de .fleet.yml van dát project: eigen lanes, eigen poorten, eigen labelnamen, eigen budgetten. De gedeelde workflows kennen die waarden niet — ze krijgen ze binnen als input van de aanroeper.
als het misgaatElk project draagt in diezelfde .fleet.yml een spine-lijst: de stations waarvan uitval stil zou zijn. Valt er daar één om, dan wordt dat in dat project gemeld in plaats van afgewacht.
02 · De gedeelde standaard
Zelfstandig, maar niet apart geregeld
Een project houdt alles wat het eigen maakt in eigen hand: eigen doelen, eigen grenzen in constitution.md, eigen tests, eigen releasecadans. Wat het deelt is de vorm eromheen — per station een caller van een paar regels naar de gedeelde workflows, en verder niets. Zo draait BiohackOS op dezelfde standaard als deze site, zonder dat het ene project iets van het andere hoeft te weten. Een nieuw project haakt op dezelfde manier aan, en deSchouwVloot verandert daar niet voor.
achtergrondHet dragende mechanisme is dat een reusable workflow draait in de context van de aanroeper: runs-on resolvet tegen de runnerpool van de aanroepende repo, github.repository is de aanroepende repo, en secrets: inherit geeft diens secrets door. De vloot levert de logica, het project levert de hardware en de secrets. Het contract tussen beide is één bestand: .fleet.yml — lanes, poorten, budgetten, labelnamen en de spine.
als het misgaatMist een project een script dat een station wél eist, dan draait dat station fail-closed en merget het per constructie nooit meer — dat was incident I28. De eis wordt nu uit de stationsdefinitie zelf afgeleid in plaats van uit een handlijst die achterloopt.
vier repo's · nul forks
Er draaien inmiddels vier repo's op de vloot. Twee daarvan hebben een compleet andere vorm dan het project waarvoor ze ooit gebouwd is — andere taal, andere stack, geen eigen server, alleen door GitHub geleverde machines. Voor geen van beide is één regel gedeelde logica gekopieerd. Dat is de echte toets: niet of een contract denkbaar generiek is, maar of een project dat er niet op ontworpen is er zonder uitzonderingen in past.
zijingang · niet de hoofdweg
Soms heb ik alleen een zin en nog geen plek. Daar is één poort voor, die er een bestemming bij zoekt en het issue daarnaartoe verplaatst — en weet hij het niet zeker, dan kiest hij niet maar vraagt hij het. Het meeste werk gaat daar niet langs: dat begint gewoon in de repo waar het thuishoort.
Lukt het niet om bij elke bestemming het genoemde pad op te halen, dan wegen paden niet mee en gedraagt de poort zich exact als voorheen. Een scherpere afstelling die stilletjes half werkt, is erger dan de botte versie.
achtergrondintake.yml is workflow_call-only, net als alle zestien workflows; een guard-script bewijst dat bij elke PR, zodat de poort nooit vanzelf kan starten. intake-decide.sh telt hele woorden uit routing.yml per consument, en weegt een genoemd bestandspad vijf keer zo zwaar als een trefwoord — een pad zegt iets over de plááts, een woord alleen over het onderwerp. Strikt de meeste punten wint; de winnaar krijgt het issue via een transfer, niet via een kopie.
als het misgaatGelijkspel of nul punten → label needs-routing en het issue blijft staan met een vraag. Die grensgevallen liggen vast in de golden-set: 08-grensgeval-domein-wint, 09-grensgeval-infra-wint, 10-gelijkspel-blijft-routing.
03 · Triage, plan, bouwen
De agent werkt het zelf uit
Triage bepaalt wat voor werk het is; plannen toetst het aan mijn vastgelegde grenzen en bakent de scope af voordat er iets gebouwd wordt. Bouwen voert dat plan daarna uit op een eigen machine, met een limiet — loopt de agent vast, dan stopt hij vanzelf.
achtergrondDe build draait op de self-hosted lane biohack-agent in een ephemeral container zonder docker-daemon, met een turn-budget van 80 en claude-sonnet-5 als model; escalatie gaat naar claude-opus-5. Concurrency-groepen staan op het issue.
als het misgaatRunner offline? De lane valt terug op ubuntu-latest, en runner-fleet-assert.yml meldt het. Turn-budget op? De agent stopt en escaleert naar needs-human in plaats van door te blijven draaien.
plan-critic · scope en criteria
epic-orchestrator.yml houdt de reeks vast: fase-issues met een vaste volgorde, en alleen de eerste staat open. plan-critic.sh heeft het plan al mechanisch getoetst op concrete stappen en op een genoemde verificatie.
als het misgaatVerwerpt de criticus het plan, dan wordt er niets gebouwd — een fout plan is het duurste faalpad, want je betaalt build, review en herstel op het verkeerde fundament. Twijfelgevallen komen er als bevinding uit, niet als afwijzing.
wel de Action, niet de standaardinstellingen
De bouwstap draait op de kant-en-klare Claude-Action — daar valt weinig aan te verbeteren. Wat het verschil maakt is alles eromheen, en dat is hier allemaal met de hand gezet: een turn-budget van 80 in plaats van ruim, een modelkeuze die uit het issuetype volgt, een concurrency-groep op het issue in plaats van op de run, en een allowlist die per commando is opgeschreven.
Die allowlist is de grootste knop. Ontbreekt er één commando, dan probeert de agent een omweg, krijgt weer een weigering, probeert iets anders — tot het budget op is. Eerder liep er zo een issue twee keer vast op 151 turns met 22 weigeringen en niets bruikbaars. Daarom telt de pijplijn die weigeringen nu achteraf: boven de drempel komt er vanzelf een melding dat er een regel ontbreekt, in plaats van dat het pas bij de derde dure run opvalt.
ontwerp · nog niet gebouwd
Bouwen gaat nu uitsluitend autonoom. Zelf meebouwen in een interactieve sessie werkt merkbaar prettiger dan diezelfde stap op een runner, dus er ligt een ontwerp om die bouwstap ook vanuit een chatsessie te kunnen oppakken. Additief, niet vervangend: de autonome route blijft de standaard voor achtergrondwerk, de tweede ingang is er alleen voor de gevallen waarin ik zelf meedoe. Allebei door dezelfde poort, dezelfde claim en dezelfde bot-identiteit — geen van beide krijgt een kortere weg.
Die claim bestaat trouwens al wél: vlak voor de agent begint leest de workflow de labels opnieuw, en draagt het issue er één dat zegt dat een sessie het al opgepakt heeft, dan stopt hij. Het label uit de gebeurtenis zelf is een momentopname; die her-check krimpt het venster waarin ze elkaar in de weg kunnen zitten tot vrijwel nul.
eigendom van kennis, niet van de pijplijn
Naast constitution.md en doelen.md brengt een project vaak ook zijn eigen retrieval mee: RAG die antwoorden ophaalt uit de eigen documentatie of logs, en een eigen MCP-server die de agent gestructureerde toegang geeft tot de tools en data van dat project. deSchouwVloot zelf blijft daarbuiten — het bevat geen domeinlogica, dus die kennis hoort bij het project, niet bij de machinerie eromheen.
Bij één van die projecten staat dat inmiddels ook echt te draaien: vóórdat de agent aan een plan of build begint, doet een los leesscript één zoekopdracht per kennistabel — bekende faalpatronen plus de relevante documentatie — en injecteert alleen de beste treffers, in plaats van hele documenten opnieuw in te lezen bij elke sessie zonder geheugen. Faalt die zoekopdracht, dan bouwt de agent gewoon door zonder die context: de kennisbron mag nooit een harde afhankelijkheid worden.
04 · Checks
Zeven bewakingen moeten groen zijn
Zeven bewakingen moeten groen zijn voordat er iets verdergaat. De tests draaien over de volledige rekenkern van het project, niet alleen over het ene stukje dat net veranderde. Menselijke review blijft het inhoudelijke oordeel; alleen de tests en de deterministische checks kunnen zelf tegenhouden.
achtergrondpr-check.yml draait zes jobs met concurrency op het PR-nummer: diff-classificatie, conventional-commit-titel, flutter analyze en flutter test (beide blokkerend), coverage (rapporteert), preview-APK, en eslint op de webkant. doctor.yml en gitflow-doctor.yml bewaken de invarianten; de doctor rapporteert hard en muteert nooit.
als het misgaatRood? De PR blokkeert, de pijplijn repareert zichzelf en draait opnieuw; blijft het rood, dan escaleert het. Loopt cockpit/project-digest.md uit de pas met de registry, dan blokkeert de digest-guard — die plattegrond werd handmatig bijgehouden tot hij drift ging vertonen, en is nu gegenereerd en CI-bewaakt.
impactanalyse · van vinkje naar bewijs
Elke feature-PR draagt een klein codeblok: db=geen, net=bestaand, risico=laag — twaalf velden, elk met een gesloten antwoordlijst. Dat verving een checklist van tien vinkjes die alleen toetste of ze waren aangevinkt, niet of ze ook klopten. Nu haalt de check de daadwerkelijke bestandenlijst van de PR op en houdt elk antwoord ertegen: claim je db=geen terwijl er een migratie in de diff zit, dan is dat aantoonbaar onwaar — en gaat de PR rood, zonder dat ik ernaar hoef te kijken.
De zeven controles zelf, de nulmeting eronder en de vier keren dat alles groen stond terwijl het stuk was: de ontwerpkeuzes, met een bron per bewering.
05 · Mens beslist
Precies drie soorten plekken
Ik grijp maar op drie momenten zelf in: bij de merge van elke fase, bij de release, en wanneer een agent escaleert omdat hij er zelf niet uitkomt. Verder nergens — al het andere gaat vanzelf door zodra het groen staat.
mergeauto-merge.yml kijkt naar gates.feature_approval in .fleet.yml: op true wacht een feature op approval, de rest mergt door op groen. branch-protection-assert.yml controleert dat de beveiliging op de branch ook echt aanstaat.
als het misgaatOntstaat er een merge-conflict, dan lost de pijplijn dat zelf op en draait opnieuw. Lukt dat niet, dan is needs-human de derde en laatste poort.
releaseissue-release.yml en promote-release.yml leveren pas als alle fase-issues gesloten zijn. release_environment: production betekent een GitHub-omgeving met required reviewer, dus de knop zit in het platform en niet in een script.
als het misgaatFaalt een release halverwege, dan blijft de epic open en gaat er niets half naar buiten. Eén release voor het geheel, of geen.
Een plattegrond van de codebase werd vroeger met de hand bijgehouden en dreef stil uit de pas met de code. Die is nu gegenereerd en CI-bewaakt: documentatie die scheef staat, houdt de build tegen — een digest-guard, geen vertrouwen op de hand.
Een van de projecten die op de vloot draait heeft al een stevige eigen testset — precies het soort dekking waar de checks op leunen, en waar nieuwe projecten naartoe groeien zodra ze aanhaken.
Daarachter draait meer zonder dat ik het zie: een watchdog die vastgelopen runs signaleert, een autofix- en conflict-solver-stap die een rode PR eerst zelf probeert te herstellen vóór er geëscaleerd wordt, de epic-orchestrator die bijhoudt welke fase van een epic open mag staan, en een branch-janitor die wekelijks gemergede branches opruimt — via de PR-historie gecontroleerd, niet via een git-ancestor-check, want deze repo's squash-mergen. Zelf-herstel is de norm, een escalatie naar mij de uitzondering.
Één project gaat nog een stap verder: opt-in, standaard uit, kan het zelf een issue aanmaken op mijn eigen repo met een gecureerde dagelijkse digest — health-checks, nieuwe momentopnames, loggaten-hypotheses. Zo'n issue legt daarna precies dezelfde weg af als elk ander idee: triage, plannen, bouwen, checks, en mijn goedkeuring op de merge.
06 · Live
Elk project gaat zelf naar buiten
Wat er buiten staat is nooit half: pas als een epic in zijn geheel binnen is, gaat het naar buiten, met mijn goedkeuring op de release-knop van dát project. Elk project dat aanhaakt houdt zijn eigen doelen, zijn eigen grondwet in constitution.md, zijn eigen aanvullingen op de tests en zijn eigen releasemoment — en deelt alleen de vorm: dezelfde poorten, dezelfde checks, dezelfde drie plekken waar ik het stuur in handen houd. Dat is wat deSchouwVloot laat zien: zelfstandige projecten die tóch als één geheel te bouwen en te beheren zijn.