Comment j'ai construit OMLA en dirigeant des agents IA plutôt qu'en écrivant le code

Publié
11 juillet 2026
Par
Jacob Lloyd — rédigé avec l'aide de l'IA, une fois le projet terminé
Temps de lecture
12 min de lecture

En clair : L'histoire vraie de comment une seule personne, sans équipe de programmeurs, a dirigé des assistants IA pour construire une vraie plateforme de licences en ligne pour une association. L'article explique la méthode de travail : des plans écrits clairs, plusieurs couches de vérification, et une règle absolue — rien ne passe en production sans validation humaine. Un plan concret pour construire un logiciel sérieux avec l'aide de l'IA.

Voici la boucle exacte que j'ai utilisée pour faire construire OMLA — le backend de licences de redevances d'une association — presque entièrement par des agents IA de codage. Piquez le processus. Le backend terminé n'est qu'une preuve que ça marche.

tl;dr

  • Ce que c'est : le workflow qui a fait construire par Claude Code et un essaim d'agents IA locaux un vrai backend d'association, avec moi dans le rôle d'architecte et de gardien, pas de simple dactylo.
  • Ce que ça coûte : quasiment rien de nouveau. Un abonnement à un agent de codage, éventuellement un GPU qui traîne pour les seconds avis. Le vrai coût, c'est votre propre temps de relecture.
  • Ce qu'il vous faut : un agent de codage, de vraies spécifications écrites plutôt que du feeling, et une règle stricte : rien ne se déploie tant qu'un humain n'a pas tapé le mot.
  • Ce que vous obtenez : un backend fonctionnel avec une traçabilité complète. Des audits en couches, des tests contre une vraie stack locale, et un script de déploiement qui refuse de livrer sans un « GO » humain.

Ce qu'on obtient

Avant tout mode d'emploi, voici ce qui est sorti de l'autre bout : OMLA, l'Open Model Licensing Association. Un backend de licences de redevances et un site public pour les modèles d'IA ouverts.

QuoiLe résultat
StatutEn cours de constitution en association à but non lucratif dans l'État de Washington, 501(c)(3) en attente. Le site affiche volontairement le badge « Beta 0.9.0 ».
Le principeGratuit pour un usage personnel, de recherche ou éducatif. L'usage commercial doit 30 % du montant le plus élevé entre le revenu attribuable et le coût d'exécution du modèle.
Base de données12 tables principales, Postgres 17 sur Supabase, sécurité au niveau des lignes partout, calculs monétaires en centimes entiers (jamais de flottants)
IdentitéSignatures hybrides ed25519 + ML-DSA-65, classique plus post-quantique, pour qu'une future faille quantique ne casse pas le système. Pas de signature vérifiée, pas de relevé de redevances.
Piste d'auditAjout seul, chaînée par hachage SHA-256, vérifiée toutes les heures, avec une « ancre » quotidienne de l'extrémité de la chaîne stockée entièrement en dehors de la base de données
FrontendEnviron 30 pages HTML statiques toutes simples, sans framework, en 13 langues, sur un hébergement mutualisé ordinaire
Qui l'a construitUn humain qui dirige la circulation, Claude Code qui écrit le code, un essaim d'agents à modèles ouverts en local qui fait les passes de relecture et les tâches ingrates
Où ça en estEntièrement mis en préproduction et testé. Le déploiement reste en dry-run tant que je ne tape pas moi-même « GO ».

Chronologie approximative, au cas où vous penseriez que ça s'est fait en un week-end :

  • Fin mai 2026 — pivot d'un projet perso de chatbot vers une association de licences, plus le schéma initial à 12 tables
  • Quelques jours après — durcissement des signatures post-quantiques, les fonctions edge du chemin monétaire, et la réécriture « sans dépôt de fonds » (détails plus bas)
  • Début juin — backend poussé en production (la base de données, pas le frontend public)
  • 16 juin — réécriture juridique terminée, la License v1.0 entre en vigueur
  • 2 juillet — refonte complète du site et du backend, et retraduction en 13 langues, mise en préproduction et testée, toujours en attente de mon « GO » au moment où j'écris ces lignes

Anecdote sur l'origine du nom : OMLA signifiait à l'origine « Open Machine Learning Assistant », un projet perso de chatbot. Les choix d'infrastructure sont passés tels quels au pivot vers les licences ; le chatbot a été rétrogradé en assistant de documentation au périmètre verrouillé, désactivé volontairement en production. Transformer un chatbot amateur en infrastructure de licences, c'est une dérive de périmètre tout à fait normale, non ?

La règle qui a rendu le reste possible

Une seule décision de conception a fait l'essentiel du travail, avant même qu'une ligne de code ne soit écrite : OMLA ne déplace, ne détient, ni ne transmet jamais d'argent. Le système calcule ce qui est dû et publie le portefeuille ou les coordonnées de paiement que le bénéficiaire a lui-même fournis. L'utilisateur commercial paie le créateur directement, de pair à pair. Pas de traitement de paiement, pas de séquestre, pas de « solde OMLA ». Les identifiants de portefeuille ne sont que des informations d'acheminement, pas des comptes.

