Onderzoek 1 — genui-concept "Mijn Zilch" (genui.vleugje.nl)
Samenvatting
"Mijn Zilch" is een Next.js-chatassistent voor Zilch-webhosting: de klant typt vrije taal, een
klein Claude-model kiest één van ~20 acties, en daarbij hoort altijd een vast React-paneel naast de
chat. De lay-out is een twee-koloms grid (chat 512px, paneel de rest) op desktop, een sleepbare
bottomsheet op mobiel. Het "brein" (app/api/brain/route.ts) is Claude Haiku met tool-use,
gedwongen tot exact één tool-call; zonder API-key of bij een fout valt de app terug op een
deterministische keyword-router, dus de demo werkt altijd. Elke actie schrijft een append-only
event in een "beslislog"; afgeronde episodes worden fire-and-forget gereflecteerd tot een korte,
herbruikbare "les" (LLM of heuristiek), die relevant (woord-overlap, geen embeddings) de volgende
systeemprompt injecteert. Een parallelle "geleerd recept"-laag onthoudt DNS-recordsets die samen
werkten. Alles wordt op /admin door mensen gecureerd (vastzetten/archiveren/twijfel). Toon is kort
(max 2 zinnen), warm, jargonvrij, doel-eerst, geen em-dashes. Voor Mi casa is vooral het patroon
"action-id + args in message-metadata → paneel-dispatch", de stub/keyword-terugval en de
beslislog-als-bewijslaag herbruikbaar; de SQLite-leerlus zelf is te zwaar voor een pitch-demo.
Bevindingen
1. Interactiemodel en lay-out
- Chat links vast op 512px, paneel rechts vult de rest: grid
lg:grid-cols-[512px_1fr](app/page.tsx:28). Op mobiel geen kolom; het paneel is een sleepbare bottomsheet die van onderaf inschuift (components/assistant-ui/tool-ui-panel.tsx:213-254, drag-to-dismiss bijinfo.offset.y > 60). - Een paneel = een React-component + eigen data-fetch + eigen schrijvende calls naar
/api/mx/*, gekozen doorswitch (id)opinvokedAction.id(tool-ui-panel.tsx:77-180). Elk paneel krijgtepisodemee zodat zijn schrijfacties aan dezelfde episode hangen. - Het gesprek loopt na een paneelactie door in dezelfde thread; het paneel blijft tot een volgend
bericht een nieuwe actie kiest (
AnimatePresence mode="wait", regel 222). Een doorvraag (bv. vage DNS-vraag) opent bewust géén paneel, en het volgende bericht deelt dezelfdeepisode-id (lib/conversation/stub-conversation-service.ts:400-436).
2. Het brein (app/api/brain/route.ts)
- Model:
claude-haiku-4-5-20251001, wisselbaar via envBRAIN_MODEL(regel 21); draaide op 18 juli tijdelijk op Sonnet (README.md,docs/testronde.md). - Eén tool
respond, gedwongen mettool_choice: { type: "tool", name: "respond" }(regel 330): een reactiezin (max 2 zinnen NL) + optioneelactionuit een vaste enum van ~22 ids (regels 140-163), met per-actie args (soort,naam,recipe,domein,route, ...). - Systeemprompt is één geannoteerde beslisboom in natuurlijke taal: "denk vanuit het doel van de
klant, niet de techniek" (regel 29-35), gevolgd door ~20 regels "situatie X → action Y" met
expliciete disambiguatie tussen bijna-gelijke acties (bv.
manage-mailvsmigrate-mailboxvsmail-instellingen, regels 39-43, 122-126). - Injectie (
bouwSysteem, regel 228-267): laatste 12 beslislog-events, tot 8 relevante lessen en tot 6 geleerde recepten, elk als eigen##-blok met instructie om proactief te verwijzen. - Terugval: een teruggegeven action-id buiten de toegestane enum (instellingenlaag zette 'm uit)
wordt geschrapt en vervangen door een vaste uit-melding (regels 340-345,
UIT_REACTIEinlib/instellingen/naden.ts:8-9). ZonderANTHROPIC_API_KEYof bij een fout antwoordt de route altijd met een vaste generieke zin enaction: null(regels 297-303, 374-378) — het brein faalt dus nooit zichtbaar. Client-side vangtlib/conversation/stub-conversation-service.tseen lege actie én ontbrekenddoorvraag-signaal op met een 300+ regels tellendekeywordRoute()(regex-cascade, mail vóór ssl vóór dns) die altijd iets zinnigs oplevert (regels 60-303).
3. De leerlus
- Episodes → lessen: na een episode (opzeggen, migratie, niet-groene diagnose) draait
reflecteer()fire-and-forget (lib/mx/reflect.ts:156-210): met API-key destilleert een klein model één les uit de event-regels (toolles, regel 62-92); zonder key een pattern-heuristiek (Gmail→DMARC, M365→autodiscover+DMARC, eigen server→www vergeten, regels 109-150). Dedup op een signatuur (sig): herhaling verhoogtkeeri.p.v. een duplicaat (lib/mx/lessons.ts:119-149). - Lessen → prompt: gewogen op
keer * verval(90-dagen-halfwaardetijd)(lessons.ts:96-105) en gefilterd op woord-overlap met de vraag (geen embeddings, drempel 0,1, max 8, budget 1600 tekens,lib/mx/kennis-selectie.ts:41-100). - Recepten:
promoteEpisode()generaliseert de DNS-records van een geslaagde episode naar een{domein}-sjabloon, dedupt op recordsignatuur (lib/mx/recipes-learned.ts:63-140); het brein biedt zo'n recept proactief aan viaaction id "learned-recipe". - Admin-curatie:
/admin(app/admin/page.tsx) toont activiteit, gewogen lessen, recepten, een "verbeterbord" (werklijst uit vier signalen: fout-oordelen, vraaggaten, lage sterren, twijfel-lessen — commitc4f9010) en een tester-oordeel ("Klopt"/"Klopt niet") dat lessen bevestigt of optwijfelzet, zodat ze niet meetellen tot een mens ze beoordeelt (reflect.ts:220-257). - Kosten/nut: het werkplan noteert leskwaliteit en een reflectie-queue expliciet als
"geparkeerd" tot >50 actieve lessen of merkbare rate-limits (
docs/plannen/leerloop-werkplan.md, Geparkeerde items). Kortom: een SQLite-tabel + twee extra LLM-calls per episode plus een stuk admin-UI — nuttig bij herhalend klantverkeer, overkill voor een eenmalige pitch-demo.
4. Toon en microcopy
- Systeemprompt eist "KORTE reactie (max 2 zinnen, Nederlands)" en verbiedt em-dashes
(
app/api/brain/route.ts:23-27); dezelfde regel staat letterlijk terug inreflect.ts:49enREADME.md. - Vaste voorbeeldzinnen zijn kort, geruststellend, nooit technisch: "Vervelend, ik doe meteen even
een controle voor je." (
stub-conversation-service.ts:108), "Ik zet de quarantaine-modus voor je aan." (regel 72), "Fijn dat je je mail meeneemt! Ik zet de overzet voor je klaar." (regel 215). - Niet-weten wordt teruggegeven als open doelvraag, geen jargon: "Vertel eens in je eigen woorden
wat je wilt bereiken. Wil je bijvoorbeeld je nieuwe website bereikbaar maken vanaf je domein, je
e-mail naar een andere provider verhuizen, of iets anders?" (regels 284-287, ook in de brein-prompt
regel 134-137) — bewust gemeten als "doorgevraagd", geen dekkingsgat (commit
6d259c2). - Fouten krijgen één vaste, nette zin ("Dit onderdeel staat op dit moment uit. Onze support helpt je
er graag mee verder.",
lib/instellingen/naden.ts:8-9), nooit een technische melding.
5. Herbruikbaar voor Mi casa (vs. niet)
- Herbruikbaar: "brein kiest action-id + typed args → dispatch-switch rendert dat paneel"
(
tool-ui-panel.tsx:71-181) vertaalt 1-op-1 naar Svelte (store metinvokedAction,{#if}-keten i.p.v. React-switch). De message-metadata-vorm (invokedAction: { id, args, sessionId },lib/conversation/types.ts:45-54) is compact en serialiseerbaar, past ook op Hono/SSE. De keyword-router (stub-conversation-service.ts:60-303) is direct bruikbaar als demo-vangnet: een regex-cascade die altijd iets zinnigs teruggeeft, los van of Claude beschikbaar/snel is. De beslislog (append-only event + mensenzin + correlationId) is een goedkoop bewijsspoor dat ook past bij "wat heeft mijn huis net gedaan". De harde "max 2 zinnen, geen em-dash, doel niet techniek"-regel is direct herbruikbaar copy-beleid. De pariteit-gaplijst-methodiek (docs/plannen/pariteit-gaplijst.md) — tools inventariseren, gaten clusteren, prioriteren op vraagfrequentie × bouwkosten — is een bruikbaar sjabloon om te bepalen welke Mi casa-acties een paneel verdienen. - Niet herbruikbaar / te zwaar: de volledige SQLite-leerlus (10+ bestanden) is gebouwd voor
herhalend klantverkeer met een curatieteam; voor een eenmalige pitch met twee vaste persona's
(Sanne, Tom) levert dat niets op dat handgeschreven "recepten" niet ook doen. De admin-app
(
app/admin/*, 12+ componenten) idem. De Auth0/oauth2-proxy-laag (lib/auth0.ts,app/page.tsx:17-22) is hosting-specifiek en overbodig zonder echte login. De 22-actie-enum is webhosting-jargon; Mi casa's actieset moet opnieuw ontworpen worden, niet overgenomen.
6. Valkuilen (uit docs/commits/sessielogs)
- framer-motion 12.42 bevriest enter-animaties op React 19 → gepind op 12.38.0 (
README.md, Lokaal draaien). Voor Mi casa's stack niet direct van toepassing, wel een waarschuwing: pin animatie-libs expliciet en test de exacte combinatie. next startwerkt niet metoutput: standalone; nodig isnode .next/standalone/server.jsplus handmatig.next/statickopiëren (README.md). Les: een build-modus die "bijna werkt" faalt onopgemerkt tot de laatste stap; test het exacte deploy-commando, niet de dev-server.- Race tussen instelling laden en gedrag bij mount: "gebruik
useInstellingenMetStatus()en wacht opgeladen, anders wint de standaard de race van een uit-stand" (README.md, Instellingen). Generieke les: elke client-side feature-flag die gedrag bij mount stuurt heeft een expliciete wachttoestand nodig. - Reconstructie-risico: de originele bron ging ooit verloren (leefde alleen in een tijdelijke
werkmap, nooit in git) en moest op 13 juli 2026 uit de gedeployde build worden teruggebouwd
(
README.md, Herkomst). Les: commit vroeg en vaak, ook demo-/prototypecode. - Routerings-precedentie is broos: meerdere commits (
9f24499,c10e455,17e9327,bc5e6e4) beschrijven hoe een keyword-tak een andere kaapte (bv. "werkt ... niet" ving ooit site-diagnose weg van vage mail-klachten) en pas na expliciete volgorde-comments en non-regressietests stabiel werd. Elke nieuwe actie moet tegen de hele bestaande cascade getest worden. - Instellingen-laag als byte-gelijk contract: een lege instellingen-tabel moet bewijsbaar exact
het huidige gedrag zijn, bewaakt door byte-gelijk-tests (
lib/instellingen/naden.test.ts,README.mdInstellingen). Nuttig als Mi casa ooit een aan/uit-schakelaar per flow krijgt.
Wat dit betekent voor Mi casa
Het sterkste overdraagbare idee is niet de leerlus maar het kleine, robuuste kerncontract: "brein kiest exact één actie met typed args uit een vaste, korte enum, met altijd een deterministische, gratis terugval die nooit zichtbaar faalt." Voor een huis dat praat vertaalt dit naar: een vaste lijst huis-acties ("toon huisprofiel", "open onderhoudsplan", "laat fans zien", "start droomprofiel"), een korte, hard begrensde systeemprompt met doel-vóór-techniek-instructie, en een keyword-router als vangnet zodat de pitch op 1 november nooit "Ik snap het niet" toont. De beslislog-als-bewijslaag past bij de harde grens dat elk cijfer een bron moet hebben: elke paneelactie logt zijn eigen mensenzin + episode, en dat is meteen bewijs dat "wat de demo toont, werkt echt". De leerlus zelf (SQLite, reflectie-LLM-calls, curatie-admin) hoort niet in de MVP thuis: te veel bouwtijd voor iets dat bij twee vaste persona's en een eenmalige pitch geen zichtbaar effect heeft.
Aanbevelingen
- Neem "action-id + typed args in message-metadata → paneel-dispatch" letterlijk over als contract tussen Hono-server en Svelte-client, met een vaste, korte actie-enum voor Mi casa.
- Bouw eerst een keyword-/regex-terugval, niet het LLM-brein: dat maakt de demo écht ononderbreekbaar.
- Schrijf de systeemprompt als expliciete beslisboom (situatie → actie, met disambiguatie tussen
buur-acties), naar het voorbeeld van
app/api/brain/route.ts:23-138, niet als abstracte instructie. - Leg een lichte beslislog aan (event + mensenzin + correlationId per actie) als bewijsspoor, zonder de leer-/curatielaag erbovenop te bouwen.
- Sla de SQLite-leerlus expliciet over voor de pitch; heroverweeg pas bij echt, herhalend gebruik.
- Hergebruik de copy-regels 1-op-1: max 2 zinnen, geen jargon, doel vóór techniek, vaste nette foutzin, geen em-dash.
- Pas de pariteit-gaplijst-methodiek toe (
docs/plannen/pariteit-gaplijst.md) om te bepalen welke Mi casa-acties, mét bewijs uit FUNCTIEKAART.md/PITCH-FEITEN.md, een eigen paneel verdienen.
Open vragen
- Wil Fable/E een "episode"-koppeling (client-id die vraag, doorvraag en actie samenbindt) voor Mi casa, of volstaat een simpelere sessie-id?
- Moet de demo een eigen "stub-modus" krijgen (zoals
NEXT_PUBLIC_STUB=1), of is "API-key altijd aanwezig op de pitchdag" een acceptabel risico? - Is er behoefte aan een vroege, kleine leerlus (alleen heuristiek, geen LLM/admin-UI) om te kunnen zeggen "het huis leert mee", of blijft dat buiten scope tot na 1 november?
Bronnen
Alle bestanden via ssh z 'cat ...' gelezen op Mac z, map ~/Developer/genui-tel/, tenzij anders
vermeld: README.md, CLAUDE.md, app/api/brain/route.ts, app/page.tsx, app/layout.tsx,
components/assistant-ui/tool-ui-panel.tsx, components/layout/chat-container/chat-container.tsx,
lib/conversation/stub-conversation-service.ts, lib/conversation/types.ts, lib/mx/lessons.ts,
lib/mx/reflect.ts, lib/mx/recipes-learned.ts, lib/mx/kennis-selectie.ts,
lib/instellingen/naden.ts, lib/quarantine.ts, app/admin/page.tsx, docs/testronde.md,
docs/plannen/leerloop-werkplan.md, docs/plannen/pariteit-gaplijst.md,
sessie-log-derde-versie-2026-09-01.txt. Verder git log --oneline in dezelfde repo (o.a. commits
9f24499, c10e455, bc5e6e4, 6d259c2, c4f9010, 266a1f1), en de schermafbeelding
/Users/el3333/Library/Mobile Documents/com~apple~CloudDocs/2026/Code/genui-tel-koppeling/oogst/flow-xel-dns-doel.png
(bekeken ter bevestiging van de paneel-lay-out).