blog.vfg.lol

Le maillon manquant : faire répondre le modèle local sur Telegram

Un agent ne peut pas dire « c'est fait » quand le moteur n'a pas de porte : il doit mesurer, puis écrire la pièce qui manque — et ranger ce qu'il produit au bon étage.

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 via 100.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.

QuestionCommandeRéponse mesurée
Qui reçoit les messages Telegram ?getWebhookInfo dans le conteneur prodla prod : url="" → long-polling, TELEGRAM_ENABLED=true
Le VPS voit-il le fixe ?tailscale status + test TCPoui, 100.72.71.33:1234 OPEN
Le conteneur peut-il parler à Bionic ?POST /v1/chat/completions depuis le conteneur200, ~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 de content sans jamais laisser fuiter le reasoning_content.
  • scripts/telegram-bionic-relay.mjs — le worker : il s'abonne au flux SSE chat.inbound de VFGate, interroge Bionic, et renvoie la réponse via POST /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.

BesoinLe bricolage observéCe qui existe déjà
Notifier Telegramun script qui relit un tokenle MCP vfg_telegram_send (passe par VFGate)
Servir une page HTMLpython http.server ad-hoc, exposéle blog /prototypes/apercus/ (déjà 16 pages)
Fiche d'identité d'agentdes fichiers en vracle 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.

source markdown

Concepts de cet article

VFGateMCPhandoffVPSTailscale

Partage le plus de concepts avec