Opinion

LMS et CRM de 205 000 lignes de code écrits par des agents IA : le prix de l'exploitation, trois statuts de lead et un caractère \r

Deux systèmes en production liés entre eux — une plateforme éducative LMS (121 853 lignes TS/TSX, 47 routeurs, 288 procédures) et un CRM doté d'un registre unifié de 334 outils (83 251 lignes) — ont été écrits sans équipe de développeurs, par des agents IA, au-dessus d'un seul PostgreSQL. Écrire le code a pris quelques soirées ; le vrai coût s'est révélé lors de la mise en production : 14 défauts critiques détectés en revue adversariale, 5 autres introduits par les correctifs eux-mêmes, et un caractère invisible de retour chariot \r qui a silencieusement désactivé la pratique interactive sur 138 leçons sur 146. Intégrer de façon fiable des agents IA à un business réel a exigé de scinder le traitement d'un lead en trois états indépendants verrouillés par FOR UPDATE, de reconstruire l'historique du dialogue côté serveur, et d'enregistrer sa propre entrée de rendez-vous avant même d'appeler les API externes Zoom et Google Calendar.

LMS et CRM de 205 000 lignes de code écrits par des agents IA : le prix de l'exploitation, trois statuts de lead et un caractère \r
Hamidun News.
Écouter l'article
Fiche synthèse et chiffres clés
ParamètreDonnées
Rédacteur en chef
Ampleur d'un seul bug CRLF invisible138 leçons pratiques sur 146 avec zéro soumission réussie, à cause d'un caractère `\r` final mal analysé côté serveur
Chiffre clé #1Récolte de la revue adversariale avant le lancement : 14 vulnérabilités dangereuses lors de la première vague + 5 défauts introduits par les correctifs eux-mêmes
  • Volume de la base de code sans équipe de développeurs : 205 104 lignes de TypeScript/TSX (121 853 dans le LMS répartis sur 532 fichiers et 83 251 dans le CRM répartis sur 367 fichiers)
  • Surface de l'API et de la façade agentique : 47 routeurs et 288 procédures tRPC dans le LMS (93 outils) + 334 outils dans le registre du CRM (257 exposés dans la façade)
  • Ampleur d'un seul bug CRLF invisible : 138 leçons pratiques sur 146 avec zéro soumission réussie, à cause d'un caractère `\r` final mal analysé côté serveur
  • Récolte de la revue adversariale avant le lancement : 14 vulnérabilités dangereuses lors de la première vague + 5 défauts introduits par les correctifs eux-mêmes

Écrire aujourd'hui un système de grande ampleur grâce à des agents IA, sans équipe de développeurs salariés, est devenu une tâche bon marché. Ce qui coûte cher, c'est tout ce qui commence le lendemain matin de la démo : la protection contre la falsification de l'historique par le client, les verrous en base lors des doubles clics des commerciaux, et les caractères spéciaux invisibles dans les secrets.

Mes deux systèmes — la plateforme d'apprentissage LMS (121 853 lignes TS/TSX, Next 16.2.6, React 19, Drizzle ORM, 58 migrations, 16 parcours, 211 leçons et 55 simulateurs) et le CRM qui lui est associé (83 251 lignes, Prisma 7.

6, 48 modèles de base de données, BullMQ et quatre agents IA connectés) — tournent sur une seule instance PostgreSQL. Nous avons construit notre propre pile technique pour deux raisons : aucun LMS sur étagère n'offre le primitif permettant à un réseau de neurones de d'abord jouer le rôle d'un étudiant au prompt faible, puis de coacher selon un préambule caché ; et le CRM devait, sans dupliquer les bases de données, se superposer à un bot de vente fonctionnant déjà sur Telegram. D'ailleurs, repeindre 289 variables de couleur lors du fork du CRM a pris une soirée, tandis que nettoyer les données de démonstration héritées (205 demandes de test, un repli « marge 38 % » et de faux badges « Tâches 7 ») s'est étalé sur deux mois.

Comment un caractère invisible de retour chariot \r a-t-il cassé 138

leçons, la déduplication et OAuth ?

Le bug le plus sournois du LMS ne laissait aucune trace dans les logs. Les étudiants voyaient un bloc `:::practice` correctement rendu, cliquaient sur « Envoyer » et recevaient « Bloc pratique introuvable ». Les statistiques de la base ont montré que 138 leçons sur 146 affichaient exactement zéro réussite. Le contenu était créé sous Windows avec des retours à la ligne `CRLF` (`\r\n`) : le frontend normalisait le texte avant le rendu, mais le parseur côté serveur découpait par `\n`, laissant un `\r` invisible à la fin de la ligne :