Le texte de la licence le dit sans détour : « OMLA publishes; OMLA does not pay. » (OMLA publie ; OMLA ne paie pas.)

Pourquoi se donner cette peine ? Être un transmetteur d'argent est un cauchemar réglementaire à part entière, en plus de celui des licences. En évitant la détention de fonds, la charge KYC/sanctions/fiscalité retombe sur les deux parties qui transigent réellement — ce qui est de toute façon là où elle doit légalement se trouver. C'est cette seule décision qui a permis à un amateur solo de seulement tenter ce projet.

Ce n'était même pas la conception d'origine. En plein développement, le schéma sous-entendait encore une détention de fonds, si bien qu'une table littéralement nommée payments a été renommée en royalty_statements — toute une migration de colonnes, index, triggers, politiques RLS et enums, rien que pour empêcher la base de données de mentir sur ce qu'elle fait. L'ancien vocabulaire est quand même revenu me mordre plus tard (voir les pièges).

Voici le trajet que suit un dollar de redevance, sans qu'OMLA ne le détienne jamais.

L'étape 4 est le verrou : sans signature vérifiée à l'étape 1, jamais de relevé de redevances. OMLA publie le montant et le portefeuille, puis ça se joue entre deux autres parties — comme si je vous disais qu'un ami vous doit vingt dollars et que je vous laissais régler ça entre vous.

La boucle réelle : humain, agents, vérification, déploiement

Le workflow n'est pas compliqué. Il est juste strict sur qui fait quoi, et il se répète à chaque changement.

Ce qui compte, ce ne sont pas les cases, c'est qu'aucune case n'est sautée. Chaque changement commence par moi qui écris ce qui doit exister et pourquoi, pas un « rends ça meilleur ». Les agents rédigent en suivant cette spec, d'autres agents auditent le brouillon avant même que je le voie, et les tests tournent contre une infrastructure qui se comporte comme la production. Ce n'est qu'ensuite que je lis le diff.

Tout le backend tourne en local d'abord : une vraie stack Supabase, Postgres 17 plus des fonctions edge, sur ma propre machine dans des conteneurs sans root. La production n'est qu'une cible de déploiement, jamais la source de vérité. Si le local et le live ne sont pas d'accord, c'est le live qui a tort — jusqu'à ce que je livre.

Le niveau d'exigence vit dans la config du projet, pas seulement dans ma tête : « Audit-ready by default: ship work that can withstand external audit. » (Prêt pour l'audit par défaut : ne livrer que ce qui peut résister à un audit externe.) Tout part du principe qu'un relecteur indépendant, IA ou humain, viendra plus tard essayer d'y trouver des failles.

Tester contre une vraie stack, pas un mock

Rien de tout ça n'a d'importance si les tests vous mentent, donc ici rien ne se livre contre une base de données mockée. Tout tourne contre une vraie stack locale, jetable. Trois couches, toutes au vert avant qu'on ne s'approche du déploiement :

deno test                  # 13 unit tests for the edge functions
./test/run.sh              # isolated scratch DB per run, migrations w/ ON_ERROR_STOP,
                            # smoke tests (PQ signatures, wrong-key rejection, audit
                            # hash-chain, payout gate), RLS cross-tenant isolation,
                            # an adversarial suite, then rollback + reapply
./test/integration.sh      # the full money path against the live local stack:
                            # register → tampered payload rejected → wallet verify →
                            # splits → usage report → statement published → replay
                            # (asserts zero duplicate writes) → admin gate → compliance

Cette ligne « rejeu qui vérifie zéro écriture en double » compte plus qu'il n'y paraît. Les rapports d'usage viennent des systèmes d'autres personnes, donc tôt ou tard l'un d'eux sera resoumis par accident ; le test vérifie que rejouer le même rapport produit zéro nouveau relevé, pas seulement que rien n'a planté. La suite a ensuite été durcie pour vérifier des résultats plutôt que de simplement les journaliser — ça semble pointilleux jusqu'au jour où vous déboguez un test qui « passe » sans rien faire.

Des audits qui notent d'autres audits

Une IA qui relit son propre travail, c'est un tampon en caoutchouc. Les audits sont donc volontairement empilés en couches : les agents de l'essaim local font une première passe de correction, puis un modèle plus puissant intervient comme verrou d'audit final et indépendant.

Ce verrou final n'a rien de cérémoniel. Lors d'une passe, il a vérifié les corrections des agents locaux et trouvé quand même un contournement de search_path, plus des lacunes de colonnes, de signature et de filiation que la première passe avait manquées. Cela a donné sa propre migration de durcissement : douze constats numérotés, chacun livré avec un test de non-régression pour qu'il ne puisse pas revenir en douce.

