# Architecture : une boucle, deux façades — explication ## De quoi s'agit-il ? `mm` est volontairement petit : une boucle d'agent, une poignée d'outils et un fichier de configuration. Ce qui mérite le regard, c'est la façon dont les pièces sont découpées pour que la même boucle puisse être pilotée depuis un terminal et depuis un éditeur sans rien dupliquer. Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages, ouvre le moteur, construit la liste des outils, affiche la bannière et passe la main à l'une des deux façades. Chaque préoccupation vit dans son propre paquet sous `internal/` : | Paquet | Responsabilité | |--------|----------------| | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. | | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, et le registre des fournisseurs. | | `tools` | Les outils intégrés que le modèle peut appeler. | | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. | | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. | | `mention` | La notation `@chemin` d'une question : les chemins existants deviennent des lignes `[attached file: …]`, la même forme que le `resource_link` d'un éditeur. Utilisé par les deux façades. | | `detector` | La détection d'une même action répétée avec le même résultat. | | `compact` | La compression de l'historique en un résumé écrit par le modèle. | | `spinner` | L'indicateur « toujours en cours » sur un terminal. | | `ui` | Où va la sortie destinée à l'humain, et le puits d'événements que les deux façades partagent. | | `agent` | Le REPL du terminal. | | `acp` | La façade Agent Client Protocol. | Un diagramme draw.io de ces paquets et de leurs dépendances est conservé dans [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio). ## Pourquoi c'est conçu ainsi **Les outils rapportent les échecs comme du texte, jamais comme des erreurs.** Une commande qui sort avec un code non nul, un fichier qui n'existe pas, une édition ambiguë : tout revient au modèle comme sortie de l'outil. C'est le modèle qui doit réagir, donc c'est lui qui doit lire le message. Une erreur Go terminerait le tour à sa place. **L'historique complet est conservé même quand un tour échoue.** Genkit ne renvoie que le dernier message ; le moteur renvoie toute la conversation, appels d'outils et résultats compris. Les façades gardent cet historique après une interruption, une coupure du watchdog ou un dépassement de `maxTurns`, parce que les commandes déjà exécutées sont des faits que le modèle ne doit ni rejouer ni inventer à la question suivante. Seule une question restée sans aucune réponse est retirée. **La sortie est montrée avant que le modèle la voie.** Dans le terminal, `bash` affiche les premières lignes de la sortie d'une commande avant de la renvoyer. « Montre-moi ce fichier » veut dire le montrer ; laisser le modèle le résumer a été observé laissant l'utilisateur sans rien. **Une boucle, deux façades.** Le REPL du terminal et la façade ACP reçoivent exactement le même moteur, le même prompt système et la même liste d'outils. Ce qui diffère, c'est la destination des événements. Dans le terminal, les outils affichent eux-mêmes leurs lignes `🛠️` et personne ne demande de permission. En mode ACP, stdout appartient à JSON-RPC, donc `main.go` redirige la sortie humaine vers stderr, désactive le spinner, et la façade installe un `ui.Sink` à chaque tour. Les outils vérifient s'il y a un puits actif : s'il y en a un, ils émettent des événements `ToolStart`, `Allow`, `ToolRunning`, `ToolEnd` au lieu d'afficher. Le paquet `ui` ne sait rien du protocole ; `acp` est le seul endroit qui traduit ces événements en notifications `session/update` et en demandes de permission. **La permission vit dans le puits, pas dans les outils.** Un dialogue de permission est la principale chose qu'un éditeur ajoute et que le terminal n'a pas. Placer la politique dans le puits ACP garde les outils identiques dans les deux modes ; « toujours autoriser » est mémorisé par nom d'outil pour la session. **Les tours sont sérialisés en mode ACP.** Le puits actif est global au processus et le moteur n'a jamais été conçu pour des générations concurrentes, donc un second `session/prompt` attend la fin du premier. Le protocole laisse ce choix à l'agent, et les éditeurs n'envoient de toute façon pas de prompts qui se chevauchent. **Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée. ## Alternatives écartées - **Un binaire par façade.** Écarté parce que le but de l'exercice est de prouver que la boucle est la même ; partager le câblage dans `main.go` est ce qui le rend visible. - **Afficher directement depuis la façade ACP.** Impossible : la spécification interdit tout ce qui n'est pas message de protocole sur stdout. - **Renvoyer des erreurs Go depuis les outils.** Écarté parce que cela termine le tour au lieu de laisser le modèle se rattraper. ## Liens avec le reste - Le registre de fournisseurs utilisé par le moteur est décrit dans [fournisseurs](providers.md). - Pourquoi et comment l'historique est raccourci est dans [compression du contexte](context-compression.md). - La surface exacte du protocole est dans la [référence ACP](../reference/acp.md) ; le contrat des outils est dans la [référence des outils](../reference/tools.md). ## Carte des paquets Le graphe d'import de tous les paquets, avec une ligne de rôle sur chacun, est tenu sous forme de diagramme draw.io dans [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio) (s'ouvre dans diagrams.net ou l'extension Draw.io de VS Code). Il est regénéré à partir de `go list` chaque fois qu'un paquet est ajouté, retiré ou recâblé.