```typescript // Sans normalisation, le \r final casse l'ancre $ en JS, // car le point (.) ne capture pas \r, et le champ task est analysé comme vide : md = md.replace(/\r\n?/g, "\n"); const match = line.match(/^(task|starter|goal|model|system)\s:\s?(.)$/i); ```

Le même `\r` modifiait le hash de contrôle du texte lors du découpage en chunks, forçant le système à repayer la génération d'embeddings, et une troisième fois il s'est retrouvé à la fin d'un secret dans `.env`, provoquant un retour `invalid_client` de Google OAuth (détecté grâce à une vérification de longueur de chaîne `awk -F= '/^GOOGLE_/ {print $1, length($2)}' .env` — 36 octets au lieu de 35).

Pourquoi ne peut-on pas faire confiance aux données venant du

navigateur, et comment fonctionne le registre de 334 outils du CRM ?

La revue adversariale menée avant le lancement a révélé 14 défauts majeurs, et la vérification des correctifs écrits en réponse en a révélé 5 autres. Dans la première version de l'exercice pratique, le navigateur envoyait lui-même au serveur le texte de l'énoncé et le tableau `history`, ce qui permettait de falsifier une réplique via les DevTools ; 8 des 55 simulateurs sur 55 obtenaient 71 à 90 points en copiant simplement l'énoncé dans la réponse. Nous avons transféré la construction du préambule et la reconstruction des 6 dernières répliques entièrement côté serveur, et relevé le délai du correcteur automatique de 30 à 60 secondes.

Dans le CRM, toute l'architecture est construite autour d'un registre unique de 334 outils (257 exposés dans la façade agentique, et 77 outils système fermés par le drapeau `facadeExposed: false`). Chaque appel passe par le pipeline unifié `invoke()` : l'identité `acting_user` n'est extraite que de la session (en distinguant les erreurs `missing` et `invalid`, par exemple lorsqu'un ID de session `api-...` ou un ID négatif de chat de groupe est transmis), et les arguments sont nettoyés par l'expression régulière `SECRET_KEY_RE` avant d'être écrits dans `audit_log`.

Pourquoi diviser un même bouton de lead en trois états, et comment

repérer les rendez-vous orphelins sur Zoom ?

Dans la vente réelle, le commercial écrit au client dans son Telegram personnel, pendant qu'un bot relance en parallèle la même personne. Il a donc fallu scinder un unique bouton « Prendre le lead » en trois états indépendants : « Je m'en occupe » (responsable désigné), « Contacté » (fait du premier contact établi) et « Intercepté » (le bot se tait et cède la conversation à l'humain). Pour éviter que deux commerciaux n'écrasent la même fiche en double-cliquant, le CRM recherche l'enregistrement le plus ancien par `telegram_id` et le verrouille avec `SELECT ...

FOR UPDATE`. Et lors de la création de rendez-vous sur Zoom et Google Calendar, la première ligne exécutée est toujours un `INSERT` dans sa propre base avec un statut `creating` et des champs `zoom_state` / `gcal_state`, afin qu'en cas de panne de l'API externe, aucun rendez-vous orphelin ne reste non comptabilisé dans le cloud.

Cette mécanique de simulateurs interactifs et de registres agentiques fonctionne au sein de [Hamidun Academy](https://academy.hamidun.com) (essai gratuit de 72 heures disponible) et dans l'écosystème [Nomad AI](https://nomadnet.ai) / [agent.nomadnet.ai](https://agent.nomadnet.ai), et nous détaillons l'architecture de nos CRM et LMS dans le [Vibe Club](https://hamidun.com/club).

Dossier
ZK
Zhemal Khamidun — CPO d'Alpina Digital, product manager d'AlpinaGPT. Conçoit des produits IA depuis 2019. Hamidun News est un projet personnel devenu un agrégateur en production avec plus de 6 000 lecteurs.
✦ Hands-On AI Engineering · Hamidun Academy

Build & Ship Production AI Agents Without Boilerplate Code

Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.

Start 72h Free Trial →Vibe-Coding Intensive
Qu'en pensez-vous ?
Chargement des commentaires…