AI Automation
de engine
Over een AI-systeem dat op mijn eigen LinkedIn-account handelt, en waarom de vangrails in code zitten en niet in goede bedoelingen
De meeste AI-demo’s sterven als pilot. Niet omdat de techniek tekortschiet, maar omdat niemand het aandurft om het systeem echt iets te laten doen. De demo draait in een sandbox, iedereen knikt, en daarna gaat er een mens tussen elke actie zitten. Dat mens wordt de bottleneck, de business case verdampt, en zes maanden later heet het project “on hold”.
Ik heb het omgedraaid. Ik bouwde in mijn eentje een LinkedIn-engine die 24 uur per dag in productie draait, op mijn eigen account. Niet op een testaccount, niet achter een goedkeuringsknop. Als iemand onder een post van mij reageert met een triggerwoord, schrijft Claude een persoonlijk bericht, en de engine verstuurt dat automatisch, met de beloofde PDF-gids als bijlage. Er kijkt geen mens meer tussen.
Dat klinkt roekeloos. Het tegendeel is waar, en dat is precies het punt van dit stuk.
wat er draait
De stack is bewust saai. Python met Playwright voor de browser-acties, Supabase als datalaag, Make voor de orchestratie, de Claude API voor het schrijven, en een Hetzner-VPS van zo’n vijf euro per maand met systemd als procesmanager. Geen Docker, geen Kubernetes, geen framework van de maand.
De architectuur heeft één harde regel: de AI-logica weet niets van LinkedIn, en de LinkedIn-acties weten niets van Claude. De browser-acties zitten in een herbruikbare library van atomaire functies. De productiescripts zijn dom: importeren, loopen, loggen. Alle intelligentie zit op één plek, alle uitvoering op een andere. Dat maakt het systeem testbaar, en het maakt de volgende modules mogelijk zonder verbouwing.
kleppen, geen vertrouwen
Het systeem mag automatisch versturen omdat ik het niet vertrouw. Dat is geen tegenstrijdigheid, dat is de ontwerpkeuze.
Elke actie gaat door kleppen die in code zitten, niet in een afspraak met mezelf. Een activity window: acties alleen tussen 07:00 en 23:00 Nederlandse tijd. Een daily cap op verstuurde berichten, hard afgedwongen in het script zelf, niet alleen in de orchestratielaag. Een validatie-gate die elke gegenereerde tekst controleert op lengte, taal, personalisatie en opmaak voordat er iets de deur uit mag. Een check of de beloofde PDF daadwerkelijk bestaat. Random vertragingen tussen elke actie, drie tot acht seconden tussen page loads, minimaal drie minuten tussen berichten. En een dry-run-modus die standaard aan staat, zodat versturen een expliciete beslissing is.
Het verschil met de gemiddelde pilot: daar zit de veiligheid in een mens die meekijkt. Hier zit de veiligheid in code die niet moe wordt, niet afgeleid raakt en geen haast heeft. Een mens als vangrail schaalt niet. Een if-statement wel.
de avond van 21 mei
En toch ging het mis. Op 21 mei zag ik in de live logs hoe de engine dezelfde commenter voor de derde keer een publieke reply stuurde. En daarna nog een keer.
De oorzaak was een ontwerpfout die er in review volstrekt redelijk uitzag. Om te voorkomen dat een comment twee keer verwerkt wordt, had elke comment een dedup-sleutel nodig. Die sleutel was een hash van de gescrapete comment-tekst. Klinkt logisch: zelfde tekst, zelfde comment.
Alleen: de gescrapete tekst bevatte ook de zichtbare reply- en reactietellers. “WIKI 0” werd “WIKI 0 1 1” zodra iemand reageerde. En wie reageerde er? Mijn eigen engine. De auto-reply verhoogde de teller, de teller veranderde de tekst, de tekst veranderde de sleutel, en voor het systeem was het opeens een nieuwe comment. Die verdiende een reply. Die de teller weer verhoogde.
Een lus die zichzelf voedde, met mijn naam eronder, publiek zichtbaar op LinkedIn. Ik ving het alleen omdat ik die avond toevallig live meekeek. De DM-kant liep niet vast, en dat is het leerzaamste detail: een DM versturen verandert niets aan de comment waar hij op gebaseerd is. De reply wel. De actie die zijn eigen invoer muteert, dat is de val.
sleutels die niet mogen bewegen
De fix was klein en het principe is groot. De dedup-sleutel hangt nu aan post plus auteur, twee dingen die de engine zelf nooit kan veranderen. En er kwam een extra klep bij: een harde dagelijkse cap op replies, zodat zelfs een lus die ik niet zie vanzelf tegen een muur loopt in plaats van tegen mijn reputatie.
Dit is de les die geen enkele demo je leert. Een demo hoeft één keer te werken terwijl iedereen kijkt. Een productiesysteem moet zich gedragen terwijl niemand kijkt, ook wanneer het zijn eigen omgeving verandert en die veranderde omgeving weer als input terugkrijgt. Idempotentie is in een slidedeck een saai woord. In productie is het het verschil tussen een systeem en een incident.
Eerlijk is eerlijk over waar dit staat: het draait, maar het is niet af. Dit is module één van drie, en LinkedIn heeft me onderweg een waarschuwing gegeven over third-party tooling. Mijn antwoord was niet stoppen maar terugschroeven: de volumes staan nu op vijf berichten per dag, een fractie van wat het systeem aankan. Dat is geen nederlaag, dat is dezelfde filosofie. De klep zit in code, dus terugdraaien is één regel configuratie.
Wat ik hieraan heb overgehouden is een simpele toets voor elk automatiseringsplan, van mij of van een klant. Niet: wat kan het systeem? Maar: wat gebeurt er als het systeem zijn eigen sporen tegenkomt, en welke regel code stopt het dan? Wie die vraag niet kan beantwoorden heeft geen productiesysteem. Die heeft een demo die nog niet heeft mogen falen.