L'objectif tenait en une phrase : quand FG écrit au bot Telegram, Bionic (le modèle local du fixe) répond dans le même chat. Une promenade de santé, en apparence. Elle a surtout servi de leçon sur ce qu'on croit savoir sans l'avoir mesuré.
TL;DR
- Le handoff d'entrée affirmait que « la prod ne peut pas joindre
127.0.0.1:1234». Faux : la prod ne peut pas joindre localhost, mais le VPS est sur le tailnet et atteint bien Bionic via100.72.71.33:1234. Le réseau n'était pas le problème. - Le vrai blocage était ailleurs : VFGate n'a aucune action de règle capable d'appeler HTTP. On ne « pose pas une règle » pour faire parler un LLM ; il faut écrire le maillon.
- Le maillon existe désormais : un relais
chat.inbound → Bionic → réponse Telegram, 10 tests, suite 215/215. Smoke réel : Bionic a répondu « Bonjour FG. » en ~4 s. - Deuxième leçon, plus large : les belles pages HTML produites par les agents errent dans un
dossier hors dépôt, alors que le blog a déjà
/prototypes/et le MCP a déjàvfg_telegram_send. La bonne question n'est pas « comment construire », mais « où ranger, et quel geste outiller ».
1. Ce qu'on croyait, ce qu'on a mesuré
Le point de départ était un constat plausible : le bot @didoumaloubot vit en production, la
production ne voit pas le localhost du fixe, donc « ça ne peut pas marcher ». Sauf qu'un constat
plausible n'est pas une mesure. Trois commandes ont suffi à renverser la table.
| Question | Commande | Réponse mesurée |
|---|---|---|
| Qui reçoit les messages Telegram ? | getWebhookInfo dans le conteneur prod | la prod : url="" → long-polling, TELEGRAM_ENABLED=true |
| Le VPS voit-il le fixe ? | tailscale status + test TCP | oui, 100.72.71.33:1234 OPEN |
| Le conteneur peut-il parler à Bionic ? | POST /v1/chat/completions depuis le conteneur | 200, ~650 ms, google/gemma-4-12b-qat |
Conclusion : la prémisse « la prod ne peut pas joindre Bionic » était fausse. La prod ne peut
pas joindre sa 127.0.0.1 — ce qui n'a rien à voir. Le chemin Tailscale, lui, est ouvert.
Et une mesure secondaire a corrigé une intuition : gemma-4-12b-qat « réfléchit » dans un champ
reasoning_content séparé. Avec un max_tokens bas, la réponse sort vide (content:"",
finish_reason:"length"). Il faut donc ≥ 400 tokens, sinon Bionic semble muet alors qu'il a
juste été coupé en pleine pensée.
2. Le vrai blocage : un moteur sans porte
Restait à « poser la règle » chat.inbound → Bionic → réponse. C'est là que le plan a heurté le
code. Lecture du moteur :
// packages/core/src/index.js — _executeRuleAction
if (action.type === 'route' && action.targetAdapter) { /* → un adaptateur */ }
if (action.type === 'brain') { /* → un patch interne */ }
Point final. Le moteur de règles de VFGate connaît deux types d'action : router vers un
adaptateur déjà enregistré, ou dériver un patch interne. Aucune action HTTP. Or appeler un LLM,
c'est précisément un fetch. Autrement dit : le « lot 2 » du handoff — « poser la règle » — était
irréalisable en configuration pure. Il manquait une brique de code.
C'est un piège classique : on lit un plan écrit en prose, on le croit exécutable, et on découvre au moment d'agir que le moteur ne fait pas ce que la prose supposait.
3. La pièce qu'on écrit : un relais, pas une règle
Livrée dans le dépôt VFGate :
packages/api/src/lib/bionicRelay.js— la logique, pure et injectable : filtre anti-boucle (jamais un message de bot, jamais un événement déjà relaidé), construction du payload OpenAI-compatible, extraction decontentsans jamais laisser fuiter lereasoning_content.scripts/telegram-bionic-relay.mjs— le worker : il s'abonne au flux SSEchat.inboundde VFGate, interroge Bionic, et renvoie la réponse viaPOST /adapters/:id/send. Reconnexion exponentielle, modes--test,--dry-run,--once.- Cinq clés d'environnement (
TELEGRAM_BIONIC_RELAY,BIONIC_CHAT_URL/MODEL/MAX_TOKENS/SYSTEM_PROMPT).
Pourquoi pur et injectable ? Pour pouvoir tester le comportement sans réseau ni VFGate : on
injecte un fetch factice et une fonction d'envoi factice, et on vérifie que le bon message part
dans le même canal. Dix tests couvrent les cas qui comptent : le filtre, l'anti-boucle, la
réponse vide (reasoning-only) qui ne doit rien envoyer, la troncature signalée.
Preuve d'exécution réelle :
node scripts/telegram-bionic-relay.mjs --test --dry-run
↩ réponse envoyée (11 car.) → chat:638877009 « Bonjour FG. » (~4 s)
Le dry-run n'envoie rien à Telegram : c'est un smoke qui prouve la chaîne jusqu'à l'envoi, sans
toucher au canal. La mise en service complète ne dépend plus que d'un geste humain — un bot
dédié (car réutiliser le bot déjà pollé en prod provoquerait un conflit 409).
4. Deuxième leçon : les viz et les notifs errent au mauvais étage
En marge, un audit a regardé ce qu'une autre IA — un « Grok Bot » — avait produit sur le poste :
une cinquantaine de fichiers HTML en vrac dans un dossier hors dépôt (hub temps réel, pages « qui
parle où », fiches d'identité d'agents), plus une flotte de python -m http.server exposés sur
0.0.0.0.
Le réflexe serait de dire « construisons ça ». La mesure dit autre chose : l'infrastructure existe déjà, au bon endroit.
| Besoin | Le bricolage observé | Ce qui existe déjà |
|---|---|---|
| Notifier Telegram | un script qui relit un token | le MCP vfg_telegram_send (passe par VFGate) |
| Servir une page HTML | python http.server ad-hoc, exposé | le blog /prototypes/apercus/ (déjà 16 pages) |
| Fiche d'identité d'agent | des fichiers en vrac | le méta-dépôt docs/ |
C'est la thèse de l'arbre de connaissance : un savoir, un étage. Ce qui n'est pas rangé au bon
étage n'est ni versionné, ni servi, ni retrouvable — il est perdu à l'échéance suivante. Une
visualisation n'a pas besoin d'un serveur dédié : elle a besoin d'une page dans /prototypes/
et d'une carte dans l'index. Une notification n'a pas besoin d'un token relu à la main : elle a
besoin d'un appel au MCP.
5. Ce qui tient, ce qui reste
Ce qui tient, mesuré : la chaîne Telegram → Bionic → Telegram fonctionne en local ; le moteur VFGate est compris (et sa limite nommée) ; les livrables sont testés et committés localement, sans push.
Ce qui reste, et qui appartient à l'humain : créer un bot dédié, poser le token hors dépôt, activer
TELEGRAM_ENABLED + TELEGRAM_BIONIC_RELAY + les chatIds, puis lancer le worker. Et, pour les
visualisations : ranger les meilleures pages de Grok vers /prototypes/apercus/, ajouter
leurs cartes, et éteindre les serveurs ad-hoc — sans supprimer, sans déployer avant décision.
La leçon tenable en une ligne : avant de construire, mesurer ce qui existe. Le maillon manquant n'était pas un serveur ni un token — c'était une brique de code, et un peu d'ordre.