Ons werk

Kunstbrein: een nieuwssite die zichzelf vult, met de redactieregels in code

Een Nederlandstalige AI-nieuwssite die elke ochtend vijftig bronnen leest, zelf beslist welk verhaal het waard is, het artikel schrijft met een duidingslaag voor Vlaamse ondernemers en het publiceert zonder dat er iemand op merge klikt. De poorten die een fout artikel tegenhouden staan in code en zijn getest.

Een instructie in een prompt is een verzoek. Een poort is een poort. Daarom staat de kwaliteitscontrole van Kunstbrein in code, niet in de opdracht aan het model.

Uitdaging

Contentmarketing schaalt niet met handwerk, en AI-content die niemand controleert kost je meer autoriteit dan ze oplevert. De vraag was dus niet of AI artikels kan schrijven, want dat kan iedereen aantonen in vijf minuten. De vraag was of je een systeem kan bouwen dat je durft laten publiceren zonder eindredactie, elke dag, zonder dat er ooit iets online komt dat zijn eigen bron tegenspreekt.

Aanpak

Alles wat in code kan, gaat in code. Ophalen, ontdubbelen, clusteren en wegen gebeurt zonder taalmodel, dus die stappen kunnen niets verzinnen en kosten niets. Het model komt alleen aan bod waar oordeel nodig is: het schrijven en de feitencontrole. Daarboven vijf poorten die weigeren in plaats van waarschuwen, 300 tests, en git als enige bron van waarheid voor de content.

50

bronnen die elke ochtend worden opgehaald, ontdubbeld, geclusterd en gewogen, volledig zonder taalmodel

50%

van de geschreven artikels haalt het niet: 22 geschreven, 11 gepubliceerd over 16 gelogde runs

300

tests over de pijplijn, de poorten en de klok, allemaal groen bij het schrijven van deze case

4

automatische taken per dag: ophalen, schrijven, controleren, aanvullen, plus een nieuwsbrief twee keer per week

Vijftig bronnen, elke ochtend, zonder model

Om 07:25 haalt de pijplijn 47 RSS- en Atom-feeds op plus drie pagina's die geen feed aanbieden en dus bevraagd worden. Die vijftig bronnen zijn niet gelijk: 41 mogen content leveren, negen zitten achter een betaalmuur en mogen alleen tippen dat er iets gebeurt. Elke bron heeft ook een rol, van primaire bron over pers en beleid tot duiding en tegenspraak.

Wat daarna gebeurt, gebeurt in code. De feeds worden genormaliseerd, ontdubbeld tegen alles wat al online staat, en geclusterd: hetzelfde verhaal bij vijf uitgevers wordt één kandidaat in plaats van vijf. De weging kijkt naar het aantal onafhankelijke bronnen (logaritmisch, want tien bronnen tellen niet tien keer), naar de verscheidenheid aan rollen (een primaire bron plus duiding plus pers is een echt verhaal, vijf keer hetzelfde persbericht niet), naar de versheid en naar de aanwezigheid van kmo-termen. Puur onderzoeksnieuws krijgt een malus. Die laatste knop kwam er nadat "Midjourney koopt een astrologie-app" kandidaat kon worden.

Dat alles zonder model is geen zuinigheid maar betrouwbaarheid. Wat in code zit kan niets verzinnen, is reproduceerbaar en is te testen. Bij dit volume zou tachtig titels per dag laten ranken door een taalmodel bovendien méér kosten dan het schrijven zelf.

De poorten: kwaliteit staat in code, niet in de prompt

Dit is het hart van het project. Vijf lagen staan tussen een geschreven artikel en de publicatie, allemaal in code en allemaal getest:

  • Letterlijke overlap. Na het schrijven gaat het artikel woord voor woord tegen de brontekst. Elke reeks van acht of meer woorden die letterlijk overeenkomt en niet in een gemarkeerd citaat staat, is een afkeuring. Feiten zijn vrij, formuleringen niet. Eén verfijning kwam uit meting: een kortere reeks mét een cijfer erin is een feitelijke beschrijving en geen plagiaat, want "van 10 gigawatt in het zuiden van Ohio" kan je niet anders zeggen zonder het feit te verliezen. Tien woorden op een rij zonder getal is nooit toeval.
  • Feitencontrole met twee maatstaven. Een samenvattingsbullet is een feit en moet in de bronnen staan. Een duidingsgevolg is een interpretatie en mag per definitie niet in de bronnen staan, want de opdracht vraagt letterlijk om een hoek die de bronnen niet hebben. Die twee over dezelfde kam scheren zou ofwel elke duiding afkeuren, ofwel elke verzonnen feitenclaim doorlaten.
  • Duplicaatpoort. Titeloverlap tegen recent gepubliceerd werk, met een drempel. Op 30 juli keurde die een artikel af omdat het dezelfde inbraak via een AI-agent reconstrueerde als een stuk van vier dagen eerder. Terecht.
  • Entiteiten- en linkcontrole. Verzonnen bron-URL's, uitgevers of namen halen het niet. De frontmatter moet verwijzen naar wat de ophaalstap écht heeft binnengehaald.
  • Lengtes, met een mikpunt en een faalpunt. Het model schrijft naar het plafond toe, dus drie tekens te veel is geen kwaliteitsfout maar kansrekening. Nu is er een limiet die de opdracht vraagt en een harde grens waar de poort afkeurt. Ertussen krijgt het artikel een stijlpunt, en stijl herstelt wel maar keurt nooit af.

