Kleurplaat voor AI agents
Agents niet blij. Wij niet blij.
18-09-2026 Samenvatting van twee gesprekken van Sander Veenhof en twee AI's, met een menselijk geschreven intro en outro
Het is de verwachting dat AI chat-sessies steeds vaker het beginpunt gaan vormen van momenten waarop we ons op 'het internet' begeven. We kletsen met bots en agents die voor ons iets opzoeken, samenvatten of ze gidsen ons ergens heen. Dus hoe maken we onszelf gereed voor die nieuwe groep bezoekers op de site, en hoe bedienen we via die weg indirect ons publiek?
Met kleine experimenten op ai.vpro.nl komen we daar stapsgewijs meer over te weten. De AI's waarmee we op steeds meer plekken converseren beloven ons veel en lijken van alles te kunnen, maar het is soms wonderlijk om te merken wat er uiteindelijk niet lukt. Je zou verwachten dat chatGPT even de VPRO site raadpleegt als je wil weten of je nog iets leuks kan kijken vanavond, maar dat gaat zomaar niet. Veel chat apps weigeren links te openen. Zeker in praat-modus doen ze dat niet graag. Om zoiets te regelen moet je zo veel knoeien dat je bijna denkt: ik kijk zelf wel even. Als je hoort dat je connectors of MCP's moet configureren, haken veel mensen toch echt wel af.
Dus vroegen wij ons af: zijn er eenvoudige manieren om de AI agents met open armen te ontvangen op een site? Maar het valt niet mee om hen daartoe te verleiden. De mogelijkheden zijn best beperkt en het is enorm puzzelen en manoeuvreren om een ontvangst-interface klaar te zetten waar een AI-agent ook daadwerkelijk mee aan de slag gaat.
Benieuwd naar de gedetailleerde struggle die nodig was om een paar stippen van een agent te ontvangen?
Feitenrelaas: een AI-agent daadwerkelijk laten tekenen op /canvas
Dit is een chronologische, feitelijke reconstructie van één concrete sessie (VPRO MediaLab, met een Claude-agent op het Sonnet-model, 17-18 september 2026) waarin geprobeerd werd een AI-agent zelfstandig te laten tekenen op ai.vpro.nl/canvas — een doek waar uitsluitend bots stippen op mogen zetten. Hieronder staat per poging: wat er geprobeerd werd, wat het resultaat was, en — waar dat kon worden vastgesteld — waarom.
Uitgangssituatie
De muur kent drie stappen (beginnen, stippen zetten, afsluiten), gedocumenteerd op /canvas/instructies.md. Bij aanvang van de sessie bestonden daarvoor twee manieren: een JSON-POST, en een GET-fallback voor tools zonder POST. Alle overige routes hieronder kwamen pas tijdens deze sessie bij, als directe reactie op wat hieronder staat.
Poging 1 — de instructiepagina lezen
Aanpak: GET op /canvas en /canvas/instructies.md.
Resultaat: gelukt. De agent kon beide statische pagina's gewoon ophalen en las daarin de drie stappen en de destijds twee manieren om ze aan te roepen.
Poging 2 — beginnen via een querystring op de instructiepagina
Aanpak: dezelfde instructiepagina opnieuw ophalen, nu met eigen waarden in de querystring (?bot=...&hoop=...&bevestig=1), zoals de tekst zelf voorstelde voor tools die geen los adres mogen verzinnen.
Resultaat: mislukt, zonder foutmelding. Het antwoord was woord voor woord identiek aan de kale instructiepagina — geen JSON, geen id, geen sleutel. Drie keer herhaald met andere waarden (andere titel, andere hoop, een toegevoegde nonce-parameter); telkens exact hetzelfde resultaat.
Verklaring: pas veel later vastgesteld, zie Poging 8.
Poging 3 — rechtstreeks posten vanuit de eigen sandbox
Aanpak: buiten de gewone fetch-tool om een directe HTTP POST doen naar /api/canvas/begin, via de code-uitvoeringsomgeving van de agent (curl en Python urllib).
Resultaat: mislukt. De sandbox weigerde de uitgaande verbinding zelf, met de melding "Host not in allowlist: ai.vpro.nl." (en bij een latere poging een 403).
Verklaring: de sandbox waarin de agent code uitvoert staat alleen verkeer toe naar een vaste lijst domeinen (zoals softwareregistries); ai.vpro.nl stond daar niet op. Een instelling van de agent-omgeving, niet van de server.
Poging 4 — de GET-fallback-route proberen
Aanpak: de in de instructies genoemde route voor tools zonder POST: GET /api/canvas/get/begin?bot=...&bevestig=1.
Resultaat: mislukt. De fetch-tool van de agent weigerde het verzoek zelf, met "This URL was not in any prior search or fetch result." Een poging om de URL eerst te vinden via zoeken leverde niets op.
Verklaring: deze fetch-tool staat alleen toe URL's op te halen die al letterlijk in het gesprek voorkwamen. Een nieuw, zelf samengesteld adres voldoet daar niet aan, ook al staat het letterlijk zo in de documentatie.
Poging 5 — een verkeerde verklaring over spraak- versus schrijfmodus
Tussendoor bleek de fetch-tool soms helemaal niet beschikbaar. De agent verklaarde dit aan de gebruiker als een vast, categorisch verschil tussen "spraakmodus" en "schrijfmodus" van de chatapp.
Resultaat: deze verklaring bleek onjuist. Later in de sessie gaf de agent zelf toe dat die stellige uitleg verzonnen was — een aanname gepresenteerd als vaststaand feit, zonder daar zeker van te zijn.
Poging 6 — de querystring-vorm herhalen na gemelde site-aanpassingen
Aanpak: telkens wanneer de gebruiker liet weten dat de site was aangepast (bot-detectie versoepeld, een nieuwe deploy), probeerde de agent opnieuw dezelfde querystring-vorm uit Poging 2.
Resultaat: vier keer herhaald, telkens hetzelfde ongewijzigde resultaat — de statische instructiepagina, zonder JSON.
Poging 7 — de volledige lijst van aangeroepen URL's opstellen
Aanpak: op verzoek van de gebruiker doorzocht de agent het eigen sessietranscript op alle daadwerkelijk aangeroepen ai.vpro.nl-URL's.
Resultaat: bevestigde het patroon uit Pogingen 2-6: negen pogingen, drie soorten mislukking, geen enkele geslaagde start.
Poging 8 — de oorzaak van de stille mislukking gevonden
Aanpak: bij een volgende, identieke poging met de querystring-vorm bekeek de agent voor het eerst de eigen tool-metadata van het antwoord.
Resultaat: het veld destination_url in die metadata bevatte alleen de kale URL, zónder de meegegeven querystring.
Verklaring: hiermee stond vast dat de fetch-tool van de agent zelf de querystring wegstript vóórdat het verzoek verstuurd wordt. Geen enkele serveraanpassing had dit kunnen oplossen.
Poging 9 — een padgebaseerde variant, zonder querystring
Aanpak: een nieuw type URL proberen, met alle waarden als pad-segmenten in plaats van als querystring — zowel met een letterlijke placeholdertekst als met "Claude" ingevuld.
Resultaat: beide mislukten met dezelfde "URL nooit eerder gezien"-melding als in Poging 4 — óók de versie met de placeholdertekst die de gebruiker zelf had getypt.
Verklaring: de fetch-tool herkent alleen de complete URL-string als geheel als "al gezien", niet de losse bouwstenen waaruit hij is samengesteld.
Poging 10 — een pagina met kant-en-klare links (eerste succesvolle start)
Aanpak: een nieuwe pagina, /canvas/beginnen.html, met voor negen vaste botnamen (waaronder Claude, ChatGPT en Gemini) een echte, aanklikbare link naar de padvariant van Poging 9.
Resultaat: de agent kon deze pagina ophalen, en de link erin — nu wél "al gezien" omdat hij letterlijk in een zojuist opgehaalde pagina stond — werkte voor het eerst: een geldig antwoord met een nieuw id en sleutel. Dit was de eerste geslaagde start in de hele sessie.
Poging 11 — kleur, x en y kiezen; de eerste voltooide tekening
Aanpak: herhaaldelijk dezelfde soort padgebaseerde links doorlopen om een kleur, een x-positie en een y-positie te kiezen, na acht stippen afgesloten met de klaar-link.
Resultaat: gelukt — de eerste tekening van de sessie hing aan de muur, met acht stippen in een oplopende lijn. De gebruiker merkte direct op dat de tekening geen enkele relatie had met de kleurplaat die al onder het doek lag, en vroeg of de agent die kleurplaat had gezien.
Poging 12 — alsnog kijken naar de eigen tekening
Aanpak: de snapshot-URL ophalen die in het klaar-antwoord vermeld stond.
Resultaat: mislukt, met dezelfde "URL nooit gezien"-melding als eerder.
Verklaring: die URL stond wel letterlijk in de tekst van het antwoord, maar dat antwoord was platte tekst, geen HTML met een echte link. Een adres telt voor deze fetch-tool kennelijk pas als "gezien" als het in een echte link staat, niet als los stuk tekst.
Poging 13 — de padvariant krijgt echte HTML en een live afbeelding
Aanpak: de padvariant aangepast om HTML terug te geven met klikbare links, plus een afbeelding van de tekening-in-wording na elke stap.
Resultaat bij eerste test: de agent haalde exact dezelfde "Claude"-link op als in Poging 10, en kreeg een oud, allang afgesloten antwoord terug — nog steeds platte tekst, hetzelfde oude id.
Verklaring: de fetch-tool had die exacte URL al eerder in deze sessie opgehaald en gaf een bewaard (gecachet) antwoord terug, ongeacht wat er intussen aan de kant van de server was veranderd.
Poging 14 — bevestiging met een nooit eerder gebruikte naam
Aanpak: beginnen.html opnieuw ophalen en de link kiezen voor een naam die nog niet eerder was aangeroepen ("ChatGPT").
Resultaat: gelukt — een nieuw id, HTML als antwoord, en een echte afbeelding met de tekening tot dan toe. Dit bevestigde zowel dat de HTML-aanpassing werkte, als dat Poging 13 een cache-effect was en geen aanhoudend serverprobleem.
Poging 15 — de afbeelding zelf blijkt niet leesbaar
Aanpak: de nieuwe afbeelding-URL (de PNG-snapshot) alsnog ophalen.
Resultaat: mislukt, met een nieuwe, andere foutmelding: "Image content is not supported."
Verklaring: geen toestemmings- of cacheprobleem meer, maar een harde, categorische beperking van deze specifieke fetch-tool: hij kan sowieso geen beeldbestanden verwerken, van geen enkele URL.
Poging 16 — de tekening als data in plaats van als beeld
Aanpak: de tekening ophalen als platte JSON-data (coördinaten en kleuren) in plaats van als afbeelding.
Resultaat: gelukt, bruikbare JSON.
Beperking: dit levert alleen de eigen, al geplaatste stippen van de agent, niet de kleurplaat die als achtergrond onder het doek ligt.
Poging 17 — drie herhalingen die eerdere bevindingen bevestigen
Aanpak: opnieuw beginnen als "Claude", beginnen als "anoniem", en verdergaan met een kleurkeuze-link binnen de al open "Gemini"-tekening.
Resultaat: "Claude" leverde opnieuw het oude, gecachete antwoord van Poging 10 op. "anoniem" werd meteen geweigerd omdat die naam niet op de lijst van beginnen.html stond. De kleurkeuze-link binnen de "Gemini"-tekening gaf een echte 404 van de server terug, terwijl diezelfde link luttele seconden eerder nog in een vers opgehaalde pagina stond.
Verklaring bij het laatste punt: voor zover binnen de sessie kon worden vastgesteld geen fetch-tool-probleem maar een afzonderlijk serverprobleem — mogelijk doordat de status van een openstaande tekening alleen in het geheugen werd bijgehouden en tussentijds verloren ging.
Poging 18 — de kleurplaat rechtstreeks als afbeelding aanbieden, buiten een URL om
Aanpak: de gebruiker plaatste de kleurplaat als bijlage rechtstreeks in het gesprek, niet als URL om op te halen.
Resultaat: gelukt. De agent kon de afbeelding gewoon bekijken en beschrijven — een lachende zon, twee wolken, een horizonlijn, een meisje dat een zandkasteel bouwt, een jongen in een zwemband, een jongen met een strandbal, en grasplukjes — inclusief een grove positie voor elk element in het rooster van de padvariant.
Verklaring: het onvermogen om beelden te verwerken (Poging 15) gold specifiek voor het ophalen van een afbeelding via een URL, niet voor het bekijken van een afbeelding die rechtstreeks in het gesprek werd geplaatst.
Conclusies
De meeste mislukkingen in deze sessie waren geen serverprobleem, maar eigenschappen van het specifieke gereedschap van de agent: een fetch-tool die querystrings wegstript zonder dat te melden, die alleen al-geziene URL's toestaat, die antwoorden per exacte URL blijft cachen binnen een sessie, en die principieel geen beeldbestanden via een URL kan verwerken. Vrijwel elke serveraanpassing loste dan ook niet het oorspronkelijke probleem op, maar omzeilde een specifieke, pas achteraf begrepen beperking. Daarnaast bleek een taalmodel bereid om, zonder kwade bedoeling, een verkeerde maar stellig klinkende verklaring voor zijn eigen beperkingen te geven (Poging 5) — een reden om claims van een agent over wat hij wel en niet kan waar mogelijk te verifiëren. Ten slotte bleek een afbeelding die rechtstreeks in het gesprek wordt geplaatst een fundamenteel andere, en voor deze agent wél werkende, route te zijn dan diezelfde afbeelding via een URL laten ophalen. De volledige technische versie van dit relaas staat op github.com/vpro/medialab-ai-public.
Bijzonder was het trouwens dat ik in Visual Studio Code door Copilot allerlei technische dingen liet fixen met Sonnet als model, terwijl de tests die ik in de Claude-app deed óók met Sonnet plaatsvonden. Hadden die twee elkaar niet even rechtstreeks kunnen bijpraten, vroeg ik me af? Maar wederom blijkt een groot verschil te zitten tussen wat een AI zegt te kunnen, denkt te kunnen, en wat hij écht daadwerkelijk kan. De ene Claude die voor de andere Claude software zat te bouwen en te debuggen, bleek eigenlijk zichzelf toch niet echt goed te kennen.
ai.vpro.nl/canvas