L'échelle correspond à l'ampleur du changement. Les changements de routine passent devant un panel de relecture à 3 agents. La refonte complète du frontend et de l'i18n a eu droit à une passe d'essaim de 24 agents, qui a repéré que le site avait dérivé (il décrivait encore l'ancien produit chatbot, pas le backend de licences qu'il était devenu) et a forcé une reconstruction pour coller à la réalité.

Le verrou de déploiement : dry-run jusqu'à ce que je le tape

Le script de déploiement n'a qu'un seul travail : s'assurer que rien au-dessus de cette ligne n'atteigne la production par accident. Dry-run par défaut, à chaque fois ; la production n'est touchée qu'avec un drapeau explicite :

./deploy.sh              # dry run (default) — shows exactly what WOULD happen
./deploy.sh --go         # only a human runs this, only when ready

Un wrapper guidé va encore plus loin : vous tapez littéralement « GO » avant toute étape qui modifie la production. Et la règle permanente pour les agents, écrite noir sur blanc pour cesser d'être une simple suggestion : ne jamais ajouter --go sans instruction humaine explicite.

D'autres garanties sont directement intégrées au script, pas seulement écrites sur le papier :

  • La synchronisation du frontend n'utilise jamais --delete, donc elle ne peut pas rayer de la carte des fichiers de l'hébergeur qu'elle ne gère pas
  • Une sauvegarde côté serveur est prise avant tout écrasement du frontend
  • Le backend refuse d'agir tant que le projet lié ne correspond pas au projet de production attendu — un garde-fou strict contre un push vers la mauvaise base de données
  • Seules des migrations additives sont jamais exécutées en remote, jamais de reset
  • La fonction du chatbot de documentation est exclue de chaque déploiement, par principe
  • Les secrets ne vivent que dans un fichier en mode 600 hors du dépôt ; une vérification en texte brut confirme que la clé de rôle de service n'apparaît jamais dans ce qui est livré

Honnêtement, le journal de déploiement de ce projet est surtout un carnet de mes dérobades : dry-run après dry-run, pendant qu'une refonte entièrement prête attendait le mot magique. Ce n'est pas un bug. C'est exactement l'effet que « verrouillé » est censé produire.

Pièges à éviter

Les parties qui ont fait mal :

  • L'octet de largeur nulle. Le SQL de migration doit être en ASCII pur. Un seul octet invisible non-ASCII peut casser un token de citation dollar $$ et fusionner silencieusement deux instructions SQL. La parade est devenue un rituel : grep les octets non-ASCII (il en faut zéro) et compter les tokens $$ (il en faut un nombre pair), à chaque fois.
  • Les signes pourcent mordent. Dans une chaîne de format RAISE EXCEPTION, %% est un pourcent littéral et chaque valeur a besoin d'exactement un %. Ça m'a mordu une fois, et c'est bien pour ça que c'est écrit ici maintenant.
  • SECURITY DEFINER a besoin d'un search_path épinglé. Chaque fonction definer et chaque trigger épingle son search_path pour que personne ne puisse masquer une table dans pg_temp et lui faire avaler de mauvaises données. Le test adverse compte les fonctions épinglées et échoue si le compte baisse.
  • Le superuser capricieux. Après un redémarrage mal propre, le rôle Postgres local revient parfois sans privilèges superuser. La solution, c'est un arrêt puis démarrage propre de la stack, pas du dépannage. Écrit noir sur blanc pour que les agents arrêtent de le redécouvrir.
  • L'i18n écrase silencieusement le HTML. La couche de traduction remplace entièrement le texte de la page, donc « corriger » le HTML peut quand même servir un texte périmé si le fichier de langue n'a pas été corrigé aussi. C'est exactement comme ça que l'ancien texte de l'époque « détention de fonds » a continué à réapparaître après avoir soi-disant été supprimé.
  • La chaîne d'audit peut être trompée — exprès, pour qu'on le sache. Un attaquant avec un contrôle total de la base de données pourrait supprimer le trigger de chaînage par hachage et reconstruire un faux historique auto-cohérent ; la vérification interne seule ne le détecterait pas. Un job quotidien ancre donc le hachage de l'extrémité de la chaîne entièrement hors de la base de données, et la suite de tests prouve les deux moitiés en une seule exécution : la vérification interne est trompée, l'ancre externe attrape quand même la falsification.
  • Le site en ligne n'est pas le site en préproduction. Au moment où j'écris ces lignes, le site public montre encore un peu de texte antérieur au réalignement (un ancien brouillon de licence, une carte obsolète sur les rails de paiement), parce que la refonte est en préproduction, pas déployée. Aussi, l'URL publique /about n'est qu'un index de répertoire nu ; la page de divulgation IA vit un niveau plus bas.

← Plus de IA et LLM locaux