De valstrik daaronder is het contentschema. Een poort die ruimer is dan het schema laat een artikel door dat de build breekt, en de pijplijn merget zijn eigen pull request. Daarom leest een test de maxima uit het contentschema en vergelijkt die met de faalpunten in code. Wie ooit een grens verhoogt en het schema vergeet, krijgt een rode test in plaats van een stilliggende site.

De helft wordt afgekeurd, en dat is de bedoeling

Over 16 gelogde schrijfruns zijn er 22 artikels geschreven en 11 gepubliceerd. De afkeuringen: 10 op de feitencontrole, 6 op lengte, 3 op plagiaat, 2 als duplicaat en 1 op een kapot modelantwoord. Die 50% is geen defect, dat is het systeem dat werkt.

De feitencontrole bestaat trouwens omdat er iets doorglipte. De bron schreef dat drie modellen betrokken waren, waaronder een nog niet uitgebracht model. Het artikel maakte daarvan een modelnaam die niemand had aangekondigd. Alle poorten stonden groen, en de body van het artikel had het correct. De fout zat in de compressielaag, in de samenvatting, en dat is precies waar nuance sneuvelt. Sindsdien wordt elke bullet in die laag afzonderlijk tegen de bronnen gelegd.

De site is zijn eigen klok

De pijplijn hing eerst aan de planner van GitHub Actions. Dat is met opzet geen garantie: geplande events komen in een gedeelde wachtrij. Op deze repo gemeten liep de ophaalstap op 26 juli 2 uur en 32 minuten te laat, de schrijfstap 2 uur en 2 minuten, en op 27 en 28 juli draaide er helemaal niets. Uitwijken naar oneven minuten hielp niet, en een wachtpost die aan diezelfde planner hangt is maar half een oplossing.

Sinds 28 juli is de rolverdeling omgekeerd. De Cloudflare Worker die de site al serveert, is nu ook de wekker: zijn cron-triggers starten de pijplijn via een API-call die meteen afvuurt, en GitHub Actions is enkel nog de werkbank. Technisch zat daar een echte hindernis onder, want de Astro-adapter bouwt een entrypoint dat alleen bezoekersverkeer aanneemt en geen plek laat voor een geplande taak. Een eigen entrypoint naast dat van Astro lost dat op, en omdat zo'n ingreep versiegevoelig is, controleert het buildscript alles waarvan het afhangt. Klopt er iets niet, dan faalt de build, en een gefaalde build is een overgeslagen deploy in plaats van een stukke site.

Diezelfde omzetting leverde de duurste les van het project. De nieuwsbriefcron stond op een waarde die in crontab en in GitHub Actions dinsdag en vrijdag betekent. Cloudflare telt de weekdagen anders, dus vertrok de nieuwsbrief een dag te vroeg. Er stónd een test, en die heette "start op dinsdag- en vrijdagochtend", maar hij controleerde of de juiste tekenreeks in de tabel stond. Hij toetste het tikwerk, niet de aanname, terwijl zijn naam suggereerde van wel. Nu staat de weekdagtelling van Cloudflare als tabel in de code en toetsen de tests wat er echt uitkomt. Een test die zijn eigen titel niet waarmaakt is erger dan geen test.

Git is de databank van de content

Er is geen CMS. Een artikel bestaat pas na een commit, en dat klinkt als een beperking maar is een voordeel: elk artikel heeft een diff, een geschiedenis en een rollback, de sitemap krijgt een echte lastmod per pagina, en er is geen databank die stil kan corrumperen. Het ontdubbelen gebeurt tegen de bron-URL's in wat al gepubliceerd is, dus er is ook geen aparte statustabel die uit sync kan lopen met de werkelijkheid.

De site zelf is Astro 7 op Cloudflare Workers, met alle artikels, rubrieken en dossiers geprerenderd: geen wachttijd en geen databankcall om een artikel te lezen. D1 doet alleen wat écht state is, zoals de nieuwsbrieflijst, de hitteller voor "meest gelezen" en de alarmen. Zoeken loopt via Pagefind, zonder externe zoekdienst. Het OG-beeld per artikel wordt in de postbuild getekend met de rubriek en de kop erin. Fonts staan lokaal, de CSP is afdwingend, GA4 laadt pas na toestemming en de admin zit achter Cloudflare Access met echte tokenvalidatie: kloppen de sleutels niet meer, dan valt die admin dicht en niet open.

