<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Blog Citios — Insights tech &amp; produit</title><description>Analyses concrètes sur l&apos;architecture, le produit et la direction technique par Sébastien Quéré, CTO freelance.</description><link>https://citios.fr/</link><language>fr-fr</language><atom:link href="https://citios.fr/rss.xml" rel="self" type="application/rss+xml"/><item><title>REX : une après-midi pour rendre le design de Citios Impeccable</title><link>https://citios.fr/blog/rex-impeccable-citios-projet-dete/</link><guid isPermaLink="true">https://citios.fr/blog/rex-impeccable-citios-projet-dete/</guid><description>Entre deux baignades (enfin, surtout entre deux onglets Cursor), j’ai traité mon site comme un vrai produit design : règles écrites, audits, contrastes, détails d’accessibilité. Voici le récit — pour comprendre le principe, sans slide deck.</description><pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;L’été, en théorie, c’est la plage. En pratique, c’est souvent le moment où un CTO freelance regarde son propre site et se dit : &lt;em&gt;« OK… ça tient la route, mais est-ce que j’ai vraiment des règles de design, ou juste des habitudes ? »&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Citios tourne bien. Pages services, blog, formulaire, cookies — le socle est sérieux. Mais le design avait grandi par couches : un hero un peu flamboyant, du vert d’eau partout, des contrastes « ça passe sur mon écran », une navigation clavier inégale. Bref, un site de CTO… qui n’avait jamais vraiment eu son atelier design dédié.&lt;/p&gt;
&lt;p&gt;Alors j’ai pris Impeccable (un assistant design dans Cursor), un café, et j’ai traité &lt;strong&gt;citios.fr&lt;/strong&gt; comme un petit projet produit d’été. Pas une refonte glossy d’agence. Un passage en revue méthodique : documenter, auditer, corriger, recommencer.&lt;/p&gt;
&lt;p&gt;Voici le récit — pour comprendre le principe, même si tu n’écris pas une ligne de code.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cest-quoi-impeccable--et-comment-linstaller&quot;&gt;C’est quoi Impeccable ? (et comment l’installer)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/pbakaus/impeccable&quot;&gt;Impeccable&lt;/a&gt;, c’est un &lt;strong&gt;outil open source&lt;/strong&gt; pour les assistants de code (Cursor, Claude Code, etc.). L’idée : au lieu de demander vaguement « rends ça plus beau » — et de récupérer le fameux look IA (dégradé violet, cartes partout, même police partout) — tu disposes d’un &lt;strong&gt;vocabulaire de design&lt;/strong&gt; en commandes : audit, typo, accessibilité, polish, et d’autres. Derrière, des guides + un détecteur de mauvaises habitudes.&lt;/p&gt;
&lt;p&gt;En gros : ça transforme l’assistant en directeur design un peu tatillon, avec un brief écrit (&lt;code&gt;PRODUCT.md&lt;/code&gt;, &lt;code&gt;DESIGN.md&lt;/code&gt;) plutôt qu’en décorateur aléatoire.&lt;/p&gt;
&lt;h3 id=&quot;installation-si-tu-bosses-dans-cursor&quot;&gt;Installation (si tu bosses dans Cursor)&lt;/h3&gt;
&lt;p&gt;À la racine du projet :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; impeccable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;L’outil détecte ton éditeur, propose une install &lt;strong&gt;projet&lt;/strong&gt; ou &lt;strong&gt;globale&lt;/strong&gt;, et dépose le skill au bon endroit. Tu peux ensuite lancer un audit sur une page, par exemple &lt;code&gt;/impeccable audit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La doc à jour : &lt;a href=&quot;https://github.com/pbakaus/impeccable&quot;&gt;github.com/pbakaus/impeccable&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;le-point-de-départ--un-site-qui--marche--pas-un-système&quot;&gt;Le point de départ : un site qui « marche », pas un système&lt;/h2&gt;
&lt;p&gt;Avant Impeccable, Citios avait déjà une identité : bleu marine, vert d’eau (teal), fond clair froid, hero sombre façon terminal. Ça collait à l’offre (CTO hands-on, pas SaaS violet).&lt;/p&gt;
&lt;p&gt;Ce qui manquait :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Une intention écrite&lt;/strong&gt; — pas juste « j’aime ce bleu »&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Des règles claires&lt;/strong&gt; — boutons, contraste, pages claires vs pages sombres&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un filet de sécurité&lt;/strong&gt; — audits chiffrés, priorités (urgent / important / plus tard)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Des faits produit ancrés&lt;/strong&gt; — 25 ans dans la tech, 15 ans CTO, 5 postes CTO/DT, pas des chiffres inventés&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sans ça, chaque session avec un assistant, c’est la loterie : il « embellit », tu te retrouves avec des pastilles arrondies et un dégradé indigo. Classic.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;acte-1--écrire-avant-de-peindre&quot;&gt;Acte 1 — Écrire avant de peindre&lt;/h2&gt;
&lt;p&gt;Impeccable pousse deux documents que je recommande à quiconque bosse avec un assistant sur l’UI :&lt;/p&gt;
&lt;h3 id=&quot;productmd--le-vrai-du-faux&quot;&gt;PRODUCT.md — le vrai du faux&lt;/h3&gt;
&lt;p&gt;Qui décide, pour qui, quoi promettre, quoi &lt;strong&gt;ne pas inventer&lt;/strong&gt;. Pour Citios : fondateur non-tech en early-stage en priorité, conversion = conversation (formulaire / prise de rendez-vous), faits d’expérience figés. Zéro faux témoignage client, zéro chiffre magique.&lt;/p&gt;
&lt;p&gt;Ça a l’air administratif. En vrai, c’est ce qui empêche l’IA de remplir le site avec du marketing creux.&lt;/p&gt;
&lt;h3 id=&quot;designmd--la-carte-didentité-visuelle&quot;&gt;DESIGN.md — la carte d’identité visuelle&lt;/h3&gt;
&lt;p&gt;On a ancré :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Intention&lt;/strong&gt; : &lt;em&gt;« La Passerelle Fondateur ↔ Tech »&lt;/em&gt; — lisible pour un CEO non-tech, crédible pour quelqu’un qui code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Couleurs&lt;/strong&gt; : Marine Atlantique + Teal Signal (le vert d’eau de marque)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Règles&lt;/strong&gt; : ombres douces, typo lisible d’abord, coins discrets, &lt;strong&gt;pas&lt;/strong&gt; de blanc sur teal vif pour les boutons principaux&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ce qu’on refuse&lt;/strong&gt; : SaaS violet, néon startup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Photographier l’existant avant de le juger aide aussi : on ne redessine pas un site qu’on n’a pas encore décrit.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;acte-2--le-choc-du-premier-audit&quot;&gt;Acte 2 — Le choc du premier audit&lt;/h2&gt;
&lt;p&gt;Premier audit Impeccable : &lt;strong&gt;12/20&lt;/strong&gt;. Acceptable, pas fier.&lt;/p&gt;
&lt;p&gt;La claque principale était le &lt;strong&gt;contraste&lt;/strong&gt; : texte blanc sur le teal de marque. Tous les boutons primaires flamboyants… difficiles à lire. Le genre de truc que tu ne vois plus quand tu es trop habitué à ton propre branding.&lt;/p&gt;
&lt;p&gt;Autres alertes classiques :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Couleurs « officielles » d’un côté, couleurs bricolées un peu partout de l’autre&lt;/li&gt;
&lt;li&gt;Animations du hero qui bougent même quand on préfère moins de mouvement&lt;/li&gt;
&lt;li&gt;Navigation au clavier incomplète&lt;/li&gt;
&lt;li&gt;Chiffres produit à clarifier (on a figé 25 ans / 15 ans CTO / 5 postes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verdict : le site ressemblait déjà à Citios, mais les règles fuyaient.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;acte-3--le-problème-du-teal-trop-beau&quot;&gt;Acte 3 — Le problème du teal trop beau&lt;/h2&gt;
&lt;p&gt;Garder le teal de marque &lt;strong&gt;et&lt;/strong&gt; des boutons lisibles, c’est un vrai casse-tête. Options explorées :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Teal vif + texte marine&lt;/strong&gt; — le bouton reste marque, mais on abandonne le blanc sur teal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fond pastel + texte plus foncé&lt;/strong&gt; — même famille de couleur, bien plus lisible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bouton contour&lt;/strong&gt; — transparent + bordure teal&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On a choisi la &lt;strong&gt;option 2&lt;/strong&gt; pour les actions principales. Le teal vif reste l’accent (bordures, hero sombre, soulignements). Sur le fond clair, on n’écrit plus avec le teal flashy : trop faible. On passe par une teinte plus sombre, prévue pour le texte.&lt;/p&gt;
&lt;p&gt;Leçon d’été n°1 : &lt;strong&gt;une couleur de marque n’est pas automatiquement une couleur de texte.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;acte-4--une-suite-de-passes-pas-un-bouton-magique&quot;&gt;Acte 4 — Une suite de passes, pas un bouton magique&lt;/h2&gt;
&lt;p&gt;Impeccable, ce n’est pas « rends tout joli ». C’est une &lt;strong&gt;suite de passes&lt;/strong&gt; avec des jobs différents. Chez moi, ça a ressemblé à ça :&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Passe&lt;/th&gt;&lt;th&gt;En clair&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Typo&lt;/td&gt;&lt;td&gt;Hiérarchie, interlignage, poids de police plus lisibles&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Durcir&lt;/td&gt;&lt;td&gt;Contrastes, focus clavier, formulaire, cookies, zones tactiles&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Images &amp;amp; polices&lt;/td&gt;&lt;td&gt;Images mieux servies, polices allégées (la « fancy » surtout au hero)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Header&lt;/td&gt;&lt;td&gt;Plus bas → moins de chrome, plus de contenu&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Polish&lt;/td&gt;&lt;td&gt;Détails finaux, messages de succès, stabilité des images blog&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Re-audit&lt;/td&gt;&lt;td&gt;Remesurer, classer ce qui reste&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Correctifs ciblés&lt;/td&gt;&lt;td&gt;Boutons lisibles sur fond clair, fil d’Ariane sombre, Escape sur les cookies, lien « Aller au contenu »&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Score en fin de parcours (à date) : &lt;strong&gt;17/20&lt;/strong&gt;. Pas un 20/20 marketing — un 17 honnête, avec encore des petits chantiers ouverts.&lt;/p&gt;
&lt;p&gt;Leçon d’été n°2 : &lt;strong&gt;auditer sans tout corriger d’un coup, puis corriger sans tout réécrire.&lt;/strong&gt; Résister à l’envie de « tout refaire from scratch » entre deux baignades.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;acte-5--amplifier-une-page-sans-tout-changer&quot;&gt;Acte 5 — Amplifier une page, sans tout changer&lt;/h2&gt;
&lt;p&gt;Après les audits transverses, j’ai voulu un cas &lt;strong&gt;ciblé&lt;/strong&gt;. La commande &lt;code&gt;bolder&lt;/code&gt;, ce n’est pas « plus d’effets partout » : c’est &lt;em&gt;une&lt;/em&gt; surface, avec le vocabulaire déjà décidé, et le reste intouché.&lt;/p&gt;
&lt;p&gt;La demande : rendre la page contact plus affirmée.&lt;/p&gt;
&lt;h3 id=&quot;ce-qui-était-mou&quot;&gt;Ce qui était mou&lt;/h3&gt;
&lt;p&gt;La page contact tenait debout, mais elle &lt;strong&gt;n’utilisait pas&lt;/strong&gt; les coups forts du système : pas d’accent teal sur le titre, hero un peu plat, lien FAQ qui détournait de la conversion, titre « Me contacter » mal placé dans le bleu.&lt;/p&gt;
&lt;h3 id=&quot;le-geste-décisif&quot;&gt;Le geste décisif&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Hero marine + lueur teal, titre avec accent &lt;em&gt;votre projet&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Formulaire seul qui « monte » dans le hero (sur grand écran) — les autres moyens de contact restent sur le fond clair&lt;/li&gt;
&lt;li&gt;FAQ sortie du hero (rangée dans le pied de page) — la page contact doit convertir, pas renvoyer lire&lt;/li&gt;
&lt;li&gt;Claims inchangés (25 ans / 15 ans CTO)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tu peux comparer les deux états ci-dessous (glisser le curseur) :&lt;/p&gt;
&lt;figure class=&quot;ba-slider&quot; data-ba-slider=&quot;&quot; id=&quot;ba-atap5d8&quot;&gt; &lt;p class=&quot;visually-hidden&quot;&gt;Comparaison avant / après — faites glisser le curseur&lt;/p&gt; &lt;div class=&quot;ba-slider-track&quot; data-ba-track=&quot;&quot;&gt; &lt;img class=&quot;ba-slider-img ba-slider-img--after&quot; src=&quot;https://citios.fr/_astro/contact-bolder-apres.DzOmwn20_Z2uwAjw.webp&quot; alt=&quot;Page contact Citios après — hero plus affirmé, formulaire qui chevauche le hero, méthodes sur fond clair&quot; width=&quot;1200&quot; height=&quot;750&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; draggable=&quot;false&quot;&gt; &lt;div class=&quot;ba-slider-before-wrap&quot; data-ba-before=&quot;&quot; style=&quot;width: 50%;&quot;&gt; &lt;img class=&quot;ba-slider-img ba-slider-img--before&quot; src=&quot;https://citios.fr/_astro/contact-bolder-avant.Cx2bYEd3_Z1y55Xl.webp&quot; alt=&quot;Page contact Citios avant — hero plat, lien FAQ et titre Me contacter mal placé&quot; width=&quot;1200&quot; height=&quot;750&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; draggable=&quot;false&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ba-slider-handle&quot; data-ba-handle=&quot;&quot; style=&quot;left: 50%;&quot; aria-hidden=&quot;true&quot;&gt; &lt;span class=&quot;ba-slider-handle-line&quot;&gt;&lt;/span&gt; &lt;span class=&quot;ba-slider-handle-knob&quot;&gt;&lt;/span&gt; &lt;/div&gt; &lt;span class=&quot;ba-slider-badge ba-slider-badge--before&quot;&gt;Avant&lt;/span&gt; &lt;span class=&quot;ba-slider-badge ba-slider-badge--after&quot;&gt;Après&lt;/span&gt; &lt;/div&gt; &lt;label class=&quot;ba-slider-range-label&quot;&gt; &lt;span class=&quot;visually-hidden&quot;&gt;Position du comparateur&lt;/span&gt; &lt;input type=&quot;range&quot; class=&quot;ba-slider-range&quot; data-ba-range=&quot;&quot; min=&quot;0&quot; max=&quot;100&quot; value=&quot;50&quot; aria-valuemin=&quot;0&quot; aria-valuemax=&quot;100&quot; aria-valuenow=&quot;50&quot;&gt; &lt;/label&gt; &lt;/figure&gt;  
&lt;p&gt;&lt;em&gt;(Captures d’écran réelles de la page contact — avant et après.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Leçon d’été n°3 : &lt;strong&gt;amplifier = une section qui rejoint le niveau d’expression que le reste du site a déjà&lt;/strong&gt;, pas une nouvelle identité.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;ce-qui-a-vraiment-changé-sur-le-site-concret&quot;&gt;Ce qui a vraiment changé sur le site (concret)&lt;/h2&gt;
&lt;h3 id=&quot;deux-registres-une-marque&quot;&gt;Deux registres, une marque&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pages claires&lt;/strong&gt; : police sobre, marine, ombres douces, boutons pastel lisibles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hero d’accueil&lt;/strong&gt; : ambiance terminal, teal qui « pop » sur fond sombre (là, le contraste passe)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Même marque, deux ambiances. Documenté pour que le prochain assistant ne clone pas le terminal sur chaque page service.&lt;/p&gt;
&lt;h3 id=&quot;accessibilité-sans-théâtre&quot;&gt;Accessibilité sans théâtre&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Boutons principaux assez contrastés pour être lus confortablement&lt;/li&gt;
&lt;li&gt;Lien « Aller au contenu » branché sur &lt;strong&gt;toutes&lt;/strong&gt; les pages (on en avait oublié deux)&lt;/li&gt;
&lt;li&gt;Bannière cookies : Escape = refus, focus remis au bon endroit&lt;/li&gt;
&lt;li&gt;Fil d’Ariane lisible sur fond sombre&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Petit détail : le lien « Aller au contenu », personne ne le voit… jusqu’au premier &lt;code&gt;Tab&lt;/code&gt;. Puis il devient évident pourquoi c’était prioritaire.&lt;/p&gt;
&lt;h3 id=&quot;plus-léger-plus-soigné&quot;&gt;Plus léger, plus soigné&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Images mieux gérées&lt;/li&gt;
&lt;li&gt;Moins de variantes de polices inutiles&lt;/li&gt;
&lt;li&gt;Animations du hero respectueuses de « moins de mouvement » quand c’est demandé&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;copy--faits&quot;&gt;Copy &amp;amp; faits&lt;/h3&gt;
&lt;p&gt;Pas de « 15+ années » floues : &lt;strong&gt;25 ans tech / 15 ans CTO / 5 expériences CTO-DT&lt;/strong&gt;. Des belles règles de design ne sauvent pas un chiffre faux.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;ce-que-jai-appris-sur-le-duo-cto--assistant-design&quot;&gt;Ce que j’ai appris sur le duo CTO + assistant design&lt;/h2&gt;
&lt;h3 id=&quot;1-le-brief-gagne-toujours&quot;&gt;1. Le brief gagne toujours&lt;/h3&gt;
&lt;p&gt;Si PRODUCT.md et DESIGN.md sont clairs, l’assistant &lt;strong&gt;affine&lt;/strong&gt;. S’ils sont vagues, il &lt;strong&gt;improvise&lt;/strong&gt; — et l’improvisation IA, en design marketing, ça ressemble à tout le monde.&lt;/p&gt;
&lt;h3 id=&quot;2-le-détecteur-se-trompe-parfois&quot;&gt;2. Le détecteur se trompe parfois&lt;/h3&gt;
&lt;p&gt;Il signalait des couleurs « hors palette » : les pastilles rouge/jaune/vert du chrome de fenêtre façon macOS. Normal. Il faut &lt;strong&gt;vérifier en contexte&lt;/strong&gt;, pas tout « corriger » bêtement.&lt;/p&gt;
&lt;h3 id=&quot;3-les-petits-chantiers-restants-ne-sont-pas-des-échecs&quot;&gt;3. Les petits chantiers restants ne sont pas des échecs&lt;/h3&gt;
&lt;p&gt;Animations encore trop présentes hors hero, quelques tailles de titre hors grille… Ce sont des dettes assumées. Un projet d’été a une fin ; un score 17/20 avec une liste claire vaut mieux qu’un 20/20 fantôme.&lt;/p&gt;
&lt;h3 id=&quot;4-le-design-sert-la-conversation-pas-lego&quot;&gt;4. Le design sert la conversation, pas l’ego&lt;/h3&gt;
&lt;p&gt;Intention &lt;em&gt;Passerelle Fondateur ↔ Tech&lt;/em&gt; = chaque choix doit aider un fondateur non-tech à &lt;strong&gt;comprendre l’offre&lt;/strong&gt; et &lt;strong&gt;prendre un créneau&lt;/strong&gt;. Si le hero est trop « insider », on perd. Si le bouton est illisible, on perd. Le reste, c’est du soin.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;pour-qui-ça-vaut-le-coup-de-reproduire&quot;&gt;Pour qui ça vaut le coup de reproduire ?&lt;/h2&gt;
&lt;p&gt;Si tu as :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un site marketing déjà en ligne,&lt;/li&gt;
&lt;li&gt;une identité visuelle « à peu près là »,&lt;/li&gt;
&lt;li&gt;et l’habitude de bosser avec Cursor (ou un assistant équivalent),&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…une semaine d’été (ou deux week-ends) sur &lt;strong&gt;écrire les règles → auditer → durcir → ré-auditer&lt;/strong&gt; change plus la qualité perçue qu’une couche de vernis.&lt;/p&gt;
&lt;p&gt;Si tu démarres de zéro, Impeccable a aussi des modes pour inventer. Là, on était clairement en mode &lt;strong&gt;affinage&lt;/strong&gt; : garder Citios, enlever le flou.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;et-maintenant&quot;&gt;Et maintenant ?&lt;/h2&gt;
&lt;p&gt;Le site est plus cohérent, plus lisible, un peu plus rapide à charger, et surtout &lt;strong&gt;plus honnête&lt;/strong&gt; avec ses propres règles. Les petits chantiers restants peuvent attendre le prochain week-end pluvieux (ou la prochaine session entre deux appels clients).&lt;/p&gt;
&lt;p&gt;Si tu veux voir le résultat : &lt;a href=&quot;https://citios.fr/&quot;&gt;citios.fr&lt;/a&gt;. Si tu veux discuter stack, design ou CTO à la demande — &lt;a href=&quot;https://citios.fr/contact/&quot;&gt;on en parle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bon été — et bon audit.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-08-07</atom:updated><readingTime>12 min</readingTime><metaDescription>Retour d’expérience sur une mise au propre du design Citios avec Impeccable (Cursor) : règles écrites, lisibilité des boutons, accessibilité, score d’audit 12→17/20.</metaDescription><category>Outillage IA</category><category>Retour d&apos;expérience</category><category>Impeccable</category><category>Design</category><category>Accessibilité</category><category>Cursor</category><category>REX</category><category>Citios</category><category>CTO à la demande</category><enclosure url="https://citios.fr/_astro/rex-impeccable-citios-ete.CAJ8Lbgp_vLive.jpg" length="1707200" type="image/jpeg"/></item><item><title>Le Harness en IA — Transformer un modèle brut en agent autonome et fiable</title><link>https://citios.fr/blog/le-harness-ia-agents-autonomes/</link><guid isPermaLink="true">https://citios.fr/blog/le-harness-ia-agents-autonomes/</guid><description>Le harness, c&apos;est tout ce qui entoure un modèle d&apos;IA pour le rendre opérationnel et maîtrisé. Retour d&apos;expérience avec BB-DEV et BB-LOG : déterminisme, tools, cron et garde-fous.</description><pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Un modèle d’IA brut, même très capable, reste une boîte noire. Ce qui le transforme en &lt;strong&gt;agent&lt;/strong&gt; utile en production, ce n’est pas seulement un meilleur prompt : c’est tout ce qu’on construit &lt;em&gt;autour&lt;/em&gt; — scripts, outils, planifications, APIs, garde-fous. C’est ça qu’on appelle le &lt;strong&gt;harness&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Sur mes projets, ce concept structure &lt;a href=&quot;https://citios.fr/blog/agent-bb-dev-claude-code-developpement-autonome/&quot;&gt;BB-DEV&lt;/a&gt; (développement autonome) et &lt;a href=&quot;https://citios.fr/blog/agent-bb-log-claude-code-monitoring-autonome/&quot;&gt;BB-LOG&lt;/a&gt; (monitoring autonome). Voici comment je le vois, et pourquoi l’équilibre entre IA générative et déterminisme change tout.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-1--quest-ce-quun-harness&quot;&gt;Partie 1 — Qu’est-ce qu’un harness ?&lt;/h2&gt;
&lt;h3 id=&quot;plus-quun-wrapper-autour-dun-llm&quot;&gt;Plus qu’un wrapper autour d’un LLM&lt;/h3&gt;
&lt;p&gt;Le harness, c’est l’écosystème déterministe qui entoure le modèle : tout ce qui définit &lt;em&gt;quand&lt;/em&gt; l’agent tourne, &lt;em&gt;quoi&lt;/em&gt; il a le droit de faire, &lt;em&gt;comment&lt;/em&gt; il récupère le contexte, et &lt;em&gt;où&lt;/em&gt; il pose le résultat.&lt;/p&gt;
&lt;p&gt;Sur le schéma ci-dessus, le « cerveau » au centre est le modèle. Autour, les modules du harness :&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Module&lt;/th&gt;&lt;th&gt;Rôle&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tools&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Actions exposées au modèle (lire un fichier, chercher dans le code…)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mcp&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Connexions vers des plateformes externes (GitHub, Sentry…)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;api&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Interfaces machine-to-machine contrôlées&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;cron&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Déclenchement planifié, sans intervention humaine&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;scripts&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Étapes déterministes (branche Git, PR, rapport docx…)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;webapp&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Interface humaine pour piloter, valider, consulter&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sans ces briques, on a un chat intelligent. Avec, on a un agent qui s’insère dans un workflow métier.&lt;/p&gt;
&lt;h3 id=&quot;pourquoi-cest-indispensable&quot;&gt;Pourquoi c’est indispensable&lt;/h3&gt;
&lt;p&gt;Un modèle générique est polyvalent — et c’est aussi son risque. Il peut dériver, inventer, sortir du cadre. Le harness force la spécialisation : chaque agent a une mission bornée, des permissions limitées, et des étapes critiques qui ne passent &lt;em&gt;pas&lt;/em&gt; par la génération libre.&lt;/p&gt;
&lt;p&gt;La tendance que je constate : on passe d’agents « fourre-tout » branchés sur plein de MCP à des agents &lt;strong&gt;spécialisés&lt;/strong&gt;, chacun avec un harness taillé pour une tâche précise.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-2--cas-pratique--le-harness-de-bb-dev&quot;&gt;Partie 2 — Cas pratique : le harness de BB-DEV&lt;/h2&gt;
&lt;h3 id=&quot;la-mission&quot;&gt;La mission&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://citios.fr/blog/agent-bb-dev-claude-code-developpement-autonome/&quot;&gt;BB-DEV&lt;/a&gt; traite des tickets GitHub tagués &lt;code&gt;BB-DEV&lt;/code&gt; de bout en bout — correctifs bornés, features simples, mises à jour de contenu — sans décision architecturale ni négociation client.&lt;/p&gt;
&lt;h3 id=&quot;ce-qui-est-déterministe-ce-qui-ne-lest-pas&quot;&gt;Ce qui est déterministe, ce qui ne l’est pas&lt;/h3&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Étape&lt;/th&gt;&lt;th&gt;Qui décide&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Sélection des tickets tagués &lt;code&gt;BB-DEV&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Script Python (déterministe)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Génération du code&lt;/td&gt;&lt;td&gt;Claude Code en CLI autonome (IA)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Création de branche, push, ouverture de PR&lt;/td&gt;&lt;td&gt;Scripts / processus Git (déterministe)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;L’IA produit le code. Le harness garantit que ce code s’insère dans un workflow de développement classique : même conventions de branche, même revue via PR, même périmètre que j’ai volontairement ouvert avec le tag.&lt;/p&gt;
&lt;p&gt;Pourquoi c’était important d’avoir une partie déterministe ? Il y a 3 semaines, j’ai eu un sérieux coup de chaud.
Alors que je travaillais avec Cursor pour construire un nouveau Workflow GitHub, je m’aperçois qu’il a décidé tout seul de pousser mon code sur GitHub.
Il s’excuse, il me dit qu’il a estimé que comme le travail concernait un Workflow, mes instructions laissaient penser que je l’avais autorisé.
Rien de grave, le workflow était opérationnel. Mais cela m’a fait prendre conscience du besoin de limiter l’espace donné à l’agent.&lt;/p&gt;
&lt;h3 id=&quot;le-résultat&quot;&gt;Le résultat&lt;/h3&gt;
&lt;p&gt;En un mois, &lt;strong&gt;48 tickets&lt;/strong&gt; traités sans intervention humaine. Le coût de « réentrée » sur les petites tâches disparaît : le développement continue en arrière-plan pendant que je me concentre sur l’architecture et le client.&lt;/p&gt;
&lt;p&gt;Et tout ça avec la garantie que le processus de travail de l’équipe sera respecté.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-3--cas-pratique--le-harness-de-bb-log&quot;&gt;Partie 3 — Cas pratique : le harness de BB-LOG&lt;/h2&gt;
&lt;h3 id=&quot;la-mission-1&quot;&gt;La mission&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://citios.fr/blog/agent-bb-log-claude-code-monitoring-autonome/&quot;&gt;BB-LOG&lt;/a&gt; audite les erreurs Sentry et crée des tickets GitHub actionnables.
Objectif : passer d’un monitoring passif (mail hebdo) à un traitement proactif.&lt;/p&gt;
&lt;h3 id=&quot;la-structure-du-harness&quot;&gt;La structure du harness&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cron hebdomadaire&lt;/strong&gt; — déclenchement déterministe, pas d’oubli.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Sentry / Posthog&lt;/strong&gt; — récupération des erreurs avec contexte (fréquence, impact, gravité, évolution, récidive).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tickets GitHub&lt;/strong&gt; — vérification des doublons, création de tickets prioritaires selon des règles fixes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rédaction IA&lt;/strong&gt; — la seule partie générative : titre, description, pré-analyse et suggestion de correction, à partir des données déjà structurées par le harness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rapport structuré&lt;/strong&gt; — production d’un docx formaté (processus déterministe), mais c’est une option, car je ne les regarde plus.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;limpact&quot;&gt;L’impact&lt;/h3&gt;
&lt;p&gt;Des rapports plus riches que le résumé Sentry, et un gain estimé à &lt;strong&gt;2–3 heures par semaine&lt;/strong&gt; de triage manuel.
L’IA rédige ; le harness décide &lt;em&gt;quoi&lt;/em&gt; traiter et &lt;em&gt;comment&lt;/em&gt; le livrer dans GitHub.
Maintenant qu’une partie importante du code de l’agent est déterministe, je sais que je peux l’appeler aussi souvent que nécessaire sans exploser mon budget token.&lt;/p&gt;
&lt;p&gt;Ce que ça a changé concrêtement ? BB-LOG vérifie désormais les nouvelles erreurs chaque heure.
Et il envoie ces erreurs à BB-DEV qui les corrige dans la foulée.
C’est comme si votre application avait une sorte de fonctionnalité auto-repair.&lt;/p&gt;
&lt;p&gt;S’il n’est pas certain de la correction, la consigne passée à l’agent est d’ajouter des logs, afin de mieux interpréter la prochaine fois qu’il aura cette erreur.
Votre agent améliore au fur et à mesure la maintenabilité de votre application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-4--léquilibre-génératif--déterministe&quot;&gt;Partie 4 — L’équilibre génératif / déterministe&lt;/h2&gt;
&lt;h3 id=&quot;la-règle-que-japplique&quot;&gt;La règle que j’applique&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;L’IA intervient là où l’intelligence est nécessaire, pas là où la logique suffit.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Récupérer des tickets tagués, ouvrir une PR, planifier un cron, filtrer les erreurs déjà ticketées : ce n’est pas de la créativité. C’est du déterminisme — et c’est précisément ce qui rend l’agent fiable.&lt;/p&gt;
&lt;p&gt;La génération (code, texte de ticket, synthèse) reste dans le périmètre où le modèle apporte de la valeur. Le harness borne ce périmètre.&lt;/p&gt;
&lt;h3 id=&quot;ce-que-ça-apporte-concrètement&quot;&gt;Ce que ça apporte concrètement&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fiabilité&lt;/strong&gt; — moins de dérives, actions alignées sur le process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prédictibilité&lt;/strong&gt; — on sait ce que l’agent peut faire, et ce qu’il ne fera jamais.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gestion des risques&lt;/strong&gt; — permissions minimales, points de contrôle (tag &lt;code&gt;BB-DEV&lt;/code&gt;, revue de PR, règles de priorité).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Montée en charge contrôlée&lt;/strong&gt; — on élargit le harness (nouveaux tools, nouveaux cron) sans lâcher le modèle en liberté totale.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;en-résumé&quot;&gt;En résumé&lt;/h2&gt;
&lt;p&gt;Le harness n’est pas un détail d’implémentation : c’est la condition pour qu’un LLM devienne un &lt;strong&gt;agent de production&lt;/strong&gt;. BB-DEV et BB-LOG n’ont pas le même moteur ni le même rythme, mais la même logique : entourer l’IA de processus déterministes, et ne lui confier que ce qui demande vraiment de l’intelligence.&lt;/p&gt;
&lt;p&gt;Si vous voulez mettre en place ce type d’agents sur votre stack — ou simplement en discuter — &lt;a href=&quot;https://citios.fr/contact/&quot;&gt;contactez-moi&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-07-26</atom:updated><readingTime>8 min</readingTime><metaDescription>Qu&apos;est-ce qu&apos;un harness IA ? Comment encadrer un modèle génératif avec des processus déterministes pour obtenir un agent fiable. Cas pratiques BB-DEV et BB-LOG.</metaDescription><category>Outillage IA</category><category>Retour d&apos;expérience</category><category>Agents IA</category><category>Harness</category><category>Automatisation</category><category>BB-DEV</category><category>BB-LOG</category><category>Claude Code</category><category>CTO à la demande</category><enclosure url="https://citios.fr/_astro/le-harness-ia-agents-autonomes.BXEUZKZS_Z1K9pBr.jpg" length="1572864" type="image/jpeg"/></item><item><title>Maîtriser l&apos;API Gemini pour les architectures d&apos;agents intelligents</title><link>https://citios.fr/blog/maitriser-api-gemini-architectures-agents-intelligents/</link><guid isPermaLink="true">https://citios.fr/blog/maitriser-api-gemini-architectures-agents-intelligents/</guid><description>Du prompt aux interactions GitHub : comment construire un agent Gemini capable de raisonner, d&apos;appeler des outils et d&apos;explorer un dépôt pour diagnostiquer un problème technique.</description><pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;L’API Gemini ne se limite pas à générer du texte. Bien utilisée, elle permet de construire des &lt;strong&gt;agents&lt;/strong&gt; capables de raisonner, de planifier et d’agir sur des systèmes externes — un dépôt GitHub, une base de connaissances, un board de tickets.&lt;/p&gt;
&lt;p&gt;C’est exactement ce que j’ai mis en place pour &lt;a href=&quot;https://citios.fr/blog/assistant-bb-po-gemini/&quot;&gt;BB-PO&lt;/a&gt;, mon assistant intégré à l’application de gestion de projet Citios. Cet article détaille l’architecture sous-jacente : appels API, ingénierie des prompts, output structuré, et surtout la boucle d’outils qui permet à Gemini d’explorer un repo pour diagnostiquer un bug ou cadrer un ticket.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-1--fondations-de-lapi-gemini&quot;&gt;Partie 1 — Fondations de l’API Gemini&lt;/h2&gt;
&lt;h3 id=&quot;modèles-et-capacités&quot;&gt;Modèles et capacités&lt;/h3&gt;
&lt;p&gt;Gemini repose sur une architecture de transformeurs multimodale. En pratique, pour un agent conversationnel métier, on s’appuie surtout sur les modèles de la famille &lt;strong&gt;Gemini Pro&lt;/strong&gt; : bon équilibre entre qualité de raisonnement, latence et coût.&lt;/p&gt;
&lt;p&gt;Ce qui change la donne pour les agents, ce n’est pas seulement la qualité du texte généré — c’est la capacité du modèle à &lt;strong&gt;décider d’appeler des fonctions&lt;/strong&gt; (tools) quand il lui manque une information, puis à intégrer le résultat dans sa réponse.&lt;/p&gt;
&lt;h3 id=&quot;authentification-et-environnement&quot;&gt;Authentification et environnement&lt;/h3&gt;
&lt;p&gt;L’API exige une clé, stockée côté serveur (variables d’environnement, secrets manager). Jamais côté client. Dans mon cas, l’agent tourne en fonction serverless ; les headers d’auth sont construits à chaque requête :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; resp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;GEMINI_OPENAI_BASE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}/chat/completions`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  headers: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;geminiAuthHeaders&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  body: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(body),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;deux-modes-dappel--streaming-et-json&quot;&gt;Deux modes d’appel : streaming et JSON&lt;/h3&gt;
&lt;p&gt;Selon le besoin, j’utilise deux wrappers :&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Fonction&lt;/th&gt;&lt;th&gt;Mode&lt;/th&gt;&lt;th&gt;Usage&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;callGemini&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Streaming (&lt;code&gt;ReadableStream&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Conversation interactive, affichage progressif&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;callGeminiJson&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Réponse complète (&lt;code&gt;stream: false&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Output structuré, boucle d’outils&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Le streaming convient à l’UI conversationnelle. La boucle d’outils, elle, a besoin d’une réponse complète pour inspecter les &lt;code&gt;tool_calls&lt;/code&gt; avant d’exécuter quoi que ce soit :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; callGeminiJson&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Record&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GeminiCompletionMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  |&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ok&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; resp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;GEMINI_OPENAI_BASE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}/chat/completions`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    headers: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;geminiAuthHeaders&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    body: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body, stream: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resp.ok) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { ok: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, status: resp.status, text: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; resp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; resp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; data.choices?.[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]?.message &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GeminiCompletionMessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;message) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { ok: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, status: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, text: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data) };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { ok: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, message };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-2--ingénierie-des-prompts-et-output-structuré&quot;&gt;Partie 2 — Ingénierie des prompts et output structuré&lt;/h2&gt;
&lt;h3 id=&quot;le-system-prompt--plus-quune-persona&quot;&gt;Le system prompt : plus qu’une persona&lt;/h3&gt;
&lt;p&gt;Un agent utile ne part pas d’un prompt générique. Le message système est assemblé dynamiquement à partir de plusieurs couches :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Persona&lt;/strong&gt; — nom et posture de l’assistant&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instructions métier&lt;/strong&gt; — règles du projet, vocabulaire, périmètre&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contexte RAG&lt;/strong&gt; — extraits de documents uploadés&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mémoires&lt;/strong&gt; — faits persistés sur l’utilisateur ou le projet&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hints d’outils&lt;/strong&gt; — quand et comment utiliser GitHub, le calculateur, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; systemMessage &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  personaHeader &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  (system_prompt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; defaultPrompt) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  agentsMdContext &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  githubToolsHint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ragContext &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  memoryContext;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; requestMessages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { role: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;system&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, content: systemMessage },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;messages,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plus le contexte est précis, moins le modèle invente. Sur BB-PO, chaque projet embarque un fichier de contexte produit : l’assistant sait de quoi on parle sans que l’utilisateur doive tout réexpliquer.&lt;/p&gt;
&lt;h3 id=&quot;contexte-conversationnel&quot;&gt;Contexte conversationnel&lt;/h3&gt;
&lt;p&gt;Gemini ne « se souvient » de rien entre deux requêtes. C’est à vous d’envoyer l’historique complet — messages utilisateur, réponses assistant, &lt;strong&gt;et résultats d’outils&lt;/strong&gt; — à chaque tour. Sans ça, l’agent perd le fil dès qu’il a appelé un tool.&lt;/p&gt;
&lt;h3 id=&quot;output-structuré--générer-un-ticket-fiable&quot;&gt;Output structuré : générer un ticket fiable&lt;/h3&gt;
&lt;p&gt;Pour automatiser la création de tickets, on ne demande pas un paragraphe libre : on impose un schéma JSON strict dans le system prompt, et on passe par &lt;code&gt;callGeminiJson&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ticketSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;You are an expert project manager bot. Extract information from the user&amp;#39;s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;request and generate a technical support ticket as JSON only.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Schema:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;title&amp;quot;: &amp;quot;string&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;description&amp;quot;: &amp;quot;string&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;priority&amp;quot;: &amp;quot;High | Medium | Low&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;assigned_to&amp;quot;: &amp;quot;string&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;steps_to_reproduce&amp;quot;: [&amp;quot;string&amp;quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;No markdown, no text outside the JSON object.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Résultat : un artefact directement injectable dans un board ou une issue GitHub, sans parsing fragile d’un pavé de markdown.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-3--architecture-des-agents-avec-les-tools&quot;&gt;Partie 3 — Architecture des agents avec les Tools&lt;/h2&gt;
&lt;h3 id=&quot;le-principe&quot;&gt;Le principe&lt;/h3&gt;
&lt;p&gt;Un &lt;strong&gt;tool&lt;/strong&gt;, c’est une fonction que vous déclarez au modèle. Gemini décide quand l’appeler, avec quels arguments. Votre backend exécute la fonction, renvoie le résultat, et le modèle continue — jusqu’à une réponse textuelle finale, ou jusqu’à une limite de tours.&lt;/p&gt;
&lt;p&gt;Sans tools, Gemini ne connaît que son entraînement. Avec tools, il peut lire un fichier du repo, chercher une erreur dans le code, explorer une arborescence.&lt;/p&gt;
&lt;h3 id=&quot;déclaration-des-outils&quot;&gt;Déclaration des outils&lt;/h3&gt;
&lt;p&gt;Chaque outil est décrit avec un nom, une description (critique pour que le modèle sache &lt;em&gt;quand&lt;/em&gt; l’utiliser) et un schéma de paramètres :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; TOOL_DEFINITIONS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    function: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;search_github_code&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Search for code in the project&amp;#39;s linked GitHub repository&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      parameters: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          query: { type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Search terms or code snippet&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          max_results: { type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;number&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Max results 1–10 (default 8)&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        required: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    function: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;read_github_file&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Read the text content of a file from the linked GitHub repository&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      parameters: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          path: { type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Path relative to repo root&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          ref: { type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Optional branch, tag, or commit SHA&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        required: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    function: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;list_repo_tree&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;List files and directories at a path in the linked GitHub repository&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      parameters: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          path: { type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Directory path (empty string for root)&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          ref: { type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Optional branch, tag, or commit SHA&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        required: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;la-boucle-dagent--runtoolagentloop&quot;&gt;La boucle d’agent : &lt;code&gt;runToolAgentLoop&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Cœur du système. À chaque round :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Envoyer system + historique + définitions d’outils&lt;/li&gt;
&lt;li&gt;Recevoir une réponse (texte et/ou &lt;code&gt;tool_calls&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;S’il n’y a pas d’appel d’outil → réponse finale&lt;/li&gt;
&lt;li&gt;Sinon → exécuter chaque outil, pousser les résultats dans la conversation, recommencer&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; runToolAgentLoop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  chatModel&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  systemMessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  messages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  toolCtx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ToolExecutionContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToolAgentResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; conversation&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;opts.messages];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; round &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; round &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MAX_TOOL_ROUNDS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; round&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; geminiResp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; callGeminiJson&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      model: opts.chatModel,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      messages: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        { role: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;system&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, content: opts.systemMessage },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;conversation,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      tools: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TOOL_DEFINITIONS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      tool_choice: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;auto&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;geminiResp.ok) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AiServiceError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(geminiResp.status, geminiResp.text);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; geminiResp.message.content &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; roundToolCalls&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; normalizeToolCalls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(geminiResp.message.tool_calls, round);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (roundToolCalls.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { content: content &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;I couldn&amp;#39;t generate a response.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, tool_rounds: round, tool_calls: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    conversation.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      role: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;assistant&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      content: content &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      tool_calls: roundToolCalls,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; tc&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; roundToolCalls) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; executeToolCall&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(tc, opts.toolCtx);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      conversation.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        role: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;tool&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        tool_call_id: tc.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        content: result,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    content: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Maximum tool rounds reached. Please try a more specific question.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    tool_rounds: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;MAX_TOOL_ROUNDS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    tool_calls: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tool_choice: &amp;quot;auto&amp;quot;&lt;/code&gt; laisse Gemini décider. &lt;code&gt;MAX_TOOL_ROUNDS&lt;/code&gt; évite les boucles infinies (et la facture qui va avec).&lt;/p&gt;
&lt;h3 id=&quot;normaliser-les-appels-doutils&quot;&gt;Normaliser les appels d’outils&lt;/h3&gt;
&lt;p&gt;Les &lt;code&gt;tool_calls&lt;/code&gt; bruts ne sont pas toujours propres (id manquant, arguments déjà parsés). Une étape de normalisation avant exécution évite des crashs silencieux :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; normalizeToolCalls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  raw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GeminiCompletionMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;tool_calls&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  round&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;raw &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Array.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isArray&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(raw)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; raw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;tc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; args&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tc.function?.arguments &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        id: tc.id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `call_${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}_${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        function: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          name: tc.function?.name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          arguments: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; args &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;string&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; args &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(args),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;tc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tc.function.name);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;scénario-concret--diagnostiquer-un-bug-via-github&quot;&gt;Scénario concret : diagnostiquer un bug via GitHub&lt;/h3&gt;
&lt;p&gt;Voici le type de flux que la boucle produit quand un utilisateur décrit une erreur :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Utilisateur&lt;/strong&gt; — « La connexion échoue avec &lt;code&gt;InvalidCredentialsError&lt;/code&gt;. »&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Round 1&lt;/strong&gt; — &lt;code&gt;search_github_code(query=&amp;quot;InvalidCredentialsError&amp;quot;)&lt;/code&gt; → localise les fichiers concernés&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Round 2&lt;/strong&gt; — &lt;code&gt;read_github_file(path=&amp;quot;src/auth/service.ts&amp;quot;)&lt;/code&gt; → lit le service d’auth&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Round 3&lt;/strong&gt; — réponse textuelle : explication + pistes (dépendance externe, validation des credentials, etc.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;L’agent n’invente pas le chemin du fichier : il le découvre. C’est la différence entre un chatbot qui « a l’air de savoir » et un assistant qui vérifie dans le code.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-4--déploiement-et-bonnes-pratiques&quot;&gt;Partie 4 — Déploiement et bonnes pratiques&lt;/h2&gt;
&lt;h3 id=&quot;serverless&quot;&gt;Serverless&lt;/h3&gt;
&lt;p&gt;L’endpoint tourne en fonction serverless (Supabase Functions / Deno). Le handler parse la requête, construit le system prompt, et bascule vers &lt;code&gt;runToolAgentLoop&lt;/code&gt; ou le streaming selon le flag &lt;code&gt;use_tools&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;logging&quot;&gt;Logging&lt;/h3&gt;
&lt;p&gt;Sans logs structurés, une boucle d’outils est opaque. Je journalise systématiquement : modèle, usage des tools, nombre de rounds, durée, taille de réponse, erreurs.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; logChatInvocation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChatInvocationLog&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; parts&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;[chat]&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `status=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `model=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `tools=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;use_tools&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `project=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;project_id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;none&amp;quot;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `msgs=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `duration_ms=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;duration_ms&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (summary.tool_rounds &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) parts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`tool_rounds=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tool_rounds&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (summary.error) parts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`error=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(parts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;ce-que-je-retients-en-production&quot;&gt;Ce que je retients en production&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tokens et coût&lt;/strong&gt; — l’historique + le contexte RAG + les résultats d’outils grossissent vite. Tronquez, résumez, limitez &lt;code&gt;MAX_TOOL_ROUNDS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompts précis&lt;/strong&gt; — rôle, schéma, exemples few-shot quand le format compte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation&lt;/strong&gt; — parser et valider le JSON de sortie avant de l’injecter dans un système aval.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sécurité&lt;/strong&gt; — clé API serveur uniquement ; tokens GitHub à scopes restreints (lecture repo, pas admin) ; valider les chemins demandés par les tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissions minimales&lt;/strong&gt; — un outil qui lit un fichier n’a pas besoin d’écrire une issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;en-résumé&quot;&gt;En résumé&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Brique&lt;/th&gt;&lt;th&gt;Rôle&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;callGemini&lt;/code&gt; / &lt;code&gt;callGeminiJson&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Streaming UI vs réponses structurées / tools&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;System prompt dynamique&lt;/td&gt;&lt;td&gt;Persona + contexte projet + RAG + hints tools&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;TOOL_DEFINITIONS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Contrat entre le modèle et votre backend&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;runToolAgentLoop&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Orchestration multi-tours jusqu’à la réponse finale&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Outils GitHub&lt;/td&gt;&lt;td&gt;Explorer, lire, chercher dans le dépôt lié&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Gemini devient intéressant pour le métier le jour où il cesse d’être un générateur de texte isolé et commence à &lt;strong&gt;agir&lt;/strong&gt; — avec des garde-fous, des logs, et des tools dont vous contrôlez le périmètre.&lt;/p&gt;
&lt;p&gt;Si vous voulez voir ça en action côté produit, &lt;a href=&quot;https://citios.fr/blog/assistant-bb-po-gemini/&quot;&gt;BB-PO&lt;/a&gt; en est l’illustration concrète. Et si vous voulez en discuter pour votre stack, &lt;a href=&quot;https://citios.fr/contact/&quot;&gt;contactez-moi&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-07-18</atom:updated><readingTime>14 min</readingTime><metaDescription>Guide technique API Gemini : streaming, output JSON structuré, ingénierie des prompts, tools (GitHub), boucle d&apos;agent et bonnes pratiques pour déployer des agents intelligents.</metaDescription><category>Outillage IA</category><category>Guide technique</category><category>Agents IA</category><category>Gemini</category><category>GitHub</category><category>API</category><category>Ingénierie des prompts</category><category>Automatisation</category><category>CTO à la demande</category><enclosure url="https://citios.fr/_astro/maitriser-api-gemini-architectures-agents-intelligents.C-gGXaXd_2bfXXS.jpg" length="1572864" type="image/jpeg"/></item><item><title>BB-PO — l&apos;assistant IA qui répond à vos questions projet et rédige vos tickets</title><link>https://citios.fr/blog/assistant-bb-po-gemini/</link><guid isPermaLink="true">https://citios.fr/blog/assistant-bb-po-gemini/</guid><description>Présentation de BB-PO, l&apos;assistant IA intégré à l&apos;application de gestion de projet Citios : il répond aux questions sur vos projets et rédige vos tickets d&apos;évolution et de bug, sur simple sollicitation.</description><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;partie-1--un-assistant-pas-un-agent&quot;&gt;Partie 1 — Un assistant, pas un agent&lt;/h2&gt;
&lt;h3 id=&quot;la-différence-compte&quot;&gt;La différence compte&lt;/h3&gt;
&lt;p&gt;J’ai déjà présenté ici deux agents IA autonomes que j’utilise au quotidien : &lt;a href=&quot;https://citios.fr/blog/agent-bb-dev-claude-code-developpement-autonome/&quot;&gt;BB-DEV&lt;/a&gt;, qui développe des tickets de bout en bout, et &lt;a href=&quot;https://citios.fr/blog/agent-bb-log-claude-code-monitoring-autonome/&quot;&gt;BB-LOG&lt;/a&gt;, qui surveille les erreurs en production. Ces deux-là tournent seuls, sur des cycles automatisés, sans que j’aie besoin de les solliciter.&lt;/p&gt;
&lt;p&gt;BB-PO fonctionne différemment. C’est un assistant, pas un agent : il ne se déclenche jamais tout seul. Il répond uniquement quand on lui pose une question ou qu’on lui demande quelque chose, exactement comme on ouvrirait une conversation avec ChatGPT. Pas de cron, pas de boucle autonome — juste une interface de discussion, disponible à la demande.&lt;/p&gt;
&lt;h3 id=&quot;où-il-vit&quot;&gt;Où il vit&lt;/h3&gt;
&lt;p&gt;BB-PO est intégré directement dans l’application de gestion de projet que j’utilise avec mes clients, celle qui est synchronisée avec GitHub pour le suivi des tickets et l’avancement du board. C’est là tout l’intérêt : l’assistant n’est pas une IA générique à côté de l’outil, il a une vision complète du projet en cours — les tickets, leur statut, leur historique.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-2--ce-que-bb-po-sait-faire&quot;&gt;Partie 2 — Ce que BB-PO sait faire&lt;/h2&gt;
&lt;h3 id=&quot;répondre-aux-questions-sur-le-projet&quot;&gt;Répondre aux questions sur le projet&lt;/h3&gt;
&lt;p&gt;La première fonction de BB-PO, c’est de répondre aux questions qu’on lui pose sur un projet : où en est telle fonctionnalité, qu’est-ce qui a été livré la semaine dernière, quels tickets sont encore ouverts sur tel sujet. Comme il a accès au board complet, il ne devine pas — il répond à partir de l’état réel du projet.&lt;/p&gt;
&lt;h3 id=&quot;créer-un-ticket-dévolution&quot;&gt;Créer un ticket d’évolution&lt;/h3&gt;
&lt;p&gt;Quand on lui décrit un besoin d’évolution, BB-PO ne se contente pas de créer un ticket avec la première formulation venue. Il pose des questions pour affiner la demande : quel est l’objectif, qui est concerné, qu’est-ce qui doit changer précisément. Le tout sans jamais devenir technique — l’échange reste au niveau du besoin, pas de l’implémentation.&lt;/p&gt;
&lt;h3 id=&quot;créer-un-ticket-de-bug&quot;&gt;Créer un ticket de bug&lt;/h3&gt;
&lt;p&gt;Pour un bug, la logique d’échange change. BB-PO demande :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les conditions de reproduction du problème,&lt;/li&gt;
&lt;li&gt;les symptômes observés,&lt;/li&gt;
&lt;li&gt;le comportement attendu à la place.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce cadrage systématique évite les tickets de bug bâclés — ceux qui arrivent avec une phrase du type « ça ne marche pas » et qu’il faut ensuite reprendre par email ou en réunion pour comprendre ce qui s’est réellement passé.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Fonction&lt;/th&gt;&lt;th&gt;Ce que fait BB-PO&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Question projet&lt;/td&gt;&lt;td&gt;Répond à partir de l’état réel du board et des tickets&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ticket d’évolution&lt;/td&gt;&lt;td&gt;Pose des questions de cadrage fonctionnel, sans jargon technique&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ticket de bug&lt;/td&gt;&lt;td&gt;Demande reproduction, symptômes, comportement attendu&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Déclenchement&lt;/td&gt;&lt;td&gt;Uniquement sur sollicitation — jamais automatique&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-3--sous-le-capot&quot;&gt;Partie 3 — Sous le capot&lt;/h2&gt;
&lt;h3 id=&quot;le-moteur-ia--gemini&quot;&gt;Le moteur IA : Gemini&lt;/h3&gt;
&lt;p&gt;BB-PO s’appuie sur l’&lt;strong&gt;API Gemini&lt;/strong&gt; — pas une interface grand public, mais une intégration directe dans l’application. Le choix n’est pas anodin : pour une interface conversationnelle exposée à des utilisateurs finaux, avec des temps de réponse rapides et un coût maîtrisé à l’usage, c’est le moteur qui correspondait le mieux à ce besoin précis.&lt;/p&gt;
&lt;p&gt;Mais un modèle seul ne suffit pas. Pour que BB-PO réponde utilement sur un projet précis, il faut lui donner du contexte — et lui donner les moyens d’aller chercher l’information qu’il n’a pas encore.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Un fichier de contexte projet, injecté dans ses instructions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Chaque projet embarque un fichier de contexte qui décrit le produit, ses utilisateurs, son vocabulaire métier et ses règles de fonctionnement. Ce fichier est chargé dans les instructions système de l’assistant à chaque conversation. BB-PO ne part pas de zéro : il sait de quoi on parle, quels termes utiliser et quel périmètre couvre le projet — sans que l’utilisateur ait à tout réexpliquer à chaque échange.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Des tools pour accéder au code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;La lecture du board seule ne suffit pas toujours pour répondre à une question. Quand il faut comprendre comment une fonctionnalité est réellement implémentée, BB-PO dispose de tools qui lui permettent de consulter le dépôt : parcourir l’arborescence, lire un fichier, chercher dans le code. Il va chercher l’information à la source plutôt que de supposer — tout en restant dans son rôle d’assistant produit, sans basculer dans un échange technique inutile pour l’utilisateur.&lt;/p&gt;
&lt;h3 id=&quot;un-niveau-comparable-à-claude-cowork-avec-le-board-en-plus&quot;&gt;Un niveau comparable à Claude Cowork, avec le board en plus&lt;/h3&gt;
&lt;p&gt;En termes de qualité d’échange et de compréhension du contexte, le niveau de réalisation de BB-PO est comparable à celui de Claude Cowork. La différence, c’est que BB-PO a en plus la vision directe du &lt;strong&gt;board&lt;/strong&gt; du projet — pas une liste de tickets brute, mais une vue kanban de toutes les tâches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ce qu’on entend par « board »&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Le board, c’est la liste des tâches du projet affichée en &lt;strong&gt;vision kanban&lt;/strong&gt; : des colonnes qui représentent les étapes d’avancement, des cartes qui se déplacent au fil du travail. C’est le même principe que ce qu’on retrouve dans des outils comme &lt;a href=&quot;https://citios.fr/blog/agent-bb-dev-claude-code-developpement-autonome/&quot;&gt;Hermes Agent&lt;/a&gt; — une lecture immédiate de l’état du projet, sans avoir à fouiller dans GitHub.&lt;/p&gt;
&lt;p&gt;Chaque ticket y figure avec son &lt;strong&gt;état précis&lt;/strong&gt; : à cadrer, en cours, en revue, livré… BB-PO voit où se situe chaque demande, de sa création jusqu’à sa livraison. Quand on lui demande « où en est telle évolution ? », il ne répond pas à partir d’une impression générale — il lit la colonne dans laquelle se trouve le ticket, et l’historique qui l’a amené là.&lt;/p&gt;
&lt;p&gt;C’est ce qui distingue BB-PO d’un assistant conversationnel classique : il ne travaille pas dans l’abstrait. Il connaît l’état réel de ce qui a été fait, de ce qui est en cours, et de ce qui reste à faire — parce que le board en est la source de vérité.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-4--une-nouvelle-offre--ouvrir-lapplication-aux-clients&quot;&gt;Partie 4 — Une nouvelle offre : ouvrir l’application aux clients&lt;/h2&gt;
&lt;h3 id=&quot;pourquoi-maintenant&quot;&gt;Pourquoi maintenant&lt;/h3&gt;
&lt;p&gt;Jusqu’ici, l’application de gestion de projet et ses assistants IA — BB-PO compris — étaient un outil interne, réservé à mon usage pour piloter les missions. La nouvelle offre change ça : j’ouvre désormais l’application aux clients qui le souhaitent.&lt;/p&gt;
&lt;p&gt;Deux cas d’usage concrets :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Après un MVP&lt;/strong&gt; : garder la main sur le suivi du produit, poser des questions à BB-PO, faire remonter des évolutions ou des bugs directement, sans dépendre d’un aller-retour par email.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pour booster une équipe&lt;/strong&gt; avec des agents autonomes de développement type BB-DEV : une équipe existante gagne en vitesse en déléguant les tickets bien cadrés à un agent IA, pendant que BB-PO reste le point d’entrée conversationnel pour cadrer les demandes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ce-que-ça-change-pour-vous&quot;&gt;Ce que ça change pour vous&lt;/h3&gt;
&lt;p&gt;Avoir accès à l’application, c’est avoir une vision transparente et en temps réel de l’avancement — sans filtre, sans rapport hebdomadaire à attendre. Et avec BB-PO, c’est aussi pouvoir poser une question ou déclarer un bug à n’importe quel moment, sans avoir à formuler un ticket GitHub techniquement parfait du premier coup.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Si l’ouverture de cette application à votre équipe, ou l’ajout d’agents IA autonomes pour booster votre développement, vous intéresse, &lt;a href=&quot;https://citios.fr/contact/&quot;&gt;contactez-moi&lt;/a&gt; — j’en parle avec plaisir.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-07-07</atom:updated><readingTime>7 min</readingTime><metaDescription>BB-PO est un assistant IA (Gemini) intégré à l&apos;application de gestion de projet Citios : réponses aux questions projet, rédaction de tickets d&apos;évolution et de bug. Découvrez la nouvelle offre d&apos;ouverture aux clients.</metaDescription><category>Outillage IA</category><category>Présentation</category><category>Agents IA</category><category>Assistant IA</category><category>Gestion de projet</category><category>GitHub</category><category>Gemini</category><category>CTO à la demande</category><enclosure url="https://citios.fr/_astro/citios-ai-conversation.Dr3Ajalo_2fB1OJ.jpg" length="1242000" type="image/jpeg"/></item><item><title>Agent BB-LOG — Quand l&apos;IA surveille les logs à ma place</title><link>https://citios.fr/blog/agent-bb-log-claude-code-monitoring-autonome/</link><guid isPermaLink="true">https://citios.fr/blog/agent-bb-log-claude-code-monitoring-autonome/</guid><description>Comment mon agent BB-LOG récupère des erreurs depuis Sentry, analyse, vérifie les traces et propose des corrections, sans que j&apos;aie besoin d&apos;intervenir.</description><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;partie-1--le-problème-des-erreurs-en-production&quot;&gt;Partie 1 — Le problème des erreurs en production&lt;/h2&gt;
&lt;h3 id=&quot;les-erreurs-existent-la-question-cest-comment-on-les-traite&quot;&gt;Les erreurs existent. La question c’est comment on les traite.&lt;/h3&gt;
&lt;p&gt;Dans toute application en production, des erreurs se produisent. C’est inévitable. La vraie question, c’est : est-ce qu’on les voit ? Est-ce qu’on les comprend ? Est-ce qu’on agit dessus dans un délai raisonnable ?&lt;/p&gt;
&lt;p&gt;Sentry, l’outil de monitoring d’erreurs que j’utilise sur mes projets, envoie un email hebdomadaire de résumé. C’est utile. Mais c’est un résumé de haut niveau : les erreurs les plus fréquentes, leur nombre d’occurrences, leur première apparition. Pas vraiment de quoi prendre une décision éclairée sur ce qui mérite une correction prioritaire.&lt;/p&gt;
&lt;p&gt;Pour vraiment comprendre une erreur Sentry, il faut aller dedans : lire la stack trace, regarder quand elle a commencé à apparaître, comprendre combien d’utilisateurs sont touchés, croiser avec le code source pour identifier la cause probable. C’est un travail de titan quand on a plusieurs erreurs à traiter sur plusieurs projets.&lt;/p&gt;
&lt;h3 id=&quot;laccumulation-silencieuse&quot;&gt;L’accumulation silencieuse&lt;/h3&gt;
&lt;p&gt;Le vrai problème, ce n’est pas l’erreur isolée. C’est l’accumulation progressive d’erreurs non traitées qui finit par peser sur la qualité du produit et la confiance des utilisateurs. On ne les traite pas parce que c’est long, parce qu’on ne sait pas par où commencer, parce qu’il n’y a pas de processus clair.&lt;/p&gt;
&lt;p&gt;J’avais besoin d’un processus automatisé qui transforme la surveillance des erreurs en actions concrètes dans GitHub.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-2--ce-que-fait-lagent-bb-log&quot;&gt;Partie 2 — Ce que fait l’agent BB-LOG&lt;/h2&gt;
&lt;h3 id=&quot;un-audit-hebdomadaire-profond-des-erreurs-sentry&quot;&gt;Un audit hebdomadaire profond des erreurs Sentry&lt;/h3&gt;
&lt;p&gt;L’agent BB-LOG tourne une fois par semaine. Sa mission : passer en revue toutes les erreurs actives dans Sentry, les analyser en profondeur, et produire un rapport actionnable.&lt;/p&gt;
&lt;p&gt;Ce n’est pas une simple copie du résumé Sentry. L’agent effectue une analyse réelle :&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Critère d’analyse&lt;/th&gt;&lt;th&gt;Ce que l’agent évalue&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Fréquence&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Nombre d’occurrences sur la période&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Impact utilisateur&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Nombre d’utilisateurs distincts touchés&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Gravité&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Blocage complet, dégradation, ou comportement inattendu ?&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Évolution&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;L’erreur est-elle nouvelle, en hausse, stable ou en baisse ?&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Récidive&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A-t-elle déjà été signalée dans une session précédente ?&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Pour chaque erreur significative, l’agent va plus loin que les métadonnées Sentry. Il lit la stack trace, retrouve le fichier source concerné, et produit une pré-analyse des causes possibles.&lt;/p&gt;
&lt;h3 id=&quot;la-connexion-mcp-sentry&quot;&gt;La connexion MCP Sentry&lt;/h3&gt;
&lt;p&gt;L’accès à Sentry se fait via le protocole MCP (Model Context Protocol). Cela permet à l’agent de fouiller les issues Sentry en profondeur : lire les breadcrumbs, accéder aux détails de l’environnement au moment de l’erreur, voir les sessions utilisateurs affectées, naviguer dans l’historique d’une issue.&lt;/p&gt;
&lt;p&gt;C’est cette profondeur d’accès qui fait la différence. L’agent ne lit pas un résumé — il explore, exactement comme un développeur qui irait manuellement faire le tour des erreurs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-3--du-rapport-sentry-aux-tickets-github&quot;&gt;Partie 3 — Du rapport Sentry aux tickets GitHub&lt;/h2&gt;
&lt;h3 id=&quot;un-cron-et-claude-code-en-mode-cli&quot;&gt;Un CRON et Claude Code en mode CLI&lt;/h3&gt;
&lt;p&gt;L’agent BB-LOG est appelé via une tache CRON, que j’ai programmée chaque semaine.
Ensuite, j’utilise Claude Code en mode CLI avec le mode autonome activé.
Cela me permet d’utiliser mon forfait Claude directement — pas besoin de tokens API supplémentaires.&lt;/p&gt;
&lt;h3 id=&quot;un-rapport-docx-structuré&quot;&gt;Un rapport docx structuré&lt;/h3&gt;
&lt;p&gt;Le premier livrable de l’agent, c’est un rapport au format docx. Pourquoi docx plutôt qu’un simple texte ? Parce que ce rapport est destiné à être partagé — avec un client, avec l’équipe, en réunion. Un document structuré avec une mise en page claire est bien plus efficace qu’un dump de terminal.&lt;/p&gt;
&lt;p&gt;Le rapport inclut pour chaque erreur :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un résumé de l’erreur en langage clair&lt;/li&gt;
&lt;li&gt;Le contexte technique (fichier, fonction, ligne)&lt;/li&gt;
&lt;li&gt;L’impact estimé (fréquence, utilisateurs touchés)&lt;/li&gt;
&lt;li&gt;La ou les causes probables identifiées par l’analyse du code&lt;/li&gt;
&lt;li&gt;La recommandation de traitement&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;la-création-automatique-de-tickets-github&quot;&gt;La création automatique de tickets GitHub&lt;/h3&gt;
&lt;p&gt;Le rapport, c’est la vision. Les tickets, c’est l’action. Après avoir produit le rapport, l’agent crée dans GitHub un ticket pour chaque erreur qui mérite un correctif.&lt;/p&gt;
&lt;p&gt;Ce n’est pas une création mécanique. L’agent :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vérifie si le ticket existe déjà&lt;/strong&gt; — si l’erreur a déjà été remontée la semaine précédente et que le ticket n’a pas été traité, il met à jour le ticket existant plutôt que d’en créer un doublon.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rédige un titre clair&lt;/strong&gt; — pas le message d’erreur brut, mais une description compréhensible de ce qui se passe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documente le ticket&lt;/strong&gt; — description, contexte, lien vers l’issue Sentry, pré-analyse des causes, suggestion de correction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Place le ticket en backlog&lt;/strong&gt; — les tickets créés arrivent en backlog, pas directement dans le sprint.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;la-boucle-avec-lagent-bb-dev&quot;&gt;La boucle avec l’agent BB-DEV&lt;/h3&gt;
&lt;p&gt;Une fois les tickets créés par BB-LOG, c’est moi qui décide lesquels méritent d’être traités en priorité. Si le correctif me semble simple et bien balisé, je le tague &lt;code&gt;BB-DEV&lt;/code&gt; — et l’agent de développement BB-DEV s’en charge dès qu’il le peut.&lt;/p&gt;
&lt;p&gt;Les agents travaillent ensemble. BB-LOG détecte et documente, BB-DEV corrige. Je reste le point de décision entre les deux.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-4--ce-que-ça-change-sur-la-gestion-des-erreurs&quot;&gt;Partie 4 — Ce que ça change sur la gestion des erreurs&lt;/h2&gt;
&lt;h3 id=&quot;passer-de-la-réaction-à-la-prévention&quot;&gt;Passer de la réaction à la prévention&lt;/h3&gt;
&lt;p&gt;Avant l’agent BB-LOG, je traitais les erreurs de manière réactive : quand un utilisateur signalait un problème, ou quand je tombais par hasard sur quelque chose dans Sentry. La couverture était partielle, le délai de traitement variable.&lt;/p&gt;
&lt;p&gt;Avec BB-LOG, le cycle est devenu proactif. Chaque semaine, j’ai une vue complète des problèmes actifs, classée par impact. Je ne découvre plus un bug critique deux semaines après son apparition.&lt;/p&gt;
&lt;h3 id=&quot;un-rapport-bien-plus-riche-que-lemail-sentry&quot;&gt;Un rapport bien plus riche que l’email Sentry&lt;/h3&gt;
&lt;p&gt;L’email hebdomadaire de Sentry vous dit qu’il y a 47 occurrences d’une erreur &lt;code&gt;TypeError: cannot read property &amp;#39;id&amp;#39; of undefined&lt;/code&gt;. L’agent BB-LOG vous dit que cette erreur touche 23 utilisateurs distincts depuis mardi, qu’elle se produit dans la fonction &lt;code&gt;processOrder&lt;/code&gt; du fichier &lt;code&gt;orders.service.ts&lt;/code&gt; à la ligne 142, que la cause probable est un cas non géré quand la commande n’a pas encore de ligne de produit, et que la correction probable est un guard de 3 lignes avant l’accès à la propriété.&lt;/p&gt;
&lt;p&gt;Ce n’est pas la même information. C’est la différence entre une alerte et une analyse.&lt;/p&gt;
&lt;h3 id=&quot;linvestissement-en-temps&quot;&gt;L’investissement en temps&lt;/h3&gt;
&lt;p&gt;Un audit hebdomadaire manuel des erreurs Sentry sur plusieurs projets prendrait facilement 2 à 3 heures. C’est du temps de développeur senior utilisé à une tâche de collecte et de mise en forme. Avec BB-LOG, ces 2 à 3 heures se passent sans moi. Ce que je reçois, c’est directement la synthèse sur laquelle je peux décider.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;vous-voulez-passer-dun-monitoring-passif-à-un-traitement-actif-des-erreurs&quot;&gt;Vous voulez passer d’un monitoring passif à un traitement actif des erreurs ?&lt;/h2&gt;
&lt;p&gt;Chez &lt;strong&gt;Citios&lt;/strong&gt;, j’aide les équipes à mettre en place des dispositifs de surveillance et de traitement automatisé : intégration Sentry, création de tickets, workflows de correction. Des systèmes qui font avancer la qualité sans attendre un incident critique.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://citios.fr&quot;&gt;Prendre contact&lt;/a&gt;&lt;/strong&gt; — Sébastien Quéré, CTO à la demande&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-06-16</atom:updated><readingTime>6 min</readingTime><metaDescription>BB-LOG est un agent de monitoring applicatif autonome : Il récupère les erreurs applicatives, il décortique les traces, recoupe avec le code, jusqu&apos;à créer un ticket actionnable . Retour d&apos;expérience.</metaDescription><category>Outillage IA</category><category>Retour d&apos;expérience</category><category>Agents IA</category><category>Claude Code</category><category>GitHub</category><category>Automatisation</category><category>Monitoring</category><category>Log</category><category>Sentry</category><category>CTO à la demande</category><enclosure url="https://citios.fr/_astro/BB-LOG.CkH-ljUA_Z1xEiGe.jpg" length="1572516" type="image/jpeg"/></item><item><title>Mon premier agent BB-DEV — Quand l&apos;IA fait le développement à ma place</title><link>https://citios.fr/blog/agent-bb-dev-claude-code-developpement-autonome/</link><guid isPermaLink="true">https://citios.fr/blog/agent-bb-dev-claude-code-developpement-autonome/</guid><description>Comment mon agent BB-DEV récupère des tickets GitHub tagués, développe, teste et ouvre des pull requests de bout en bout, sans que j&apos;aie besoin d&apos;intervenir.</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;partie-1--pourquoi-jai-eu-besoin-dun-agent-de-développement&quot;&gt;Partie 1 — Pourquoi j’ai eu besoin d’un agent de développement&lt;/h2&gt;
&lt;h3 id=&quot;lia-ma-rendu-plus-rapide-et-pourtant&quot;&gt;L’IA m’a rendu plus rapide, et pourtant&lt;/h3&gt;
&lt;p&gt;Depuis un peu plus d’un an, j’utilise Cursor intensivement pour mes développements. L’outil est devenu central dans ma façon de travailler : aujourd’hui, je travaille à 95 % assisté par des IA.&lt;/p&gt;
&lt;p&gt;Au début, les résultats étaient inégaux. Une fois sur deux, le code produit ne compilait même pas. Mais les progrès ont été fulgurants. Les modèles de langage ont atteint un niveau où ils livrent du code de qualité correcte de manière régulière, y compris sur des tâches complexes.&lt;/p&gt;
&lt;p&gt;Et c’est là qu’est apparu un nouveau problème.&lt;/p&gt;
&lt;p&gt;Plus l’IA est capable, plus je lui confie de travail. Et plus je lui confie de travail, plus je me retrouve à… attendre. J’essayais de jongler entre plusieurs projets ou plusieurs features en parallèle pour rester productif pendant qu’elle travaillait. Mais ce n’est pas très efficace : chaque changement de contexte a un coût cognitif. Et sur les tâches simples, je perdais clairement du temps à rester impliqué dans des cycles qui pourraient se dérouler sans moi.&lt;/p&gt;
&lt;h3 id=&quot;le-constat--certaines-tâches-nont-pas-besoin-de-moi&quot;&gt;Le constat : certaines tâches n’ont pas besoin de moi&lt;/h3&gt;
&lt;p&gt;Il y a des tickets où le travail est clair, balisé, sans ambiguïté. Un correctif de bug documenté. Une nouvelle page qui suit un pattern existant. Une intégration d’API bien spécifiée. Ce type de tâche ne demande pas de décision architecturale, pas de négociation avec le client, pas d’arbitrage technique. Il demande juste de l’exécution.&lt;/p&gt;
&lt;p&gt;C’est exactement là que je perdais du temps : à rester dans la boucle pour des choses qui n’ont pas besoin que je sois dans la boucle.&lt;/p&gt;
&lt;p&gt;Ma conclusion : il me fallait un agent capable de prendre en charge ces tickets de bout en bout — de la récupération du ticket jusqu’au pull request — sans que j’aie à intervenir.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-2--ce-qui-na-pas-marché&quot;&gt;Partie 2 — Ce qui n’a pas marché&lt;/h2&gt;
&lt;h3 id=&quot;les-échecs-ça-forme-aussi&quot;&gt;Les échecs, ça forme aussi&lt;/h3&gt;
&lt;p&gt;Si tout avait marché du premier coup, cet article serait beaucoup moins intéressant à écrire — et probablement aussi à lire. Je trouve qu’on apprend autant de ses réussites que de ses échecs. C’est pour ça que je tiens à partager ce qui n’a &lt;em&gt;pas&lt;/em&gt; fonctionné pour moi avant d’arriver à BB-DEV. Spoiler : ça a pris plusieurs détours.&lt;/p&gt;
&lt;h3 id=&quot;openclaw&quot;&gt;OpenClaw&lt;/h3&gt;
&lt;p&gt;Premier essai : OpenClaw, sur un vieux PC Ubuntu que j’avais sous la main. Installation complexe, plusieurs heures à batailler, et au bout du compte des instabilités qui refusaient de disparaître. J’ai fini par lâcher l’affaire. Parfois, le bon réflexe, c’est de savoir s’arrêter avant d’y laisser sa soirée.&lt;/p&gt;
&lt;h3 id=&quot;hermes-agent&quot;&gt;Hermes Agent&lt;/h3&gt;
&lt;p&gt;Deuxième tentative : Hermes Agent, cette fois sur un VPS chez Hostinger. L’installation est plus simple — Hostinger propose une image “toute prête” — mais même avec ça, j’ai eu droit à des instabilités entre le front et la gateway. Mon Claude Code m’a bien aidé à creuser les problèmes (oui, j’utilise déjà l’IA pour configurer mes IA, on n’arrête pas le progrès), mais la gateway de Hermes continuait de redémarrer toute seule. Je ne suis pas administrateur système, mais disons que c’était quand même bien technique pour ce que je voulais en faire.&lt;/p&gt;
&lt;p&gt;Une fois le système stabilisé, j’ai commencé à utiliser Hermes pour configurer un premier agent. J’ai acheté pour 10 € de tokens chez Anthropic. Résultat : une dizaine de prompts plus tard — pour configurer l’agent, créer une première skill et connecter un MCP — les 10 € étaient déjà cramés. Autant dire que ça ne m’a pas franchement rassuré sur l’efficience de la consommation de tokens.&lt;/p&gt;
&lt;h3 id=&quot;google-adk-et-openhands&quot;&gt;Google ADK et OpenHands&lt;/h3&gt;
&lt;p&gt;J’ai aussi creusé du côté de Google Agent Development Kit (ADK) : très puissant, mais largement surdimensionné pour mon besoin. Et OpenHands, qui m’a semblé être une très bonne solution sur le papier — mais qui, comme Hermes, fait également tourner la note en consommant des tokens d’API.&lt;/p&gt;
&lt;h3 id=&quot;la-conclusion-qui-sest-imposée&quot;&gt;La conclusion qui s’est imposée&lt;/h3&gt;
&lt;p&gt;Tous ces essais m’ont amené à la même conclusion : il me fallait un petit script maison, capable de s’appuyer sur les abonnements que je paie déjà — Claude Code et Cursor — plutôt que de cramer des tokens d’API à chaque interaction. C’est cette idée toute simple qui a posé les bases de ce qui allait devenir BB-DEV.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-3--architecture-de-lagent-bb-dev&quot;&gt;Partie 3 — Architecture de l’agent BB-DEV&lt;/h2&gt;
&lt;h3 id=&quot;les-composants-du-système&quot;&gt;Les composants du système&lt;/h3&gt;
&lt;p&gt;L’agent BB-DEV repose sur un ensemble de briques simples, délibérément légères :&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Composant&lt;/th&gt;&lt;th&gt;Rôle&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;VPS Hostinger&lt;/td&gt;&lt;td&gt;Environnement d’exécution stable et toujours disponible&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Liste de projets GitHub + tokens&lt;/td&gt;&lt;td&gt;Périmètre d’action défini manuellement&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Script Python de récupération des tickets&lt;/td&gt;&lt;td&gt;Filtre et sélectionne les tâches éligibles&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Claude Code en mode CLI&lt;/td&gt;&lt;td&gt;Exécute le développement de manière autonome&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Les skills du projets&lt;/td&gt;&lt;td&gt;Indiquent à Claude comment travailler sur ce projet&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Le MCP GitHub&lt;/td&gt;&lt;td&gt;Permet d’interagir avec le repo et les issues&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cron toutes les heures&lt;/td&gt;&lt;td&gt;Orchestre le cycle complet&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;pourquoi-un-script-python-pour-la-collecte-pas-une-ia&quot;&gt;Pourquoi un script Python pour la collecte, pas une IA ?&lt;/h3&gt;
&lt;p&gt;C’est un choix délibéré. La collecte des tickets GitHub est une opération simple et déterministe : appeler l’API GitHub, filtrer sur un tag spécifique (&lt;code&gt;BB-DEV&lt;/code&gt;), retourner la liste. Faire passer cette étape par une IA aurait consommé des tokens inutilement sur une opération qui n’a pas besoin d’intelligence.&lt;/p&gt;
&lt;p&gt;La règle que j’applique : &lt;strong&gt;l’IA intervient là où l’intelligence est nécessaire, pas là où la logique suffit.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;le-filtre-bb-dev--comment-je-décide-ce-que-lagent-peut-traiter&quot;&gt;Le filtre &lt;code&gt;BB-DEV&lt;/code&gt; : comment je décide ce que l’agent peut traiter&lt;/h3&gt;
&lt;p&gt;Je ne confie pas tous les tickets à l’agent. Seulement ceux que j’ai volontairement tagués &lt;code&gt;BB-DEV&lt;/code&gt;. C’est moi qui fais l’aiguillage. Ce tag signifie : “j’ai évalué ce ticket, le développement est suffisamment clair et borné pour que l’agent s’en charge sans mon intervention.”&lt;/p&gt;
&lt;p&gt;Ce mécanisme me donne deux garanties :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L’agent ne travaille jamais sur quelque chose d’ambigu ou de risqué sans que je l’aie décidé.&lt;/li&gt;
&lt;li&gt;Je garde la main sur la stratégie et les arbitrages techniques.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;claude-code-en-mode-cli--le-cœur-de-lexécution&quot;&gt;Claude Code en mode CLI : le cœur de l’exécution&lt;/h3&gt;
&lt;p&gt;Pour l’exécution du développement, j’utilise Claude Code en mode CLI avec le mode autonome activé. Cela me permet d’utiliser mon forfait Claude directement — pas besoin de tokens API supplémentaires.&lt;/p&gt;
&lt;p&gt;Le modèle utilisé est Claude Sonnet. C’est un bon équilibre entre performance et coût pour des tâches de développement courantes.&lt;/p&gt;
&lt;p&gt;Un point important : toute la configuration du projet — contexte, skills, MCPs — est versionnée dans le dépôt. Claude Code la charge automatiquement quand il démarre sur un projet. Il n’y a donc aucune configuration supplémentaire à faire côté agent : le comportement attendu de l’IA est décrit dans le projet lui-même.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-4--le-cycle-de-vie-dun-ticket&quot;&gt;Partie 4 — Le cycle de vie d’un ticket&lt;/h2&gt;
&lt;h3 id=&quot;de-lissue-github-au-pull-request&quot;&gt;De l’issue GitHub au pull request&lt;/h3&gt;
&lt;p&gt;Voici ce qui se passe à chaque exécution du cron :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. Le script Python interroge l&amp;#39;API GitHub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── pour chaque projet dans la liste&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       └── récupère les tickets ouverts avec le tag &amp;quot;BB-DEV&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. Pour chaque ticket éligible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── Lance Claude Code en mode autonome&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ├── avec le contexte du ticket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ├── dans le dossier du projet concerné&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       └── avec les instructions pour créer une branche, développer, committer et ouvrir un PR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. Claude Code exécute le développement&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ├── analyse le code existant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ├── implémente les modifications&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   ├── teste ce qui peut l&amp;#39;être&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   └── crée le pull request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4. Le ticket est mis à jour sur GitHub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;ce-que-lagent-fait-seul&quot;&gt;Ce que l’agent fait seul&lt;/h3&gt;
&lt;p&gt;Depuis un mois que le système tourne, l’agent a traité 25 tickets sans intervention de ma part. Ce sont principalement :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Des petits correctifs de bugs avec une cause identifiée&lt;/li&gt;
&lt;li&gt;Des ajouts de features simples qui suivent des patterns existants&lt;/li&gt;
&lt;li&gt;Des mises à jour de contenu ou de configuration&lt;/li&gt;
&lt;li&gt;Un ticket par jour, pas si mal&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ce-que-je-continue-de-faire-moi-même&quot;&gt;Ce que je continue de faire moi-même&lt;/h3&gt;
&lt;p&gt;L’agent ne remplace pas le jugement. Je continue de traiter :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les tickets qui nécessitent un échange avec le client&lt;/li&gt;
&lt;li&gt;Les choix d’architecture ou de modèle de données&lt;/li&gt;
&lt;li&gt;Les features nouvelles sans pattern existant dans le projet&lt;/li&gt;
&lt;li&gt;Tout ce qui touche à la sécurité ou aux données sensibles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La ligne de partage est claire : exécution déléguée, décision gardée.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-5--ce-que-ça-change-concrètement&quot;&gt;Partie 5 — Ce que ça change concrètement&lt;/h2&gt;
&lt;h3 id=&quot;la-fin-du-coût-de-réentrée-sur-les-petites-tâches&quot;&gt;La fin du coût de réentrée sur les petites tâches&lt;/h3&gt;
&lt;p&gt;Avant, même pour un ticket simple, je devais ouvrir le projet, retrouver le contexte, lancer l’IA, relire le code produit, committer. Sur un ticket qui prend 20 minutes d’exécution, le coût de ma présence était réel.&lt;/p&gt;
&lt;p&gt;Maintenant, ces tickets se traitent sans moi, dans la journée, au fil du cron. Quand je reviens sur le projet, le PR est là, je le relis, je le merge ou je demande une correction.&lt;/p&gt;
&lt;h3 id=&quot;un-rythme-de-développement-en-arrière-plan&quot;&gt;Un rythme de développement en arrière-plan&lt;/h3&gt;
&lt;p&gt;Le système s’étale dans la journée : maximum un ticket par heure par le cron. Cela crée un flux régulier de small PRs qui avancent les projets de manière continue, sans que je sois présent.&lt;/p&gt;
&lt;p&gt;Je n’ai pas encore eu de saturation du forfait Claude, même en travaillant en parallèle sur plusieurs projets.&lt;/p&gt;
&lt;h3 id=&quot;ce-que-je-veux-faire-évoluer&quot;&gt;Ce que je veux faire évoluer&lt;/h3&gt;
&lt;p&gt;Le système actuel est volontairement simple. Pour la suite, j’explore l’idée de remplacer mes scripts maison par un SDK dédié comme OpenHands, qui offre une abstraction plus robuste pour ce type d’orchestration multi-agents.&lt;/p&gt;
&lt;p&gt;Je vais également essayer Hermes Agent avec un compte Open AI qui me permettra d’utiliser le forfait fourni par Open AI, mais sous la forme d’API, ce que ne permet plus Claude.&lt;/p&gt;
&lt;p&gt;Enfin et surtout, je vais continuer de créer de nouveaux agents. BB-DEV a besoin d’être bien entouré pour constuire une équipe de choc. Je vous tiens au courant dans les prochaines semaines.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Chez &lt;strong&gt;Citios&lt;/strong&gt;, j’accompagne les équipes tech et les fondateurs dans l’automatisation de leurs workflows de développement. Définir le périmètre d’action, choisir les outils, sécuriser le processus : c’est le genre de cadrage qui évite de partir dans une mauvaise direction.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-06-08</atom:updated><readingTime>7 min</readingTime><metaDescription>BB-DEV traite des tickets GitHub de bout en bout avec Claude Code en mode autonome : développement, tests et pull requests, sans intervention. Retour d&apos;expérience.</metaDescription><category>Outillage IA</category><category>Retour d&apos;expérience</category><category>Agents IA</category><category>Claude Code</category><category>GitHub</category><category>Automatisation</category><category>Développement</category><category>CTO à la demande</category><enclosure url="https://citios.fr/_astro/agent-bb-dev-claude-code-developpement-autonome.DbhruCL__AStAN.jpg" length="752400" type="image/jpeg"/></item><item><title>Comment protéger chaque client de ses voisins</title><link>https://citios.fr/blog/supabase-rls-securiser-saas-multi-tenant/</link><guid isPermaLink="true">https://citios.fr/blog/supabase-rls-securiser-saas-multi-tenant/</guid><description>Comment la Row Level Security de Supabase/PostgreSQL garantit l&apos;isolation des données entre vos clients, même en cas d&apos;erreur dans votre code applicatif.</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;partie-1--le-problème-que-tout-fondateur-saas-doit-comprendre&quot;&gt;Partie 1 — Le problème que tout fondateur SaaS doit comprendre&lt;/h2&gt;
&lt;h3 id=&quot;lincident-qui-ne-devrait-jamais-arriver&quot;&gt;L’incident qui ne devrait jamais arriver&lt;/h3&gt;
&lt;p&gt;Imaginez la scène : Thomas dirige une PME, il utilise votre logiciel SaaS depuis six mois. Un matin, il se
connecte à son tableau de bord et tombe sur une liste de contacts qui ne lui appartient pas — les prospects
d’un concurrent direct, stockés dans le même outil que lui. Il vous appelle, furieux. Vous cherchez dans votre
code. Vous trouvez : un filtre oublié, une ligne manquante dans une requête. Rien de malveillant. Juste une
erreur humaine. Mais la confiance, elle, est déjà brisée.&lt;/p&gt;
&lt;p&gt;Ce scénario n’est pas rare. Il arrive quand on construit un SaaS sans réfléchir dès le départ à l’isolation
des données entre les clients.&lt;/p&gt;
&lt;h3 id=&quot;plusieurs-clients-une-seule-base-de-données--le-défi-du-multi-tenancy&quot;&gt;Plusieurs clients, une seule base de données : le défi du multi-tenancy&lt;/h3&gt;
&lt;p&gt;Un SaaS, par définition, sert plusieurs clients — on les appelle des &lt;strong&gt;tenants&lt;/strong&gt; (locataires, en
anglais). Ils partagent la même application, les mêmes serveurs, parfois la même base de données. C’est ce qui
rend le modèle économiquement viable : mutualiser les coûts d’infrastructure.&lt;/p&gt;
&lt;p&gt;Un SaaS, c’est donc comme une sorte de colocation. Il y a des parties accessibles à l’ensemble des locataires,
comme le hall d’entrée, les ascenseurs, mais il y a également des parties privées qu’il faut absolument
sécuriser. Comment s’assurer que les données du client A ne sont jamais accessibles au client B ?&lt;/p&gt;
&lt;p&gt;Il existe trois grandes approches pour répondre à cette question :&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approche&lt;/th&gt;&lt;th&gt;Principe&lt;/th&gt;&lt;th&gt;Coût&lt;/th&gt;&lt;th&gt;Sécurité&lt;/th&gt;&lt;th&gt;Complexité&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Une base par client&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Chaque client a sa propre base de données isolée&lt;/td&gt;&lt;td&gt;Élevé&lt;/td&gt;&lt;td&gt;Maximale&lt;/td&gt;&lt;td&gt;Forte (déploiement, maintenance)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Un schéma par client&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Même base, mais des espaces séparés à l’intérieur&lt;/td&gt;&lt;td&gt;Moyen&lt;/td&gt;&lt;td&gt;Bonne&lt;/td&gt;&lt;td&gt;Moyenne&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Même table, filtre par ID client&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Toutes les données cohabitent, séparées par un identifiant&lt;/td&gt;&lt;td&gt;Faible&lt;/td&gt;&lt;td&gt;Variable&lt;/td&gt;&lt;td&gt;Faible… mais risquée&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;La troisième approche — &lt;strong&gt;toutes les données dans les mêmes tables, triées par un identifiant
client&lt;/strong&gt; — est la plus répandue dans les SaaS modernes. Elle est moins coûteuse, plus simple à
maintenir, et suffit amplement si elle est bien sécurisée. C’est elle que font tourner la majorité des outils
que vous utilisez au quotidien.&lt;/p&gt;
&lt;h3 id=&quot;le-risque-caché--la-sécurité-dans-le-code-ça-se-casse&quot;&gt;Le risque caché : la sécurité dans le code, ça se casse&lt;/h3&gt;
&lt;p&gt;Avec cette approche, chaque requête vers la base de données doit inclure un filtre du type :
&lt;em&gt;“donne-moi uniquement les projets appartenant à ce client”&lt;/em&gt;. Ce filtre, c’est le développeur qui
l’écrit, à la main, dans chaque requête.&lt;/p&gt;
&lt;p&gt;Le problème ? Un développeur distrait, fatigué, ou simplement pressé, peut oublier ce filtre. Une fois. Sur
une seule requête. Et c’est suffisant pour exposer les données d’un client à un autre.&lt;/p&gt;
&lt;p&gt;La sécurité placée uniquement dans le code applicatif est fragile. Elle dépend de la rigueur humaine. Ce
qu’il faut, c’est un filet de sécurité plus bas : au niveau de la base de données elle-même.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-2--ce-que-supabase-apporte--rls-le-garde-fou-dans-la-base&quot;&gt;Partie 2 — Ce que Supabase apporte : RLS, le garde-fou dans la base&lt;/h2&gt;
&lt;h3 id=&quot;supabase-en-trois-lignes&quot;&gt;Supabase en trois lignes&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://supabase.com&quot;&gt;Supabase&lt;/a&gt; est une plateforme open source qui fournit une base de données PostgreSQL managée, une API générée automatiquement, un système d’authentification, et des outils de stockage de fichiers. C’est l’un des choix les plus populaires pour construire un SaaS moderne rapidement, sans gérer soi-même l’infrastructure. Je l’utilise dans de nombreux projets.&lt;/p&gt;
&lt;p&gt;Ce qui nous intéresse ici, c’est une fonctionnalité native de PostgreSQL qu’il expose facilement : la &lt;strong&gt;Row Level Security&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;row-level-security--la-base-qui-se-défend-elle-même&quot;&gt;Row Level Security : la base qui se défend elle-même&lt;/h3&gt;
&lt;p&gt;La &lt;strong&gt;Row Level Security&lt;/strong&gt; (RLS) — ou &lt;em&gt;Sécurité au Niveau des Lignes&lt;/em&gt; — est un mécanisme
qui permet de définir des règles directement dans la base de données pour contrôler qui peut lire ou modifier
quelles lignes.&lt;/p&gt;
&lt;p&gt;Concrètement : même si un développeur oublie un filtre dans son code, la base de données refusera de renvoyer
des lignes qui n’appartiennent pas à l’utilisateur connecté. La base devient son propre garde-fou.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Analogie&lt;/strong&gt; : imaginez un immeuble de bureaux où chaque locataire a son propre couloir avec une
serrure. Peu importe qui tient la clé de l’immeuble ou qui ouvre la porte principale — sans la bonne clé de
couloir, personne n’entre. Ce n’est pas le gardien qui protège les couloirs (il peut se tromper, être absent),
c’est la serrure elle-même.&lt;/p&gt;
&lt;h3 id=&quot;pourquoi-cest-supérieur-à-un-filtre-dans-le-code&quot;&gt;Pourquoi c’est supérieur à un filtre dans le code&lt;/h3&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Filtre dans le code&lt;/th&gt;&lt;th&gt;Row Level Security&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Où se trouve la protection&lt;/td&gt;&lt;td&gt;Dans l’application&lt;/td&gt;&lt;td&gt;Dans la base de données&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Risque d’oubli&lt;/td&gt;&lt;td&gt;Oui, à chaque requête&lt;/td&gt;&lt;td&gt;Non, s’applique automatiquement&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Applicable à tous les accès&lt;/td&gt;&lt;td&gt;Non (APIs externes, scripts…)&lt;/td&gt;&lt;td&gt;Oui, quel que soit l’appelant&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Confiance nécessaire&lt;/td&gt;&lt;td&gt;Dans chaque développeur&lt;/td&gt;&lt;td&gt;Dans la configuration initiale&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;h3 id=&quot;premiers-éléments-de-code&quot;&gt;Premiers éléments de code&lt;/h3&gt;
&lt;p&gt;Activer RLS sur une table se fait très simplement avec l’interface Supabase. Pour l’activer sur une table, en ligne de commande SQL :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- On active le verrou sur la table &amp;quot;projects&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; projects &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ENABLE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ROW&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; LEVEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SECURITY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;À partir de là, si vous réalisez une requête &lt;code&gt;SELECT * FROM projects&lt;/code&gt;, même sans aucun filtre, la base ne renverra aucune ligne. C’est le principe du security by design. Par défaut, vous ne voyez aucune donnée et vous ne pouvez pas non plus les modifier. Pour voir des données, nous allons devoir ajouter des Policies.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-3--mise-en-œuvre-concrète--du-modèle-de-données-à-la-politique&quot;&gt;Partie 3 — Mise en œuvre concrète : du modèle de données à la politique&lt;/h2&gt;
&lt;h3 id=&quot;b2b-ou-b2c--comment-segmenter&quot;&gt;B2B ou B2C : comment segmenter ?&lt;/h3&gt;
&lt;p&gt;La question de l’isolation des données ne se pose pas de la même façon selon votre modèle :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;B2C&lt;/strong&gt; (vous vendez à des particuliers) : chaque utilisateur est son propre tenant. L’identifiant de l’utilisateur suffit comme clé d’isolation. C’est simple.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B2B&lt;/strong&gt; (vous vendez à des entreprises, avec plusieurs utilisateurs par client) : un tenant = une organisation. Plusieurs utilisateurs partagent les données de leur entreprise, mais pas celles des autres. C’est le cas le plus courant et le plus structurant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans un contexte B2B, il faut une table intermédiaire qui relie chaque utilisateur à son organisation. C’est cette organisation — et non l’utilisateur — qui devient l’unité d’isolation.&lt;/p&gt;
&lt;h3 id=&quot;modèle-de-données-type&quot;&gt;Modèle de données type&lt;/h3&gt;
&lt;p&gt;Chaque table métier doit porter un champ &lt;code&gt;organisation_id&lt;/code&gt; (ou &lt;code&gt;tenant_id&lt;/code&gt;) qui fait le lien avec le propriétaire de la donnée.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;┌─────────────────┐       ┌───────────────────┐       ┌───────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│ organisations   │       │ members           │       │ projects          │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│─────────────────│       │───────────────────│       │───────────────────│&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│ id              │◄──┐   │ id                │   ┌──►│ id                │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│ name            │   └───│ organisation_id   │   │   │ name              │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│ created_at      │       │ user_id           │   │   │ organisation_id   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└─────────────────┘       │ role              │   │   │ created_at        │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          └───────────────────┘   │   └───────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                                  │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          ┌───────────────────┐   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          │ tasks             │   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          │───────────────────│   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          │ id                │   │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          │ project_id        │───┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          │ organisation_id   │──►(filtre RLS)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          └───────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Règle d’or&lt;/strong&gt; : toute table qui contient des données métier sensibles doit avoir un &lt;code&gt;organisation_id&lt;/code&gt;. Sans ça, RLS ne peut pas s’appliquer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;gestion-des-rôles--qui-voit-quoi&quot;&gt;Gestion des rôles : qui voit quoi ?&lt;/h3&gt;
&lt;p&gt;Dans un SaaS B2B, il y a généralement trois niveaux d’accès :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Utilisateur standard&lt;/strong&gt; : voit uniquement les données de son organisation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin organisation&lt;/strong&gt; : voit toutes les données de son organisation, peut gérer les membres&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Super-admin SaaS&lt;/strong&gt; (vous) : accès technique global, jamais exposé côté client&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour implémenter cela, on utilise une fonction sécurisée qui récupère l’organisation de l’utilisateur connecté, puis on base les politiques RLS dessus :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Fonction sécurisée : récupère l&amp;#39;organisation de l&amp;#39;utilisateur connecté&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE OR REPLACE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.get_user_organisation_id()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;RETURNS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bigint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;LANGUAGE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; sql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SECURITY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DEFINER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; search_path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; private&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; organisation_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  FROM&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; public&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;members&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;uid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$$;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;REVOKE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ALL &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_user_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; public;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GRANT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXECUTE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_user_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; authenticated;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensuite, pour chaque table métier, nous allons devoir ajouter une politique RLS. Par exemple, pour la table “members” :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Politique : un utilisateur voit uniquement les membres de son organisation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; POLICY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;Select members for authenticated&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;public&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;members&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; authenticated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  organisation_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_user_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et voici comment gérer un usage admin :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Fonction pour récupérer l&amp;#39;organisation pour laquelle l&amp;#39;utilisateur est admin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE OR REPLACE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.get_admin_organisation_id()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;RETURNS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bigint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;LANGUAGE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; sql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SECURITY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DEFINER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; search_path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; private&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; organisation_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  FROM&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; public&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;members&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;uid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; role&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;admin&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$$;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;REVOKE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ALL &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_admin_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; public;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GRANT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXECUTE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FUNCTION&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_admin_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; authenticated;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensuite, pour mettre à jour la table “members” avec le rôle admin :&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Politique : Seul l&amp;#39;admin peut modifier un membre de son organisation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; POLICY&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;Update members for admin authenticated&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;public&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;members&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; authenticated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FOR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UPDATE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  organisation_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_admin_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WITH&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; CHECK&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  organisation_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; private&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;get_admin_organisation_id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notez bien que ces fonctions ont été créées sur un schéma indépendant appelé ici &lt;code&gt;private&lt;/code&gt;. Vous devrez bien sûr adapter ces fonctions à votre modèle de données.&lt;/p&gt;
&lt;h3 id=&quot;pièges-courants-à-éviter&quot;&gt;Pièges courants à éviter&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Oublier une table.&lt;/strong&gt; RLS doit être activé sur &lt;em&gt;toutes&lt;/em&gt; les tables métier. Une seule
table sans protection suffit à créer une fuite. Tenez une checklist lors de chaque migration. Supabase vous
alerte (dans les règles de sécurité) si vous n’avez pas activé RLS sur toutes les tables.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Les jointures qui contournent.&lt;/strong&gt; Si une table A est protégée mais qu’elle joint une table B
non protégée, les données de B peuvent fuir via la jointure. RLS s’applique table par table — pensez à
l’activation de bout en bout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Les migrations non testées.&lt;/strong&gt; Ajouter une colonne, renommer une table, créer une vue — tout
cela peut interagir avec les politiques existantes. Testez les accès après chaque migration en base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Le super-admin dans le code.&lt;/strong&gt; Ne codez jamais un bypass de RLS dans votre application.
L’accès super-admin doit passer par des connexions techniques séparées, jamais par un utilisateur applicatif.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;partie-4--ce-que-ça-change-pour-votre-produit-et-vos-clients&quot;&gt;Partie 4 — Ce que ça change pour votre produit (et vos clients)&lt;/h2&gt;
&lt;h3 id=&quot;sécurité-by-design-vs-sécurité-ajoutée-après&quot;&gt;Sécurité by design vs. sécurité ajoutée après&lt;/h3&gt;
&lt;p&gt;Il y a deux façons de gérer la sécurité dans un produit :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La sécurité rajoutée&lt;/strong&gt; : on construit vite, on sécurise plus tard. C’est tentant au démarrage. C’est risqué à long terme — les couches de sécurité s’ajoutent en patchwork sur une architecture qui n’y était pas préparée.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La sécurité by design&lt;/strong&gt; : on intègre les contraintes dès la conception. Ça prend un peu plus de temps au départ. Ça évite des incidents coûteux, des refactorisations lourdes, et des clients perdus.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RLS, c’est de la sécurité by design. Elle ne se greffe pas sur une architecture existante — elle &lt;em&gt;est&lt;/em&gt; l’architecture. La mettre en place dès le premier sprint, c’est un investissement qui rapporte à chaque nouvelle table ajoutée. En réalité, une fois que vous avez créé les quelques fonctions SQL nécessaires, cela devient un réflexe et le temps passé sur chaque nouvelle table devient négligeable.&lt;/p&gt;
&lt;h3 id=&quot;ce-que-vous-pouvez-dire-à-vos-clients&quot;&gt;Ce que vous pouvez dire à vos clients&lt;/h3&gt;
&lt;p&gt;Avec RLS correctement configuré, vous pouvez tenir ce discours à vos clients en toute honnêteté :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Vos données sont protégées au niveau de la base de données elle-même. Même en cas d’erreur dans notre code, un compte client ne peut pas accéder aux données d’un autre client.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C’est un argument de confiance fort, surtout face à des acheteurs B2B qui posent des questions sur la sécurité lors des demos ou lors de revues de conformité.&lt;/p&gt;
&lt;h3 id=&quot;conformité-rgpd--rls-comme-levier-disolation&quot;&gt;Conformité RGPD : RLS comme levier d’isolation&lt;/h3&gt;
&lt;p&gt;Le RGPD impose de garantir que les données personnelles de vos clients sont traitées et stockées de façon sécurisée. L’isolation par RLS contribue directement à deux principes clés :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La minimisation des accès&lt;/strong&gt; : chaque utilisateur ne voit que ce dont il a besoin&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La séparation des données&lt;/strong&gt; : les données d’un client ne peuvent pas être atteintes par un autre&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce n’est pas suffisant seul — la conformité RGPD est plus large — mais c’est une brique solide à documenter dans votre politique de sécurité et à mentionner dans vos CGV ou DPA (Data Processing Agreement).&lt;/p&gt;
&lt;h3 id=&quot;les-limites-à-connaître&quot;&gt;Les limites à connaître&lt;/h3&gt;
&lt;p&gt;RLS est puissant, mais ce n’est pas une solution totale. Il ne remplace pas :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Le chiffrement des données sensibles&lt;/strong&gt; (mots de passe, données bancaires, données médicales)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les logs d’audit&lt;/strong&gt; pour tracer qui a accédé à quoi et quand&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les sauvegardes isolées&lt;/strong&gt; par client, utiles en cas de demande RGPD de suppression ou de portabilité&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les tests de pénétration&lt;/strong&gt; réguliers pour vérifier l’ensemble de la chaîne&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une bonne architecture SaaS combine plusieurs couches. RLS en est une — essentielle, mais non exclusive.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Chez &lt;strong&gt;Citios&lt;/strong&gt;, j’accompagne les fondateurs et équipes tech dans la conception d’architectures SaaS robustes : choix de stack, modèle de données, sécurité by design, audit de base existante. Que vous partiez de zéro ou que vous vouliez sécuriser un produit existant, une heure de cadrage peut éviter des mois de dette technique.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-04-30</atom:updated><readingTime>10 min</readingTime><metaDescription>Comment Row Level Security de Supabase protège les données de vos clients dans un SaaS multi-tenant. Guide technique avec exemples PostgreSQL concrets.</metaDescription><category>Architecture</category><category>Guide technique</category><category>Supabase</category><category>Row Level Security</category><category>PostgreSQL</category><category>SaaS B2B</category><category>Sécurité données</category><category>Multi-tenant</category><enclosure url="https://citios.fr/_astro/supabase-rls-securiser-saas-multi-tenant.DvT7APKE_hkfYr.jpg" length="1572864" type="image/jpeg"/></item><item><title>Comment cadrer un MVP sans brûler son budget tech</title><link>https://citios.fr/blog/comment-cadrer-un-mvp-sans-bruler-son-budget-tech/</link><guid isPermaLink="true">https://citios.fr/blog/comment-cadrer-un-mvp-sans-bruler-son-budget-tech/</guid><description>Méthodologie en 6 étapes pour aligner business, produit et engineering sans multiplier les sprints inutiles. Avec des exemples concrets tirés du terrain.</description><pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;La plupart des startups et PME ne ratent pas leur MVP par manque de compétences techniques. Elles le ratent
parce qu’elles ont mal cadré le périmètre au départ — et que personne n’a tenu ce périmètre sous pression.&lt;/p&gt;
&lt;p&gt;Voici une méthodologie en 6 étapes que j’applique avec mes clients pour aligner business, produit et
engineering sans multiplier les sprints inutiles.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-définir-le-périmètre-minimal&quot;&gt;1. Définir le périmètre minimal&lt;/h2&gt;
&lt;p&gt;La question à se poser : &lt;strong&gt;“Quel est le strict minimum pour valider mon hypothèse de valeur ?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pas le minimum confortable. Le strict minimum.&lt;/p&gt;
&lt;p&gt;Parfois, une landing page suffit. Avec les outils disponibles aujourd’hui, vous pouvez avoir un mini-site en
ligne en une journée. Ce n’est pas le MVP, mais c’est un signal précoce : est-ce que des gens cliquent ?
Est-ce que quelqu’un laisse son email ?&lt;/p&gt;
&lt;p&gt;L’objectif du MVP, c’est de prouver que vous résolvez un vrai problème pour de vrais utilisateurs — et
d’identifier ceux pour qui ce problème est le plus urgent. Lean Startup d’Eric Ries reste la meilleure
référence sur ce sujet.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-prioriser-avec-léquipe-et-pas-seul-dans-un-coin&quot;&gt;2. Prioriser avec l’équipe (et pas seul dans un coin)&lt;/h2&gt;
&lt;p&gt;La priorisation n’est pas un exercice solitaire. Impliquez product, tech et business dans une session de
travail dédiée — 2h maximum, avec un livrable clair en sortie : une liste ordonnée des fonctionnalités
candidates, avec une décision explicite sur ce qui est &lt;strong&gt;in&lt;/strong&gt;, ce qui est &lt;strong&gt;out&lt;/strong&gt;,
et ce qui est &lt;strong&gt;plus tard&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Deux formats qui marchent bien :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RICE ou Value vs Effort&lt;/strong&gt; : objectiver les décisions avec des scores, évite les débats d’opinion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buy-a-feature&lt;/strong&gt; : chaque participant dispose d’un budget fictif à répartir entre les fonctionnalités. La somme des votes révèle les vraies priorités collectives — et souvent surprend tout le monde&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce type d’atelier a un effet secondaire précieux : il crée un engagement partagé sur le périmètre. Quand
quelqu’un demande d’ajouter une feature plus tard, vous pouvez revenir à cette décision collective.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-tenir-le-périmètre-face-aux-pressions--le-vrai-défi&quot;&gt;3. Tenir le périmètre face aux pressions — le vrai défi&lt;/h2&gt;
&lt;p&gt;C’est là que la plupart des MVP déraillent. Pas à cause d’un bug technique. À cause d’une demande du
cofondateur à J+10 : &lt;em&gt;“Juste une petite chose, ça prend deux jours max.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;J’ai observé ce pattern dans des équipes de toutes tailles. La pression vient souvent des meilleures
intentions — un client important a fait une demande, un investisseur a posé une question, une démo approche.&lt;/p&gt;
&lt;p&gt;La discipline de périmètre, ça se tient collectivement :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nommez un décideur unique&lt;/strong&gt; sur le périmètre MVP (CTO, CPO, ou CEO — mais une seule personne)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Toute nouvelle demande passe par l’atelier de priorisation&lt;/strong&gt; — elle déplace quelque chose, ou elle attend la v2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rendez visible le coût d’un ajout&lt;/strong&gt; : “Cette feature, c’est 3 jours de dev, soit 2 semaines de décalage sur le lancement”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La transparence sur le coût réel est le meilleur rempart contre le feature creep.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-estimer-le-budget-réel--coûts-visibles-et-invisibles&quot;&gt;4. Estimer le budget réel — coûts visibles et invisibles&lt;/h2&gt;
&lt;p&gt;Les estimations de développement sont rarement fausses sur les fonctionnalités elles-mêmes. Elles oublient
le reste.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ce qu’on oublie souvent :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Infra et hébergement (même minimal, ça se configure)&lt;/li&gt;
&lt;li&gt;Authentification, gestion des rôles, sécurité de base&lt;/li&gt;
&lt;li&gt;Tests, intégration continue, déploiement&lt;/li&gt;
&lt;li&gt;Onboarding des premiers utilisateurs (support, bugs, itérations rapides)&lt;/li&gt;
&lt;li&gt;Maintenance corrective post-lancement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ma règle pratique : si vous n’êtes pas habitué à estimer,
&lt;strong&gt;multipliez par 1,5&lt;/strong&gt; les estimations de votre équipe. Pas par pessimisme — par réalisme.&lt;/p&gt;
&lt;p&gt;L’autre approche que je recommande : le &lt;strong&gt;Poker Planning&lt;/strong&gt; avec l’équipe de dev. C’est plus lent
qu’une estimation solo, mais l’engagement et la responsabilisation qui en découlent valent largement le temps
investi.&lt;/p&gt;
&lt;p&gt;Si le total vous semble déraisonnable, le problème est en amont : le périmètre n’est pas assez sélectif.
Retour à l’étape 2.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-prototyper--et-savoir-quand-arrêter&quot;&gt;5. Prototyper — et savoir quand arrêter&lt;/h2&gt;
&lt;p&gt;Ne développez pas ce que vous pouvez prototyper. Les outils actuels (Bolt, Lovable, Figma, Framer, etc.)
permettent d’avoir quelque chose de cliquable en quelques heures.&lt;/p&gt;
&lt;p&gt;Un prototype bien fait vous permet :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;De valider l’UX avant d’investir du temps de dev&lt;/li&gt;
&lt;li&gt;De challenger vos estimations (souvent à la baisse)&lt;/li&gt;
&lt;li&gt;D’obtenir des feedbacks concrets d’utilisateurs — pas des opinions sur un document Word&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Mais attention à la limite :&lt;/strong&gt; un prototype n’est pas scalable. Le moment de basculer vers du
vrai code, c’est quand vous avez validé les parcours critiques et que vous avez vos premiers utilisateurs
prêts. Pas avant — pas après non plus.&lt;/p&gt;
&lt;p&gt;Garder un prototype trop longtemps est un piège : ça crée une fausse sensation d’avancement et accumule de la
dette implicite si le prototype devient “temporairement” le produit.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-lancer-apprendre-itérer&quot;&gt;6. Lancer, apprendre, itérer&lt;/h2&gt;
&lt;p&gt;“Si tu n’as pas honte de la première version de ton produit, c’est que tu as lancé trop tard.” — Reid Hoffman&lt;/p&gt;
&lt;p&gt;L’objectif du MVP n’est pas la perfection. C’est l’apprentissage le plus rapide possible.&lt;/p&gt;
&lt;p&gt;Et n’attendez pas le lancement pour parler de votre produit. Chaque conversation avec un utilisateur potentiel
avant le lancement est un feedback gratuit que vous n’avez pas à payer en sprints de développement.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;en-résumé&quot;&gt;En résumé&lt;/h2&gt;
&lt;p&gt;Le MVP ne brûle pas le budget à cause de mauvais devs ou de mauvais outils. Il le brûle quand :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le périmètre n’est pas décidé collectivement&lt;/li&gt;
&lt;li&gt;personne ne le défend sous pression&lt;/li&gt;
&lt;li&gt;les coûts invisibles ne sont pas anticipés&lt;/li&gt;
&lt;li&gt;le prototype dure trop longtemps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C’est exactement le rôle d’un CTO fractionné : tenir ce cadre, même quand c’est inconfortable, pour que
l’équipe avance vite sur ce qui compte.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2026-01-13</atom:updated><readingTime>7 min</readingTime><metaDescription>Méthodologie en 6 étapes pour cadrer un MVP sans exploser votre budget tech. Alignez business, produit et engineering dès le premier sprint.</metaDescription><category>Stratégie produit</category><category>Guide pratique</category><category>MVP</category><category>Budget tech</category><category>Priorisation produit</category><category>Product management</category><category>Startup</category><enclosure url="https://citios.fr/_astro/comment-cadrer-un-mvp-sans-bruler-son-budget-tech.BUUE7VWu_1FXXOA.jpg" length="1572528" type="image/jpeg"/></item><item><title>Pourquoi j&apos;ai créé une activité de CTO à la demande</title><link>https://citios.fr/blog/citios-pourquoi-cto-a-la-demande/</link><guid isPermaLink="true">https://citios.fr/blog/citios-pourquoi-cto-a-la-demande/</guid><description>Après 25 ans dans la tech, j&apos;ai choisi de devenir CTO freelance par conviction. Les startups et PME méritent un troisième chemin entre recruter un CTO et faire sans.</description><pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Après 25 ans dans la tech — d’abord comme directeur produit pendant 15 ans, puis comme directeur technique
sur des équipes jusqu’à 47 personnes — j’ai pris une décision qui m’a semblé évidente : devenir CTO freelance.&lt;/p&gt;
&lt;p&gt;Pas par envie de liberté abstraite. Par conviction que le vrai problème à résoudre est ailleurs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;le-problème-que-jobserve-depuis-des-années&quot;&gt;Le problème que j’observe depuis des années&lt;/h2&gt;
&lt;p&gt;Les startups et PME innovantes ont deux modes de fonctionnement quand elles ont besoin d’expertise tech :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mode 1 : elles recrutent un CTO.&lt;/strong&gt; C’est long (3 à 6 mois en moyenne), coûteux, et risqué —
surtout en early stage, quand le produit n’est pas encore stabilisé et que les besoins changent vite.
Un mauvais recrutement à ce stade peut coûter 12 à 18 mois de retard.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mode 2 : elles font sans.&lt;/strong&gt; Le fondateur technique prend tout sur lui, ou l’équipe avance sans
cap clair. On accumule de la dette technique, on prend des décisions d’architecture par défaut, on recrute sans
critères précis. Les problèmes se révèlent 18 mois plus tard, quand ils sont beaucoup plus chers à corriger.&lt;/p&gt;
&lt;p&gt;Il existe un troisième chemin. C’est ce que j’ai construit avec Citios.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;ce-quest-citios&quot;&gt;Ce qu’est Citios&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Citios, c’est un CTO expérimenté disponible à la demande&lt;/strong&gt; — sans les contraintes d’un
recrutement, sans engagement long terme, avec un niveau d’expertise immédiatement opérationnel.&lt;/p&gt;
&lt;p&gt;J’interviens sur trois dimensions :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La tech.&lt;/strong&gt; Architecture, choix de stack, audit de code, scalabilité, développement de MVP,
outillage interne. Les décisions techniques structurantes que l’équipe n’a pas le recul ou le temps de prendre
seule.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le management.&lt;/strong&gt; Méthodes de travail, structuration d’équipe, recrutement, pilotage du delivery,
montée en compétences. Une équipe technique bien organisée va deux fois plus vite — c’est mesurable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le produit.&lt;/strong&gt; Roadmap, priorisation, arbitrages entre vitesse et qualité, alignement
business-tech. Le CTO ne fait pas que de la technique : il traduit la vision produit en décisions
d’architecture, et il défend les contraintes techniques face aux attentes business.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;comment-ça-fonctionne-concrètement&quot;&gt;Comment ça fonctionne concrètement&lt;/h2&gt;
&lt;p&gt;Une journée, une semaine ou plusieurs mois : je m’adapte à la situation réelle, pas à un forfait prédéfini.&lt;/p&gt;
&lt;p&gt;Certains clients ont besoin d’un regard externe ponctuel — un audit, un recrutement à cadrer, une décision
d’architecture à trancher. D’autres ont besoin d’une présence régulière sur la durée, le temps de structurer
l’équipe et d’installer les bonnes pratiques.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, l’objectif est le même : &lt;strong&gt;avancer vite sur ce qui compte, sans accumuler les problèmes pour plus tard.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;pourquoi-maintenant&quot;&gt;Pourquoi maintenant&lt;/h2&gt;
&lt;p&gt;La fenêtre entre “l’idée existe” et “le produit est prêt à scaler” est courte. C’est là que les décisions
structurantes se prennent — et qu’elles ont le plus d’impact sur la suite.&lt;/p&gt;
&lt;p&gt;Un CTO senior qui arrive au bon moment peut faire gagner 6 à 12 mois sur le time-to-market, éviter une refonte
technique coûteuse, et poser les bases d’une organisation qui tient dans la durée.&lt;/p&gt;
&lt;p&gt;C’est le service que j’ai voulu rendre accessible, sans les frictions d’un recrutement classique.&lt;/p&gt;
&lt;p&gt;Citios est disponible dès maintenant pour des missions en Bretagne, à Paris, et à distance.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Quéré</dc:creator><atom:updated>2025-11-25</atom:updated><readingTime>4 min</readingTime><metaDescription>Après 25 ans dans la tech, j&apos;ai créé Citios : un CTO expérimenté à la demande, pour les startups et PME qui veulent un troisième chemin.</metaDescription><category>À propos</category><category>Manifeste</category><category>CTO à la demande</category><category>CTO fractionnel</category><category>CTO externalisé</category><category>Startup</category><category>Direction technique</category><enclosure url="https://citios.fr/_astro/citios-pourquoi-cto-a-la-demande.DwgbdSL0_Z2dwDmO.jpg" length="1014000" type="image/jpeg"/></item></channel></rss>