# Rapport d'Architecture : CharmBracelet Crush > Analyse complète de l'application [charmbracelet/crush](https://github.com/charmbracelet/crush.git) — comment elle communique avec les IA, gère les outils, optimise les tokens et les performances. --- ## Table des matières 1. [Architecture globale](#1-architecture-globale) 2. [Ce qui est envoyé à l'IA](#2-ce-qui-est-envoyé-à-lia) 3. [Système de prompts](#3-système-de-prompts) 4. [Système de résumé / compaction](#4-système-de-résumé--compaction) 5. [Fonctionnement des outils (Tools)](#5-fonctionnement-des-outils-tools) 6. [Optimisation de la consommation de tokens](#6-optimisation-de-la-consommation-de-tokens) 7. [Optimisations de performance](#7-optimisations-de-performance) 8. [Système de permissions](#8-système-de-permissions) 9. [Providers et modèles](#9-providers-et-modèles) 10. [Système de skills](#10-système-de-skills) 11. [Intégration LSP](#11-intégration-lsp) 12. [Intégration MCP](#12-intégration-mcp) 13. [Structure de la base de données](#13-structure-de-la-base-de-données) 14. [Fichiers clés à explorer](#14-fichiers-clés-à-explorer) 15. [Leçons pour notre application](#15-leçons-pour-notre-application) --- ## 1. Architecture globale ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ TUI / CLI │────▶│ Backend │────▶│ Coordinator │ │ (UI chat) │ │ (workspace) │ │ (orchestrateur) │ └──────────────┘ └──────────────┘ └────────┬─────────┘ │ ┌────────▼─────────┐ │ SessionAgent │ │ (moteur IA) │ └────────┬─────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ ┌────────▼──────┐ ┌────────▼──────┐ ┌────────▼──────┐ │ fantasy │ │ SQLite DB │ │ Tools │ │ (LLM client) │ │ (messages) │ │ (22+ outils) │ └───────────────┘ └───────────────┘ └───────────────┘ ``` **Flow de données complet :** ``` User Prompt → Backend.SendMessage() → Coordinator.Run() → UpdateModels() (recharge models + tools) → mergeCallOptions() (merge JSON des options provider) → SessionAgent.Run() → preparePrompt() (construit l'historique, filtre les orphelins) → fantasy.Agent.Stream() avec : - System prompt = coder.md.tpl + instructions MCP - System prompt prefix = prefix provider - Messages = historique filtré (tronqué au résumé si existe) - Files = pièces jointes binaires - Tools = ensemble filtré d'outils - Provider options = options merged Anthropic/OpenAI/Google/etc. - Cache control = ephemeral sur dernier system + 2 derniers messages → Auto-summarize si fenêtre de contexte quasi pleine → Queue + recurse pour messages en attente ``` ### Fichiers clés de l'architecture | Fichier | Rôle | |---------|------| | `internal/agent/agent.go` | Moteur principal — construction des messages, streaming, résumé | | `internal/agent/coordinator.go` | Orchestration — création agents, assembly tools/models | | `internal/agent/prompts.go` | Factory de prompts système | | `internal/agent/templates/coder.md.tpl` | Template du prompt système principal (405 lignes) | | `internal/backend/backend.go` | Gestion des workspaces | | `internal/backend/agent.go` | API transport-agnostic vers le coordinator | --- ## 2. Ce qui est envoyé à l'IA ### 2.1 System Prompt Le system prompt est construit à partir du template `coder.md.tpl` et contient : ``` System Message: ├── — 13 règles absolues (read before edit, autonomous, etc.) ├── — Style de réponse (concis, <4 lignes) ├── — Séquence de travail (search → read → edit → test) ├── — Décisions autonomes vs. bloquantes ├── — Règles d'édition (exact match, whitespace) ├── — Checklist précision ├── — Complétion exhaustive des tâches ├── — Stratégies de récupération d'erreurs ├── — Gestion des fichiers mémoire ├── — Conventions de code ├── — Règles de test après changements ├── — Utilisation des outils ├── — Proactivité vs. intention utilisateur ├── — Format des réponses finales ├── — Variables dynamiques : │ ├── Working Directory: {{.WorkingDir}} │ ├── Is Git Repo: {{.IsGitRepo}} │ ├── Platform: {{.Platform}} │ ├── Date: {{.Date}} │ └── Git Status (branch, git status --short | head -20, git log --oneline -n 3) ├── (optionnel) — État des serveurs LSP ├── — Skills disponibles en XML ├── — Instructions d'utilisation des skills ├── — Fichiers de contexte (crush.md, AGENTS.md, etc.) └── — Instructions MCP injectées dynamiquement ``` ### 2.2 Historique des messages Chaque appel à l'IA inclut l'historique complet de la session, formaté comme : ``` Messages (array de fantasy.Message): ├── [0] User: "This is a reminder that your todo list is currently empty..." ├── [1] User: "Premier prompt de l'utilisateur" ├── [2] Assistant: "Réponse IA + tool_calls" ├── [3] Tool: "Résultat du tool call" ├── [4] Assistant: "Réponse suivante" ├── ... └── [N] User: "Nouveau prompt" ``` **Types de contenu dans les messages :** - `ReasoningContent` — Chaîne de pensée (pour modèles thinking) - `TextContent` — Texte brut - `ImageURLContent` — Images (URL ou base64) - `BinaryContent` — Fichiers binaires - `ToolCall` — Appel d'outil (ID, nom, input) - `ToolResult` — Résultat d'outil (text, error, ou media) - `Finish` — Métadonnées de fin (end_turn, max_tokens, tool_use, canceled, error) ### 2.3 Fichiers joints Les pièces jointes sont traitées différemment selon leur type : - **Fichiers texte** : contenus inline dans le prompt utilisateur via `PromptWithTextAttachments()` - **Fichiers binaires** : envoyés comme `FilePart` séparés - **Images** : envoyées comme `ImageURLContent` (base64) si le modèle les supporte ### 2.4 Options provider fusionnées Les options sont fusionnées en 3 couches (le plus profond gagne) : ``` 1. catwalk.Model.Options.ProviderOptions (défauts du catalogue) 2. ProviderConfig.ProviderOptions (config du provider) 3. SelectedModel.ProviderOptions (choix utilisateur) → JSON merge avec sémantique "deepest wins" ``` Options spécifiques par provider : - **Anthropic** : thinking, reasoning_effort, beta headers (`interleaved-thinking-2025-05-14`) - **OpenAI** : responses API, reasoning - **Google** : thinking_config - **OpenRouter** : reasoning, suffixe `:exacto` --- ## 3. Système de prompts ### 3.1 Templates embarqués | Template | But | Taille | |----------|-----|--------| | `coder.md.tpl` | Prompt système principal de l'agent coder | ~405 lignes | | `task.md.tpl` | Prompt système des sous-agents | ~15 lignes | | `initialize.md.tpl` | Prompt d'initialisation du codebase | Analyse + génération AGENTS.md | | `summary.md` | Prompt de résumé/compaction | Sections structurées obligatoires | | `title.md` | Génération de titre de session | ≤50 chars | | `agent_tool.md` | Instructions du tool `agent` | Sous-agent read-only | | `agentic_fetch.md` | Instructions du tool `agentic_fetch` | Sous-agent web | | `agentic_fetch_prompt.md.tpl` | Prompt du sous-agent de fetch | Template dynamique | ### 3.2 Prompt de résumé (`summary.md`) Ce prompt est crucial — il définit comment la conversation est compactée : **Sections obligatoires du résumé :** 1. **Current State** — État actuel de la tâche 2. **Files & Changes** — Tous les fichiers modifiés et les changements 3. **Technical Context** — Stack technique, dépendances, patterns 4. **Strategy & Approach** — Stratégie adoptée 5. **Exact Next Steps** — Prochaines étapes exactes **Philosophie :** *"No limit. Err on the side of too much detail rather than too little."* ### 3.3 Prompt de sous-agent (`task.md.tpl`) Minimal et ciblé : ``` Rules: 1. Be concise, direct 2. Share file names and paths 3. Use absolute paths only Working Directory: {{.WorkingDir}} Is Git Repo: {{.IsGitRepo}} Platform: {{.Platform}} Date: {{.Date}} ``` ### 3.4 Variables dynamiques injectées Le système injecte des données en temps réel via le template : ```go type PromptData struct { WorkingDir string IsGitRepo bool Platform string Date string GitStatus string // branch + git status --short | head -20 + git log --oneline -n 3 Config Config AvailSkillXML string // XML des skills disponibles ContextFiles string // Contenu des fichiers de contexte } ``` ### 3.5 Fichiers de contexte (Memory) Chemins par défaut explorés et injectés dans `` : ``` .github/copilot-instructions.md .cursorrules .cursor/rules/ CLAUDE.md CLAUDE.local.md GEMINI.md crush.md CRUSH.md AGENTS.md ``` --- ## 4. Système de résumé / compaction ### 4.1 Déclenchement automatique Deux conditions de déclenchement vérifiées après **chaque étape** de l'agent : ``` remaining = contextWindow - (completionTokens + promptTokens) if contextWindow > 200,000: threshold = 20,000 tokens (buffer fixe) else: threshold = 20% × contextWindow (ratio) if remaining ≤ threshold AND !disableAutoSummarize: → DÉCLENCHE LE RÉSUMÉ if contextWindow == 0 (modèle inconnu/local): → PAS de résumé auto (évite la troncature aveugle) ``` **Constantes clés :** - `largeContextWindowThreshold = 200,000` - `largeContextWindowBuffer = 20,000` - `smallContextWindowRatio = 0.2` ### 4.2 Processus de résumé ``` ┌─────────────────────────────────────────────┐ │ 1. Récupérer tous les messages de session │ │ 2. Convertir en fantasy.Message[] │ │ 3. Créer message assistant │ │ avec IsSummaryMessage: true │ │ 4. Streamer le résumé via summary.md prompt │ │ + todo list courante │ │ 5. Update session: │ │ - SummaryMessageID = summary.ID │ │ - CompletionTokens = résumé output │ │ - PromptTokens = 0 │ └─────────────────────────────────────────────┘ ``` ### 4.3 Comment la compaction fonctionne au chargement ```go func getSessionMessages(session): msgs = loadAllMessages(sessionID) if session.SummaryMessageID != "": summaryIndex = findIndex(msgs, summaryMessageID) msgs = msgs[summaryIndex:] // TRONQUE tout avant le résumé msgs[0].Role = "user" // Change rôle assistant → user return msgs ``` **Résultat :** Seuls le résumé + les messages après le résumé sont envoyés à l'IA. Tout l'historique précédent est éliminé du contexte. ### 4.4 Détection de boucle (Loop Detection) Mécanisme secondaire de déclenchement du résumé : ``` - Fenêtre glissante de 10 dernières étapes - Signature = SHA-256(ToolName + \x00 + Input + \x00 + Output + \x00) - Si une signature apparaît > 5 fois dans la fenêtre → BOUCLE DÉTECTÉE - Déclenche le résumé + arrêt de l'agent ``` ### 4.5 Reprise après interruption Si le résumé est déclenché pendant des appels d'outils en cours : ``` Re-queue le prompt original avec : "The previous session was interrupted because it got too long, the initial user request was: " ``` --- ## 5. Fonctionnement des outils (Tools) ### 5.1 Liste complète des outils (22+) | Outil | Type | Séquentiel/Parallèle | But | |-------|------|---------------------|-----| | `bash` | Core | Séquentiel | Exécution de commandes shell | | `edit` | Core | Séquentiel | Remplacement find-and-replace dans un fichier | | `multiedit` | Core | Séquentiel | Multiples remplacements séquentiels | | `write` | Core | Séquentiel | Création/écrasement de fichier | | `view` | Core | Séquentiel | Lecture de fichier avec line numbers | | `ls` | Core | Séquentiel | Arborescence de répertoire | | `glob` | Core | Séquentiel | Recherche de fichiers par pattern | | `grep` | Core | Séquentiel | Recherche dans le contenu de fichiers | | `fetch` | Core | Parallèle | Fetch URL brut (text/markdown/html) | | `agentic_fetch` | Core | Parallèle | Fetch IA avec extraction/résumé | | `sourcegraph` | Core | Parallèle | Recherche de code sur GitHub | | `download` | Core | Parallèle | Téléchargement de fichier | | `agent` | Agent | Parallèle | Sous-agent de recherche/analyse | | `todos` | Core | Séquentiel | Gestion de la todo list | | `crush_info` | Core | Séquentiel | État runtime de Crush | | `crush_logs` | Core | Séquentiel | Logs internes de Crush | | `job_output` | Core | Séquentiel | Output de processus background | | `job_kill` | Core | Séquentiel | Terminaison de processus background | | `lsp_diagnostics` | LSP | Séquentiel | Diagnostics LSP | | `lsp_references` | LSP | Séquentiel | Références LSP | | `lsp_restart` | LSP | Séquentiel | Redémarrage LSP | | `list_mcp_resources` | MCP | Parallèle | Liste ressources MCP | | `read_mcp_resource` | MCP | Parallèle | Lecture ressource MCP | | `mcp_{server}_{tool}` | MCP | Parallèle | Outils dynamiques MCP | ### 5.2 Architecture d'un outil Chaque outil suit le pattern : ```go fantasy.NewAgentTool(name, description, params, func(ctx context.Context, params Params) (fantasy.ToolResponse, error) { // 1. Validation des paramètres // 2. Extraction du contexte (sessionID, messageID, workingDir) // 3. Vérification de permission (si mutation) // 4. Exécution de la logique // 5. Retour de la réponse avec métadonnées }) ``` **Deux constructeurs :** - `fantasy.NewAgentTool` — Outil séquentiel (bloquant) - `fantasy.NewParallelAgentTool` — Outil parallèle (peut tourner en //) ### 5.3 Types de réponses ```go fantasy.NewTextResponse(content) // Succès texte fantasy.NewTextErrorResponse(message) // Erreur (IsError=true) fantasy.NewImageResponse(data, mimeType) // Image fantasy.NewMediaResponse(data, mimeType) // Autre média fantasy.WithResponseMetadata(resp, meta) // Attache métadonnées typées ``` ### 5.4 Détails des outils clés #### `bash` — Exécution de commandes ``` Paramètres: {Description, Command, WorkingDir, RunInBackground, AutoBackgroundAfter} Sécurité: ├── safeCommands: ls, cat, head, tail, pwd, echo, which, env, git status/diff/log │ → Pas de permission requise (read-only) ├── Banned: curl, wget, sudo, apt, npm, etc. │ → Bloqué au niveau shell ├── Background: si RunInBackground=true ou > AutoBackgroundAfter (60s) │ → Retourne ShellID pour suivi └── Output: tronqué à 30,000 chars (début + fin + compte lignes tronquées) ``` #### `view` — Lecture de fichiers ``` Paramètres: {FilePath, Offset, Limit} Comportement: ├── crush: prefix → lecture depuis FS embarqué (skills builtins) ├── Chemins relatifs → résolus via SmartJoin(workingDir, ...) ├── Permission requise si hors workingDir ├── Max fichier: 100KB, défaut 2000 lignes ├── Images JPG/PNG/GIF/WebP → NewImageResponse ├── Ajoute numéros de ligne (padding 6 chars) ├── LSP: ouvre fichier, attend 300ms pour diagnostics ├── Enregistre lecture via filetracker.RecordRead() └── Suggestions si fichier non trouvé ``` #### `edit` — Édition de fichiers ``` Paramètres: {FilePath, OldString, NewString, ReplaceAll} 3 modes: ├── OldString="" → Créer nouveau fichier ├── NewString="" → Supprimer contenu └── Les deux → Remplacer contenu Sécurité: ├── Doit avoir lu le fichier avant (filetracker check) ├── Fichier non modifié depuis la lecture (ModTime check) ├── OldString unique (sauf ReplaceAll=true) ├── Permission "write" avec diff affiché ├── Gestion CRLF automatique └── LSP notifié après écriture ``` #### `grep` — Recherche de contenu ``` Paramètres: {Pattern, Path, Include, LiteralText} Optimisations: ├── ripgrep (rg --json) en priorité, fallback Go regex ├── Respecte .gitignore et .crushignore ├── Cache de regex compilés (csync.Map thread-safe) ├── Résultats triés par date de modification (plus récent d'abord) ├── Max 100 résultats ├── Lignes tronquées à 500 chars └── Timeout configurable ``` ### 5.5 Outils spéciaux #### `agent` — Sous-agent ``` Flow: 1. Valide params.Prompt non vide 2. Crée une session enfant (CreateTaskSession) 3. Lance un SessionAgent séparé (NonInteractive: true) 4. Outils du sous-agent: glob, grep, ls, sourcegraph, view (READ-ONLY) 5. Propage le coût de la session enfant → session parent ``` #### `agentic_fetch` — Fetch intelligent ``` Flow: 1. Mode URL: fetch + convert (HTML→MD) → Si contenu > 50KB: sauve en temp file, dit au sous-agent d'utiliser view/grep 2. Mode Search: construit prompt pour web_search + web_fetch 3. Sous-agent avec petit modèle + outils restreints 4. Auto-approve des permissions pour le sous-agent ``` --- ## 6. Optimisation de la consommation de tokens ### 6.1 Résumé automatique (Auto-Summarization) **La plus grande optimisation.** Voir [Section 4](#4-système-de-résumé--compaction). Quand la fenêtre de contexte approche sa limite, toute la conversation est remplacée par un résumé détaillé. Ce résumé devient le nouveau point de départ. ### 6.2 Anthropic Prompt Caching Marqueurs `ephemeral` ajoutés automatiquement : ```go // Cache control placement: ├── Dernier message système → ephemeral ├── Avant-dernier message → ephemeral └── Antépénultième message → ephemeral ``` Cela permet à Anthropic de mettre en cache ces messages et de réduire les tokens d'input sur les tours suivants. ### 6.3 Filtrage des orphelins de tool calls ```go // Avant d'envoyer à l'IA: filterOrphanedToolResults() // Supprime tool results sans tool call correspondant syntheticToolResultsForOrphanedCalls() // Injecte erreurs synthétiques pour tool calls orphelins ``` Cela nettoie l'historique des messages inutiles qui consomment des tokens. ### 6.4 Deux modèles (Large/Small) ``` ├── Large Model: tâches principales (coder, résumé) └── Small Model: titre de session, agentic_fetch → Économise les tokens de haute qualité pour les tâches simples ``` ### 6.5 Limitations de taille des outils | Outil | Limite | Impact token | |-------|--------|-------------| | bash output | 30,000 chars | Tronque les longues sorties | | view | 100KB max, 2000 lignes | Limite les gros fichiers | | grep | 100 résultats, 500 chars/ligne | Limite les recherches larges | | glob | 100 fichiers | Limite les résultats | | ls | 1000 fichiers | Limite les répertoires massifs | | fetch | 100KB | Limite les pages web | | sourcegraph | 20 résultats max | Limite les recherches code | | description tools | FirstLineDescription() | N'envoie que la 1ère ligne de description | ### 6.6 Short Tool Descriptions ```go // Par défaut: seul le premier ligne non-vide de la description est envoyé FirstLineDescription(fullMarkdownDescription) // Désactivable via: CRUSH_SHORT_TOOL_DESCRIPTIONS=0 ``` Chaque outil a une description markdown complète, mais seule la première ligne est envoyée à l'IA — économie significative sur 22+ outils. ### 6.7 Workaround média par provider Pour les providers non-Anthropic/Bedrock, les images dans les tool results sont converties en messages utilisateur séparés avec pièces jointes — évitant les erreurs API et les tokens gaspillés. ### 6.8 Injection system_reminder minimale Seul un rappel minimal est injecté comme premier message utilisateur : ``` "This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly..." ``` Ce message est court et sert de garde-fou sans consommer beaucoup de tokens. ### 6.9 Estimation de tokens ```go ApproxTokenCount(text) = len(text) / 4 // Heuristique 4 chars = 1 token ``` Utilisé pour le logging et les métriques, pas pour le contrôle strict. --- ## 7. Optimisations de performance ### 7.1 Concurrence ``` ├── sync.WaitGroup pour chargement providers (Catwalk + Hyper en //) ├── sync.OnceValue pour cache Hyper (computed once) ├── fastwalk pour découverte des skills (concurent, suit symlinks) ├── errgroup.Group pour readiness des agents (system prompt + tools en //) ├── csync.Map pour maps concurrent-safe (providers, regex cache) ├── sync.RWMutex pour skill tracker └── Shell mutex sérialise les commandes par instance shell ``` ### 7.2 File d'attente de messages (Message Queue) ``` Si agent occupé → message en queue ↓ Dans PrepareStep → injecte messages en queue comme user messages supplémentaires ↓ Pas de perte de messages, traitement séquentiel garanti ``` ### 7.3 Cache providers ``` ├── Cache JSON: $XDG_DATA_HOME/crush/providers.json ├── ETag support pour revalidation HTTP ├── Fallback: frais → cache → embarqué └── Chargement concurrent Catwalk + Hyper ``` ### 7.4 CGO et GC ``` ├── CGO_ENABLED=0 (pas de CGO overhead) └── GOEXPERIMENT=greenteagc (GC optimisé) ``` ### 7.5 Base de données SQLite ``` ├── SQLC pour code SQL type-safe généré ├── Transactions avec retry (3 tentatives) pour conflits UNIQUE ├── Atomic SQL increment pour token usage (évite race conditions) └── Index sur (session_id, created_at) pour requêtes messages rapides ``` ### 7.6 Pub/Sub ``` ├── Système d'événements découplé ├── Canal d'événements par workspace └── Pas de polling — push-based ``` --- ## 8. Système de permissions ### 8.1 Actions | Action | Quand | Outils concernés | |--------|-------|-----------------| | `"read"` | Lecture hors workingDir | view, ls | | `"write"` | Modification de fichiers | edit, multiedit, write | | `"execute"` | Commande shell non-safe | bash | | `"fetch"` | Requête réseau | fetch, agentic_fetch | | `"download"` | Téléchargement | download | | `"list"` | Liste ressources MCP | list_mcp_resources | | `"read"` | Lecture ressource MCP | read_mcp_resource | ### 8.2 Auto-approbations - Commandes bash `safeCommands` (ls, cat, pwd, git status, etc.) → **pas de permission** - Docker MCP tools whitelistés (`mcp_docker_mcp-find`, etc.) → **auto-approuvé** - Sous-agents (agent, agentic_fetch) → **auto-approuvé** ### 8.3 Sécurité shell ``` Banned commands: alias, aria2c, axel, chrome, curl, curlie, firefox, http-prompt, httpie, links, lynx, nc, safari, scp, ssh, telnet, w3m, wget, xh, doas, su, sudo, apk, apt, apt-cache, apt-get, dnf, dpkg, emerge, home-manager, makepkg, opkg, pacman, paru, pkg, pkg_add, pkg_delete, portage, rpm, yay, yum, zypper, at, batch, chkconfig, crontab, fdisk, mkfs, mount, parted, service, systemctl, umount, firewall-cmd, ifconfig, ip, iptables, netstat, pfctl, route, ufw ``` --- ## 9. Providers et modèles ### 9.1 Types de providers supportés ``` ├── OpenAI ├── Anthropic ├── OpenRouter (suffixe :exacto pour modèles supportés) ├── Vercel ├── Azure ├── AWS Bedrock ├── Google (Gemini) ├── Google Vertex AI ├── OpenAI-compatible (générique) └── Hyper (Charm's meta-provider) ``` ### 9.2 Hyper Provider ``` ├── Endpoint: https://hyper.charm.land/api/v1/fantasy ├── Activation: HYPER, HYPERCRUSH, HYPER_ENABLE, HYPER_ENABLED env vars ├── Modèles: GLM-5, GLM-5.1, gpt-oss-120b, Kimi K2.5, Kimi K2.6 ├── Routage: Anthropic/OpenAI/Google/OpenAI-compat selon model ID └── Header: x-crush-id pour identification ``` ### 9.3 Construction du provider ```go buildProvider(config) → fantasy.Provider: 1. Parse le type de provider 2. Configure base URL, API key, headers 3. Ajoute beta headers si thinking model (Anthropic) 4. Pour Hyper: route vers le bon provider selon model ID 5. Pour OpenRouter: ajoute :exacto si supporté ``` --- ## 10. Système de skills ### 10.1 Standard Agent Skills Crush implémente le standard ouvert [agentskills.io](https://agentskills.io). ### 10.2 Découverte ``` Chemins explorés: ├── $CRUSH_SKILLS_DIR ├── ~/.config/agents/skills/ ├── ~/.config/crush/skills/ ├── .agents/skills/ ├── .crush/skills/ ├── .claude/skills/ ├── .cursor/skills/ └── Custom via options.skills_paths Recherche: ├── fastwalk (concurent, suit symlinks) ├── Cherche fichiers SKILL.md ├── Parse YAML frontmatter (entre ---) ├── Validation: nom alphanum-hyphens ≤64 chars, description ≤1024 chars └── Déduplication: dernier occurence gagne (user > builtin) ``` ### 10.3 Injection dans le prompt ```xml skill-name Description courte /path/to/SKILL.md builtin When a user task matches a skill's description, read the skill's SKILL.md file... ``` **Important :** Seules les métadonnées (nom, description, chemin) sont injectées dans le system prompt. Les **instructions complètes** ne sont lues que quand l'agent décide d'activer le skill — économie de tokens. ### 10.4 Skill Tracker ```go type Tracker struct { active map[string]bool // Skills actifs (post-dedup, post-filter) loaded map[string]bool // Skills dont les instructions ont été lues mu sync.RWMutex } MarkLoaded(name) // Marque un skill comme lu (uniquement si dans active set) IsLoaded(name) // Vérifie si déjà chargé LoadedNames() // Liste des skills chargés ``` --- ## 11. Intégration LSP ### 11.1 Configuration ```yaml lsp: - command: "gopls" args: ["serve"] env: {} file_types: [".go"] root_markers: ["go.mod"] init_options: {} ``` ### 11.2 Outils LSP - `lsp_diagnostics` — Diagnostics par fichier ou projet entier (max 10 fichiers, 5s wait) - `lsp_references` — Recherche de références symboliques (grep + LSP FindReferences) - `lsp_restart` — Redémarrage d'un ou tous les clients LSP ### 11.3 Intégration dans les outils ``` view → openInLSPs + wait 300ms pour diagnostics edit → notifyLSPs + wait 5s pour diagnostics write → notifyLSPs + wait 5s pour diagnostics ``` Les diagnostics sont appendés dans les réponses des outils via des tags XML : ```xml ... ... errors: N, warnings: M ``` --- ## 12. Intégration MCP ### 12.1 Configuration ```yaml mcp: - name: "my-server" command: "npx" # Mode stdio args: ["-y", "my-mcp"] env: {} url: "" # OU mode HTTP/SSE timeout: 30s disabled_tools: [] ``` ### 12.2 Outils MCP dynamiques - Outils nommés `mcp_{server}_{tool}` - Schéma extrait de `InputSchema` MCP - Permission requise (sauf whitelist Docker) - Support image/média dans les résultats ### 12.3 Instructions MCP injectées Les instructions des serveurs MCP connectés sont injectées dans le system prompt via ``. --- ## 13. Structure de la base de données ### 13.1 Tables principales ```sql -- Sessions sessions ( id, title, prompt_tokens, completion_tokens, summary_message_id, -- ID du message résumé (NULL si pas de résumé) cost, todos, -- Coût et todo list created_at, updated_at ) -- Messages messages ( id, session_id, role, -- user/assistant/system/tool parts, -- JSON array de {type, data} model, provider, is_summary_message, -- Flag de compaction created_at, finished_at ) -- Fichiers (version history) files ( id, session_id, path, content, version, -- Auto-incrémenté par path is_new, created_at ) -- Fichiers lus (read tracking) read_files ( path, session_id, last_read_time -- Pour "read before edit" ) ``` ### 13.2 Requêtes clés ```sql -- Messages d'une session (chronologique) SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC; -- Update atomique des tokens (évite race conditions) UPDATE sessions SET prompt_tokens = prompt_tokens + ?, completion_tokens = completion_tokens + ?, cost = cost + ? WHERE id = ?; -- Derniers fichiers par path (version max) SELECT f.* FROM files f INNER JOIN ( SELECT path, MAX(version) as max_ver FROM files WHERE session_id = ? GROUP BY path ) latest ON f.path = latest.path AND f.version = latest.max_ver; -- Stats: utilisation des outils via JSON SELECT json_extract(part.data, '$.name') as tool_name, COUNT(*) FROM messages, json_each(messages.parts) as part WHERE json_extract(part.value, '$.type') = 'tool_call' GROUP BY tool_name; ``` --- ## 14. Fichiers clés à explorer ### Architecture et flow principal | Fichier | Ce qu'il contient | |---------|------------------| | `internal/agent/agent.go` | Moteur IA — construction messages, streaming, résumé, queue | | `internal/agent/coordinator.go` | Orchestration — création agents, tools, models, provider options | | `internal/agent/prompts.go` | Factory de prompts système | | `internal/agent/loop_detection.go` | Détection de boucles (SHA-256 signatures) | ### Templates (ce qui est envoyé à l'IA) | Fichier | Ce qu'il contient | |---------|------------------| | `internal/agent/templates/coder.md.tpl` | System prompt principal (405 lignes) | | `internal/agent/templates/task.md.tpl` | Prompt sous-agent | | `internal/agent/templates/summary.md` | Prompt de résumé/compaction | | `internal/agent/templates/title.md` | Prompt de génération de titre | | `internal/agent/templates/agent_tool.md` | Instructions tool agent | | `internal/agent/templates/agentic_fetch_prompt.md.tpl` | Prompt sous-agent fetch | ### Outils | Fichier | Ce qu'il contient | |---------|------------------| | `internal/agent/agent_tool.go` | Tool agent (sous-agent spawner) | | `internal/agent/agentic_fetch_tool.go` | Tool agentic_fetch | | `internal/agent/tools/bash.go` | Tool bash | | `internal/agent/tools/edit.go` | Tool edit | | `internal/agent/tools/multiedit.go` | Tool multiedit | | `internal/agent/tools/write.go` | Tool write | | `internal/agent/tools/view.go` | Tool view | | `internal/agent/tools/glob.go` | Tool glob | | `internal/agent/tools/grep.go` | Tool grep | | `internal/agent/tools/ls.go` | Tool ls | | `internal/agent/tools/fetch.go` | Tool fetch | | `internal/agent/tools/sourcegraph.go` | Tool sourcegraph | | `internal/agent/tools/web_search.go` | Tool web_search (DuckDuckGo) | | `internal/agent/tools/web_fetch.go` | Tool web_fetch | | `internal/agent/tools/download.go` | Tool download | | `internal/agent/tools/todos.go` | Tool todos | | `internal/agent/tools/diagnostics.go` | Tool LSP diagnostics + helpers | | `internal/agent/tools/references.go` | Tool LSP references | | `internal/agent/tools/lsp_restart.go` | Tool LSP restart | | `internal/agent/tools/crush_info.go` | Tool crush_info | | `internal/agent/tools/crush_logs.go` | Tool crush_logs | | `internal/agent/tools/mcp-tools.go` | Outils MCP dynamiques | ### Messages et données | Fichier | Ce qu'il contient | |---------|------------------| | `internal/message/message.go` | Service de messages (CRUD + events) | | `internal/message/content.go` | Types de contenu + conversion fantasy.Message | | `internal/message/attachment.go` | Pièces jointes | | `internal/history/file.go` | Historique de versions fichiers | | `internal/db/sql/sessions.sql` | Requêtes SQL sessions | | `internal/db/sql/messages.sql` | Requêtes SQL messages | | `internal/db/sql/files.sql` | Requêtes SQL fichiers | | `internal/db/sql/stats.sql` | Requêtes SQL statistiques | ### Configuration et providers | Fichier | Ce qu'il contient | |---------|------------------| | `internal/config/config.go` | Structures de config (models, providers, tools, agents) | | `internal/config/provider.go` | Chargement des providers (Catwalk + Hyper + cache) | | `internal/backend/config.go` | API config backend | | `internal/agent/hyper/provider.go` | Provider Hyper | | `internal/agent/hyper/provider.json` | Définition des modèles Hyper | ### Infrastructure | Fichier | Ce qu'il contient | |---------|------------------| | `internal/shell/shell.go` | Shell POSIX cross-platform (mvdan/sh) | | `internal/diff/diff.go` | Génération de diffs unifiés | | `internal/skills/skills.go` | Système de skills (découverte, injection) | | `internal/skills/tracker.go` | Suivi des skills chargés | | `internal/lsp/manager.go` | Gestionnaire LSP | | `internal/filetracker/service.go` | Suivi des fichiers lus | --- ## 15. Leçons pour notre application ### 15.1 Patterns à adopter | Pattern | Pourquoi | Comment Crush le fait | |---------|----------|----------------------| | **Résumé automatique adaptatif** | Gère les longues conversations sans perte | Seuil 20K tokens (fenêtres >200K) ou 20% (fenêtres <200K), skip si fenêtre inconnue | | **Deux niveaux de modèle** | Économise les tokens coûteux | Large pour coder, Small pour titres et fetch | | **Skills avec lazy loading** | N'injecte que les métadonnées, charge les instructions à la demande | Métadonnées dans system prompt, instructions lues via view | | **Short tool descriptions** | Économise des tokens sur 22+ outils | FirstLineDescription() par défaut | | **Anthropic prompt caching** | Réduit les tokens d'input sur les tours suivants | `ephemeral` sur dernier system + 2 derniers messages | | **Filetracker read-before-edit** | Sécurité + contexte pour l'IA | Enregistre chaque lecture, vérifie avant édition | | **Loop detection** | Arrête les boucles infinies coûteuses | SHA-256 signature sur fenêtre de 10 étapes, max 5 répétitions | | **Orphan filtering** | Nettoie l'historique des messages inutiles | Supprime tool results orphelins, injecte erreurs synthétiques | | **Atomic SQL increments** | Pas de race conditions sur les compteurs | `prompt_tokens = prompt_tokens + ?` au lieu de read-modify-write | | **Message queue** | Pas de perte de messages | Queue + injection dans PrepareStep | ### 15.2 Optimisations token spécifiques 1. **Troncature de sortie** — bash (30K), grep (500 chars/ligne), view (2000 lignes) 2. **Limites de résultats** — glob (100), grep (100), ls (1000), sourcegraph (20) 3. **Première ligne de description** — Seulement la 1ère ligne des descriptions markdown d'outils 4. **Estimation heuristique** — `len(text) / 4` pour logging sans appel API 5. **Résumé détaillé structuré** — Sections obligatoires assurent pas de perte d'information critique 6. **Reset des compteurs après résumé** — PromptTokens=0, CompletionTokens=résumé seulement ### 15.3 Points d'attention - **Le system prompt fait ~405 lignes** — C'est énorme mais structuré en sections XML claires - **Pas de troncature du system prompt** — Les skills et context files sont injectés en entier - **Le résumé n'a pas de limite** — *"Err on the side of too much detail"* - **La détection de boucle est basique** — SHA-256 sur ToolName+Input+Output, mais ne détecte pas les boucles sémantiques - **Pas de token counting précis** — Heuristique 4 chars/token, pas de tiktoken - **Le file history persiste** — Les versions de fichiers survivent au résumé (pas dans le contexte LLM mais disponibles dans l'UI) ### 15.4 Architecture recommandée ``` Pour notre app, inspirons-nous de: 1. Coordinator Pattern └── Orchestrateur central qui assemble models + tools + prompts └── Agents par session avec state isolé 2. Summary/Compaction Pipeline └── Seuils adaptatifs (fenêtre large vs small) └── Résumé structuré avec sections obligatoires └── Reset des compteurs après compaction 3. Tool Architecture └── Interface uniforme (params, execute, response) └── Permission system par action └── Output truncation systématique └── Metadata sur chaque réponse 4. Provider Layer └── JSON merge en 3 couches pour options └── Support multi-provider avec routage └── Cache avec ETag + fallback 5. Lazy Skill Loading └── Métadonnées dans system prompt └── Instructions complètes à la demande └── Tracker pour éviter les rechargements ``` --- ## Annexe : Schéma de dépendances ``` main.go └── internal/cmd/ (CLI commands) └── internal/backend/ (workspace management) └── internal/app/ (application logic) └── internal/agent/coordinator.go (orchestration) ├── internal/agent/agent.go (moteur IA) │ ├── internal/agent/templates/ (prompts) │ ├── internal/agent/prompt/ (prompt builder) │ ├── internal/message/ (message types) │ └── internal/agent/loop_detection.go ├── internal/agent/tools/ (22+ outils) ├── internal/skills/ (skill discovery) ├── internal/config/ (configuration) ├── internal/lsp/ (LSP management) ├── internal/shell/ (shell execution) ├── internal/db/ (SQLite persistence) ├── internal/filetracker/ (read tracking) └── internal/diff/ (diff generation) ``` --- *Rapport généré le 26 avril 2026 — Basé sur l'analyse du commit `HEAD` de [charmbracelet/crush](https://github.com/charmbracelet/crush)*