De duidingslaag is de eigen bijdrage

Een AI-antwoord verwijst niet naar een site die hetzelfde zegt als vijf andere. Daarom is de kern van elk Kunstbrein-artikel niet het nieuws maar de duiding: drie tot vier concrete profielen, zoals een kmo met een chatbot bij een externe leverancier of een marketeer die AI-abonnementen op prijs vergelijkt, met bij elk profiel het gevolg. Dat is de tekst die nergens anders staat, en dus het enige stuk waarvoor een AI-antwoord naar Kunstbrein moet verwijzen.

Daarrond staat de verantwoording die AI-antwoorden belonen. Elk artikel toont zijn bronnen met naam, titel, tijdstip en link, zonder nofollow, want een bronvermelding is het tegenovergestelde van een betaalde link. Elk artikel zegt zelf dat het met AI-assistentie is samengesteld, onder redactie van Frederiek Pascal. Het redactiestatuut is een echte pagina en is tegelijk het doelwit van publishingPrinciples in de schema.org-graaf. Elk artikel is een NewsArticle waarvan de citaten opgebouwd zijn uit het verplichte bronnenveld: geen bron is geen artikel, afgedwongen in het schema. Daarbovenop een bewust AI-crawlerbeleid, een llms.txt en een sitemap met echte lastmod per pagina.

Wat het kost

Eén tot drie dollar per maand aan modelkosten. Dat lage cijfer komt niet uit een goedkoop model maar uit de architectuur: het model doet alleen het schrijven en de feitencontrole, en al het rest gebeurt in code. Alles loopt via de Message Batches API aan de helft van de tokenprijs, want niemand zit om tien over acht 's ochtends op het antwoord te wachten. De flow werkt in rondes: eerst alle hoofdvragen als één batch, dan de poorten in code, dan alle herstelvragen samen als volgende batch. Een batch die na drie kwartier niet klaar is, wordt geannuleerd en de run faalt veilig: geen artikel, geen pull request, morgen opnieuw.

Die kosten worden ook echt gemeten, niet geschat: een script haalt het verbruik en de kosten bij de leverancier zelf op. De eigen optelsom uit de logs telde namelijk alleen de artikels die gepubliceerd raakten, en een afgekeurd artikel kost precies even veel tokens. Verder: Cloudflare Workers, D1 en e-mailrouting binnen het bestaande plan, en Resend voor de verzending. Geen CMS-licentie, geen zoekdienst, geen beeldbank.

Wat we niet claimen

Een case die alleen de mooie kant toont, is een reclamefolder. Dus:

  • Dit is geen "AI schrijft, mens controleert". Er is géén eindredactie vóór publicatie. De poorten staan vóór de merge, de kwaliteitsbewaking achteraf gebeurt in de admin, waar een artikel gearchiveerd kan worden of feedback een herwerking kan starten. Dat is een bewuste keuze.
  • Geen verkeers- of rankingclaims. De metingen staan pas aan sinds de lancering. Het ijkpunt ligt op drie en zes maanden: verschijnt Kunstbrein in AI-antwoorden op vragen van de doelgroep, en komt er verkeer naar onze eigen AI-pagina's. Zo niet, dan stopt het, met een archief dat blijft staan.
  • De oudste artikels zijn opbouwmateriaal uit de fase vóór de pijplijn live ging, en die zijn korter. Cijfers over gemiddelde artikellengte splitsen we dus altijd op die datum, anders meet je twee dingen tegelijk.

Vijftig bronnen, elke ochtend

Ophalen, ontdubbelen, clusteren en wegen gebeurt zonder taalmodel, dus het kan niets verzinnen en het kost niets.

De helft wordt afgekeurd

22 artikels geschreven, 11 gepubliceerd. Een artikel dat zijn eigen bron tegenspreekt of letterlijk overschrijft, komt er niet in.

De site is zijn eigen klok

Dezelfde Cloudflare Worker die de pagina's serveert, start de pijplijn, nadat de planner van GitHub drie dagen op rij oversloeg.

Gebruikte tools

Claude Code (bouwen met AI)
Claude Sonnet via de Message Batches API
Astro 7, geprerenderd, geen CMS
Cloudflare Workers, D1 en cron-triggers
GitHub Actions als werkbank, pull requests als publicatieweg
Pagefind voor zoeken, satori en resvg voor de OG-beelden
Resend voor de nieuwsbrief, met dubbele opt-in in eigen beheer

Project in beeld

Jouw project als volgende case?

We werken met Vlaamse KMO's die willen groeien.

Via Shopify, performance marketing en AI.

Klantenervaringen

Ervaringen van klanten

Waarom ondernemers graag met ClickForest blijven werken.

Uitstekend op Google5.0★★★★★gebaseerd op 23 beoordelingen