URnexus est une plateforme de publication sociale où l'assistant n'est pas un chatbot posé à côté du produit : il agit à travers le même catalogue d'actions que les boutons de l'interface, et le même que le connecteur MCP qui permet de piloter la plateforme depuis un assistant externe. Une capacité déclarée une fois, exposée trois fois.
La façon courante d'ajouter de l'IA à un produit consiste à brancher un chat sur une API dédiée, écrite pour lui. Deux chemins de code coexistent alors pour la même capacité : celui des boutons, et celui de l'assistant.
Ce dédoublement coûte cher et se paie toujours au même endroit. Une règle métier ajoutée côté interface — un quota, une validation, une transition d'état interdite — n'existe pas côté assistant, qui continue d'accepter ce que le produit refuse. L'utilisateur reçoit une confirmation pour une action qui n'a pas eu lieu, ou pire : elle a lieu en contournant la règle.
Sur un produit multi-tenant, le même dédoublement touche l'isolation. Chaque nouvelle route écrite pour l'assistant est une occasion d'oublier le filtre qui empêche un client de voir les données d'un autre.
J'ai pris le problème dans l'autre sens : déclarer les capacités une seule fois, dans un module sans dépendance, et faire passer les trois surfaces par le même exécuteur.
Le catalogue décrit chaque action : son nom, son schéma d'entrée, les rôles autorisés, et sa classe d'effet. C'est un module pur — aucune base, aucun réseau, aucun import de framework — donc exhaustivement testable sans infrastructure.
Les boutons et formulaires du produit appellent l'exécuteur partagé. Ce que voit l'utilisateur est exactement ce que le catalogue déclare.
L'assistant présent sur chaque écran reçoit le catalogue comme liste d'outils. Il ne peut rien faire qui n'y soit déclaré.
8 outils exposés à un assistant externe, avec une clé par utilisateur. Le client pilote sa plateforme depuis son propre assistant.
↓ les trois passent par le même exécuteur, avec les mêmes contrôles de droits et de quotas ↓
Ce que ça change concrètement. Ajouter une capacité au produit, c'est une entrée dans le catalogue — et elle apparaît du même coup dans l'interface, dans l'assistant et dans le connecteur. Ajouter une règle métier, c'est un seul endroit à modifier. Aucune des trois surfaces ne peut dériver des deux autres, parce qu'aucune ne possède son propre chemin d'écriture.
Un agent qui agit a besoin qu'on lui dise où s'arrêter. Chaque action du catalogue porte une classe d'effet, et c'est elle — pas le modèle, pas le prompt — qui décide si une confirmation humaine est exigée.
lecture
Consulter des publications, lire les quotas ou la charte graphique. Jamais de confirmation, aucun coût à journaliser. L'agent se renseigne librement.
ecriture_interne
Enregistrer un brouillon, capturer une idée. Modifie une donnée du client, réversible, sans engagement extérieur. L'agent agit seul.
effet_externe
Programmer une publication, ou dépenser des crédits de génération. Confirmation humaine obligatoire : l'intention de publier est visible et son effet ne se rattrape pas.
Un assistant qui se trompe d'identifiant détruirait un travail irrécupérable. La capacité n'est simplement pas déclarée — ce n'est pas un contrôle qu'on peut contourner, c'est une action qui n'existe pas.
Publier au nom d'un client sur la base d'une consigne mal comprise est public et irréversible. Programmer reste permis : c'est un brouillon daté, relisible et annulable avant l'envoi réel.
Un post ne passe pas de brouillon à publié : il traverse programmé puis en cours. Un post publié ne revient jamais en arrière. Ces règles vivent dans le domaine, donc elles s'appliquent à l'agent comme au bouton — vérifié en tentant le raccourci, qui est refusé.
Le coût d'une génération varie d'un facteur six selon le modèle : une image rapide vaut 1 crédit, une Pro en 4K en vaut 6. Exposer un quota « en images » aurait rendu la facturation incompréhensible dès le premier changement de modèle.
Décision de conception tenue sur tout le produit : chaque écran est une grille de blocs déplaçables, et l'assistant est l'un d'eux — à la même place sur tous les écrans. Il ne surgit pas d'un tiroir latéral sur certaines pages pour disparaître ailleurs : il est toujours là, au même endroit, et c'est ce qui en fait un outil de travail plutôt qu'une fonctionnalité qu'on va chercher.





Sur un produit multi-tenant, la question qui compte n'est pas « est-ce que mon code est juste » mais « est-ce qu'un client peut voir les données d'un autre ». Cette question-là ne se vérifie pas en test unitaire, où les deux côtés de la comparaison sont écrits par la même main.
Chaque frontière d'isolation est donc éprouvée contre l'API réelle, en HTTPS public, authentification et règles d'accès comprises : 17 assertions couvrant la lecture croisée entre deux clients, l'accès direct par identifiant et les tentatives d'élévation de privilèges. Toutes passent.
Un détail de conception y est visible : un accès à une ressource d'un autre client répond 404 et non 403. Répondre « interdit » confirmerait que la ressource existe, et transformerait l'API en annuaire de la clientèle.
Ce que je retiens de ce projet. La partie difficile d'un produit à base d'agents n'est pas de faire répondre un modèle : c'est de décider ce qu'il a le droit de faire, de l'écrire à un seul endroit, et de prouver qu'il ne peut pas en sortir. Les trois quarts du travail utile sont dans le catalogue d'actions et les classes d'effet — pas dans les prompts.