URnexus · SaaS multi-tenant · conception et développement

L'agent IA utilise les mêmes outils que l'interface

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.

RôleConception produit, UX/UI, développement, exploitation
ContexteProduit propre, en production sur app.yournexus.fr
PériodeDepuis octobre 2026 · 406 commits
SoclePayload 3 · Next 16 · PostgreSQL · R2
3surfaces, 1 catalogueUI · agent · MCP
1 030cas de test80 fichiers
12recettes sur l'API réelleHTTP, pas de mock
5languesfr · en · es · de · it
Le problème

Un assistant greffé finit par mentir

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.

La décision structurante

Un catalogue d'actions, trois consommateurs

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.

Interface

Les boutons et formulaires du produit appellent l'exécuteur partagé. Ce que voit l'utilisateur est exactement ce que le catalogue déclare.

Agent intégré

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é.

Connecteur MCP

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.

Les garde-fous

Toutes les actions ne se valent pas

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.

Le produit

L'assistant est un bloc, pas un panneau

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.

Tableau de bord URnexus : bloc assistant à gauche avec suggestions d'actions, trois cartes d'action rapide, bandeau de quotas mensuels (publications, crédits IA, carrousels), listes des prochaines publications et des brouillons à finir.
Le tableau de bord. L'assistant occupe la colonne de gauche avec des suggestions qui sont de vraies actions du catalogue. Le bandeau de quotas montre la consommation du mois : les carrousels approchent du plafond, et la barre passe en alerte sans attendre le refus.
Comment je le sais

Les tests unitaires ne prouvent pas l'isolation

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.

17assertions d'isolationsur l'API publique
18collections+ 28 migrations
46routes API1 exécuteur partagé
182cas sur l'IAagent, MCP, rédaction

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.

Payload 3Next 16React 19TypeScript PostgreSQLCloudflare R2StripeMCP Satori + resvgGeminiUnipilesystemd