MagicCode/README.md
2026-10-22 01:54:32 +02:00

49 KiB
Raw Blame History

MAIA

MAIA

Assistant IA en ligne de commande : un seul agent (MAIA), le LLM se choisit parmi ceux de LiteLLM, l'interface utilise Ink (React pour CLI).

Démarrer

pnpm install                               # gestionnaire de paquets : pnpm@12.14.0 (champ packageManager, via Corepack)
mkdir -p ~/.maia && cp settings.example.json ~/.maia/settings.json   # puis renseigner l'URL de LiteLLM
pnpm dev                                  # le modèle est choisi dans la liste au démarrage
pnpm dev -- --model litellm:gpt-4o        # nom du modèle tel que LiteLLM le connaît
pnpm test                                 # test de la boucle agent (provider factice)
pnpm build && node packages/cli/dist/index.js

Lancer MAIA depuis un autre dossier

pnpm dev s'exécute depuis la racine du dépôt : le dossier de travail est alors cette racine (./MAIA.md, ./.maia/skills… y sont cherchés). Pour utiliser MAIA sur un autre projet ./X, lancez-le depuis ./X :

pnpm build
# une fois : installe la commande `maia` (pnpm 12 n'a plus `pnpm link --global`)
mkdir -p ~/.local/bin && ln -sf "$PWD/packages/cli/dist/index.js" ~/.local/bin/maia && chmod +x packages/cli/dist/index.js
#   (~/.local/bin doit être dans le PATH ; alternative : `cd packages/cli && npm link`)
cd ~/X && maia                       # le dossier de travail est ~/X
# ou sans installer :  cd ~/X && node /chemin/vers/maia/packages/cli/dist/index.js
# ou en développement : MAIA_CWD=~/X pnpm --dir /chemin/vers/maia dev

Pourquoi pas pnpm --filter … dev : pnpm exécute alors le script depuis packages/cli, pas depuis le dossier où vous avez tapé la commande. MAIA tient compte de MAIA_CWD, puis de INIT_CWD (dossier où pnpm/npm a été lancé), puis de process.cwd().

Architecture

Paquet Rôle
core Contrats (LLMProvider, Tool, AgentUI…), boucle agent, sessions, !, @fichier, §skill
providers Adaptateurs LLM : litellm (provider unique : requêtes au format OpenAI vers LiteLLM, voir ci-dessous)
ui Interface Ink/React (journal, saisie, listes de choix, confirmations, spinner)
agent L'agent MAIA : contexte (MAIA.md / CLAUDE.md), skills .maia/skills + outils Read/Glob/Grep/Write/Edit/Bash/TodoWrite/WebFetch/CurrentModel
cli Point d'entrée : configuration, boucle de conversation

Résolution du modèle (pas de défaut : s'il n'est pas défini, il est demandé) : option --model > variable MAIA_MODEL

.maia/config.json (projet) > ~/.maia/config.json > défauts.

Il n'y a plus de notion de mode ni d'extensions : un seul agent, MAIA.

Spécifications techniques détaillées (contrats, formats, constantes) : SPECS_TECH.md.

Commandes

Saisie Effet
/help aide (commandes, skills)
/permissions affiche les règles de permissions chargées, par fichier : badges ALLOW, ASK, DENY suivis des règles
/model recharge la liste des modèles LiteLLM et change de modèle (conversation conservée)
/clear supprime le contexte de la session et recharge MAIA.md / CLAUDE.md et les skills, comme au démarrage (la session garde son nom)
/createAgent [nom] crée un sous-agent depuis le modèle .ressources/AGENT.md : demande le niveau (projet ./.maia/agents ou global ~/.maia/agents) puis le nom
/agents liste les sous-agents chargés (nom, origine, description)
/mcp liste les serveurs MCP activés : OK (vert) ou FAIL (rouge)
/compact résume la conversation et repart de ce résumé (voir « Compactage »)
/rename <nom> · /load [nom] · /new sessions (voir plus bas)
/exit (ou Ctrl+D) quitte (la session est sauvegardée)
!commande · @fichier · §skill · &agent commande shell, fichier injecté, skill (voir plus bas)

Option de lancement : --model <spec> (ex. litellm:gpt-4o).

Interface (Ink)

L'interface est une application React rendue par Ink (Ink 7 : React ≥ 19.2 ; vérifiez la version de Node qu'il exige). Le CLI garde une façade impérative (InkUI : info, select, readInput, confirm, withSpinner…) au-dessus d'un UIStore sans dépendance à React, ce qui rend la logique testable (store.test.ts). Les composants (saisie, liste, confirmation, spinner) sont écrits à la main ; seuls les badges viennent de @inkjs/ui. Un terminal interactif (TTY) est requis.

Présentation : la bannière d'accueil reprend le style « MAIA » : image à gauche, affichée telle quelle en couleurs réelles (demi-blocs ▀▄█ : 32 × 32 pixels, soit 32 colonnes × 16 lignes ; sans pixelisation). Elle vient de packages/ui/assets/mask.png ; python3 packages/ui/scripts/make-mask.py [taille] (Pillow) régénère src/mask-data.ts à une autre taille, titre « Maia » en gros caractères pixel avec ombre et dégradé arc-en-ciel à droite, puis trois textes (vert : « Assistant IA en ligne de commande… », rose : « Un seul agent, plusieurs modèles », puis la signature « Arnaud VINCENT » en jaune et « ClaudeCode » en orange). Terminal d'au moins 58 colonnes pour la version complète ; entre 25 et 57 : sans le masque ; en dessous : cadre à double bordure compact, titre centré ; une ligne vide sépare chaque message.

Messages du journal, avec le composant Badge de @inkjs/ui :

Badge Usage Texte
INFO (bleu) information bleu clair (LIGHT_BLUE)
WARN (jaune) avertissement par défaut
FAIL (rouge) erreur #DC8ADD (ERROR_TEXT)
ASK (vert) question posée à l'utilisateur (liste de choix, confirmation o/n), à gauche de la question gras

Les réponses de l'agent sont rendues en markdown (titres, listes, code, tableaux, gras…) avec markdansi. Tout est isolé dans packages/ui/src/markdown.tsx : pour le retirer, supprimer ce fichier, la dépendance markdansi et remplacer <Markdown /> par <Text> dans components.tsx. Le texte en cours de streaming reste brut ; la mise en forme apparaît quand la réponse est terminée. MAIA_NO_MARKDOWN=1 désactive le rendu sans toucher au code, et un échec de markdansi retombe sur le texte brut.

Les couleurs sont dans packages/ui/src/colors.ts. Une fois la réponse validée, la question est remplacée par la ligne ◆ Question : choix dans le journal.

Barre de contexte : sous la zone de saisie, en bas à droite, une barre de carrés ■ (de gauche à droite) montre la taille du contexte de la conversation par rapport à la taille maximale du modèle, à l'échelle 1 carré = 10 000 tokens (200 000 tokens → 20 carrés), avec le libellé « 45.7k / 200k · 23% ». Les carrés sont verts, jaunes à partir de 60 %, rouges à partir de 85 %. Si la barre ne tient pas dans le terminal (très grands contextes), l'échelle est agrandie par paliers de 10 000 et indiquée (1■ = 20k).

  • Taille maximale : après le choix du modèle (au démarrage, avec --model/config, et avec /model), MAIA demande au LLM « Taille maximale du contexte pour ce modèle » (réponse en tokens, lue par parseTokenCount : 128000, 128 000 tokens, 200k, 1,5 million…). La valeur est annoncée (INFO) ; une réponse par modèle et par exécution. Si la réponse est illisible, ou si la question échoue ou dépasse 60 s (Ctrl+C l'abandonne), un avertissement s'affiche et la barre est masquée. Un LLM peut se tromper sur sa propre limite : la valeur affichée en INFO permet de la vérifier.
  • Contexte utilisé = tokens de tout ce que la session enverrait au modèle : prompt système (MAIA.md…), définition des outils, messages (texte, appels d'outils et leurs résultats, +4 tokens de structure par message) et contexte en attente (résultats de commandes !, fichiers @). Le calcul est local et déterministe, avec js-tiktoken (vocabulaire o200k_base) ; la consommation renvoyée par le serveur n'est plus utilisée, car elle faisait varier la valeur. C'est une somme des comptes de chaque élément : la valeur ne fait qu'augmenter quand la conversation s'enrichit, et ne diminue que si le contexte est réduit (/clear, /compact, tour interrompu, autre session). Le comptage est refait avant chaque saisie de l'utilisateur (et après chaque réponse du LLM) ; le compte de chaque message est mémorisé, donc le recalcul reste instantané. Pour les modèles qui n'utilisent pas ce vocabulaire (Claude, Mistral…), la valeur est une bonne approximation, pas le compte exact du serveur.

La zone de saisie est encadrée par une bordure en pointillés (┈) au-dessus et au-dessous seulement, en greenBright, sans bords gauche ni droit. À sa gauche, le spinner arrow3 de cli-spinners s'anime en continu. Pendant l'attente de la réponse après validation de la saisie, « En réflexion » s'affiche avec le spinner dots10 à sa gauche, en yellowBright (même spinner pour les autres attentes : chargement des modèles, exécution d'une commande !).

Contexte Touches
Saisie Entrée : envoyer · ←/→, Ctrl+A/E : se déplacer · ↑/↓ : historique · Ctrl+U/W : effacer · Ctrl+C ou Ctrl+D (saisie vide) : quitter · le collage multi-lignes est conservé
Liste (/model, /load, choix initial) ↑/↓ (ou j/k) · Entrée : valider · Échap : annuler
Confirmation d'une action (Write, Edit, Bash…) o : autoriser · n ou Échap : refuser · Entrée seule est ignorée (réponse explicite exigée)
Pendant un tour de l'agent Ctrl+C : interrompt la requête et restaure la conversation avant cette demande

Dossier de base

Le dossier où MAIA est lancé (ex. ./X) est la base de tous les chemins relatifs : fichiers lus, créés ou modifiés par les outils (Read, Write, Edit, Glob, Grep, Bash), .maia/config.json, MAIA.md / CLAUDE.md et .maia/skills/. Un chemin complet est utilisé tel quel (~/… désigne le dossier personnel). Ce dossier est capturé une fois au démarrage, affiché (« Dossier de travail : … ») et transmis aux outils et aux providers ; les règles de permissions (Read(./.env), Edit(src/**)) sont elles aussi relatives à ce dossier. Seul ~/.maia (config, settings.json et skills utilisateur) est lu depuis le dossier personnel.

Commande ! : exécuter une commande shell

!commande exécute la commande directement, sans passer par le modèle. Deux syntaxes :

Saisie Commande exécutée
!ls · !ls -la src tout ce qui suit le !
!'ls -la' le texte entre guillemets simples (délimite la commande avec ses paramètres)
!'ls' -la src · !'ls -la' | wc -l le texte entre guillemets, puis ce qui suit est ajouté comme paramètres : ls -la src, ls -la | wc -l
!'echo "l\'ami"' \' donne un guillemet simple littéral dans la commande
!'ls -la erreur (guillemet non fermé) : rien n'est exécuté

Le reste du comportement :

  • elle tourne dans le dossier de lancement (voir « Dossier de base ») ; un cd n'a donc pas d'effet durable ;
  • stdout et stderr sont affichés dans l'ordre d'arrivée, avec le code de sortie en cas d'échec ; durée maximale 2 minutes ;
  • aucune confirmation n'est demandée (c'est vous qui la tapez) et les règles de permissions des outils ne s'appliquent pas ;
  • le résultat est ajouté au contexte de votre prochaine demande (jusqu'à 30 000 caractères, milieu tronqué au-delà), pour que le modèle sache ce qui a été lancé. /clear l'efface ;
  • Ctrl+C interrompt la commande ; ! seul affiche l'aide. Les programmes interactifs (vim, less, saisie au clavier…) ne sont pas pris en charge.

Références @fichier

@chemin/vers/fichier dans votre message injecte le contenu du fichier dans le contexte de la demande (en plus de le laisser dans le texte) :

Saisie Effet
explique @src/a.ts injecte src/a.ts
compare @a.ts et @ b.ts plusieurs fichiers ; la forme @ chemin (avec espace) est acceptée
lis @'mon dossier/a b.txt' guillemets simples pour un chemin avec espaces (\' : guillemet littéral)
résume @docs un dossier : la liste de ses entrées est injectée
@/chemin/complet.txt · @~/notes.md chemin complet, ou depuis le dossier personnel
  • Chemins relatifs au dossier de lancement (voir « Dossier de base »). Aucune confirmation : c'est vous qui citez le fichier, les règles de permissions des outils ne s'appliquent pas.
  • Un @mot qui ne désigne aucun fichier existant reste du texte ordinaire (@mention, @Override, jean@exemple.fr) ; un avertissement n'apparaît que s'il ressemble à un chemin (@src/x.ts introuvable).
  • Limites : 2 Mo par fichier, 50 000 caractères injectés par fichier (milieu tronqué au-delà) et 150 000 par message ; les fichiers binaires sont ignorés ; un même fichier cité plusieurs fois n'est injecté qu'une fois.
  • Le contenu est marqué comme donnée, pas comme instructions. Les @fichier écrits dans le corps d'une commande personnalisée ne sont pas encore traités (seuls ceux de votre saisie le sont).
  • Pas d'autocomplétion des chemins à la saisie pour l'instant.

Sessions (./.maia/.sessions)

Au lancement, ./.maia et ./.maia/.sessions sont créés dans le dossier d'exécution s'ils n'existent pas. Les sessions sont une fonctionnalité de l'application.

  • Sauvegarde automatique à la sortie (/exit, Ctrl+D, SIGTERM/SIGHUP). Une session sans aucun message n'est pas enregistrée.
  • Nom par défaut : session_{date}_{heure}_{minutes}_{secondes} (ex. session_2026-10-13_01_04_05).
  • /rename <nom> : renomme la session courante ; ce nom sera utilisé à la sauvegarde.
  • /load : sans argument, liste les sessions enregistrées (sélection) ; /load <nom> recharge cette session (le modèle courant est conservé).
  • /new : nouvelle session, nouveau contexte (MAIA.md / CLAUDE.md et skills rechargés) ; l'utilisateur est averti que la session quittée est sauvegardée (sous son nom).

Chaque session est un fichier JSON : elle peut contenir des extraits de fichiers ou de sorties de commandes. .maia/ est ajouté au .gitignore.

Skills : appel direct avec §

§nom_du_skill [arguments] lance un skill de l'agent (ex. §sk_test) ; § seul liste les skills disponibles. L'agent expose ses skills via ModeInitResult.skills (contrat AgentSkill : name, description, invoke(args)), ce qui rend l'appel indépendant du format propre à l'agent. invoke retourne le prompt envoyé au LLM. Ce prompt contient les instructions du SKILL.md (.maia/skills/<nom>/) (avec $ARGUMENTS / ARGUMENTS:), sans passer par l'outil Skill du modèle.

Accès aux LLMs : LiteLLM

MAIA ne parle qu'à un serveur LiteLLM : toutes les requêtes de tous les agents sont envoyées au format OpenAI (chat/completions, en streaming, avec appels d'outils) vers le endpoint OpenAI de LiteLLM.

Au démarrage, le fichier ~/.maia/settings.json (dossier de l'utilisateur, pas celui de lancement) est chargé :

{
  "LiteLLM_URL": "http://localhost:4000",
  "OpenAI_EndPoint": "/v1/chat/completions",
  "LiteLLM_Models": "/v1/models"
}
  • LiteLLM_URL : URL du serveur LiteLLM.
  • OpenAI_EndPoint : suffixe ajouté à LiteLLM_URL pour envoyer une requête au format OpenAI.
  • LiteLLM_Models : suffixe ajouté à LiteLLM_URL pour récupérer la liste des modèles disponibles.
  • Optionnel : LiteLLM_API_Key (ou variable d'environnement LITELLM_API_KEY), envoyée en Authorization: Bearer …. Ce fichier peut donc contenir un secret : ne pas le versionner.
  • Optionnel : MaxIterations (entier, défaut 50) : étapes (appel du LLM puis des outils) par série avant que MAIA demande s'il faut continuer.
  • Optionnel : MaxOutputTokens (entier, ex. 16000) : max_tokens envoyé à chaque requête. Sans lui, le serveur applique sa limite par défaut, parfois courte : un long fichier écrit par l'outil Write peut être coupé (le paramètre content arrive alors absent). MAIA affiche un avertissement quand la réponse est coupée (finish_reason length, ou sortie qui tombe pile sur une limite usuelle 4096/8192… avec un appel d'outil : certains serveurs « réparent » alors l'appel et annoncent tool_calls). Cas constaté avec anthropic--claude-4.5-sonnet : completion_tokens: 4096 et Write reçu avec path seul → fixer par ex. "MaxOutputTokens": 32000.
  • Diagnostic : MAIA_DEBUG_LLM=1 maia enregistre le flux brut renvoyé par LiteLLM dans maia-llm-debug.log (dossier temporaire ; MAIA_DEBUG_LLM=/chemin/fichier.log pour choisir le fichier).

Si le fichier est absent ou incomplet, MAIA s'arrête avec un message qui en donne un exemple.

Choix du modèle : au démarrage (sauf si --model ou MAIA_MODEL le définit), MAIA récupère les modèles auprès de LiteLLM et demande de choisir celui de la session. /model rouvre cette liste en cours de session (la conversation est conservée). Spécification explicite : litellm:<nom du modèle> (ou le nom seul).

Agent MAIA : contexte et skills

Au lancement, le contexte de départ est chargé depuis le dossier de lancement : MAIA.md s'il existe, sinon CLAUDE.md (s'il existe). Le fichier chargé est indiqué par un message INFO ; si aucun des deux n'existe, un WARN le signale. Seul ce dossier est regardé (pas les dossiers parents).

Les dossiers .claude ne sont plus lus (ni settings.json, ni commands/, ni skills/, ni .claude/CLAUDE.md, ni ~/.claude).

  • Skills : .maia/skills/<nom_du_skill>/SKILL.md (ou skill.md), chargés depuis trois emplacements. Le plus spécifique l'emporte en cas de même nom :

    1. le dossier utilisateur : ~/.maia/skills ;
    2. le dossier parent du dossier de lancement : ex. /X/.maia/skills pour un lancement dans /X/Y ;
    3. le dossier de lancement : ex. /X/Y/.maia/skills.

    Le fichier commence par un en-tête YAML : description (objectif du skill) et name (optionnel ; s'il est présent, il doit être identique au nom du dossier, qui fait foi). Seule la liste (nom + description) est mise dans le prompt ; le contenu est chargé à la demande par l'outil Skill, ou lancé directement avec §nom.

---
name: sk_test          # optionnel, = nom du dossier
description: Lance les tests du projet et résume les échecs
---
Instructions du skill… ($ARGUMENTS = arguments passés avec §sk_test)

Conséquence : il n'y a plus de variables d'environnement venant de la configuration de l'agent ni de slash-commandes personnalisées. Les permissions sont décrites dans la section suivante.

Permissions

Guide complet avec exemples (commande simple, commande avec tube, interdiction d'un rm caché) : GUIDE_PERMISSIONS.md.

Les permissions s'appliquent aux outils (intégrés ou non). Trois types de règles :

Type Effet
allow l'outil est utilisé sans approbation manuelle
ask l'utilisateur doit confirmer chaque utilisation (badge ASK, réponse o/n)
deny l'outil ne peut pas être utilisé (l'agent reçoit un refus)

Ordre d'évaluation : deny, puis ask, puis allow. La première famille qui correspond décide, quelle que soit la précision des règles : un allow étroit comme Bash(aws s3 ls) ne fait pas exception à un deny large comme Bash(aws *), ni à un ask plus large. Sans règle applicable, c'est ask qui s'applique (pour tous les outils, y compris Read, Glob, Grep…).

Où les définir : ~/.maia/settings.json (utilisateur) et ./.maia/settings.json (dossier de lancement). Les listes des deux fichiers s'additionnent. Le fichier ../.maia/settings.json du dossier parent n'est pas pris en compte. Dans chaque fichier, la propriété permissions contient une liste par type, tous facultatifs :

"permissions": {
  "allow": ["Bash(allow stop *)"],
  "ask":   ["Bash(allow ask *)"],
  "deny":  ["Bash(allow deny *)", "Read(./.deny.md)"]
}

Format d'une règle : Outil (tous les appels de l'outil) ou Outil(motif) :

Outil Le motif porte sur Exemples
Bash la commande ; * = n'importe quelle suite ; cmd * couvre cmd avec ou sans arguments (mais pas cmdx) ; sans * : commande exacte Bash(allow stop *), Bash(git status), Bash(npm run test:*)
Read, Write, Edit, Glob, Grep le chemin (relatif au dossier de lancement, motifs glob) Read(./.deny.md), Edit(src/**)
WebFetch le domaine WebFetch(domain:docs.example.com), WebFetch(domain:*.example.org)
Skill le nom du skill Skill(pdf)
autres outils (ex. ajoutés plus tard) une valeur texte de l'entrée de l'outil (* accepté) mcp__db__query(SELECT *)

Précisions sur Bash :

  • commande composée (&&, ;, |) : un deny ou un ask s'applique dès qu'une des parties correspond, y compris cachée dans $(…), des backticks ou des parenthèses ; un allow ne s'applique que si toutes les parties sont couvertes ;
  • un allow ne couvre jamais une commande avec substitution ($(…), backticks) : elle repasse en ask ; une redirection de sortie (>, >>, 2>, &>) repasse en ask sauf si le fichier visé est autorisé par une règle Redirect(chemin) (2>&1 n'écrit dans aucun fichier : sans effet). deny: ["Redirect(.env)"] interdit d'écrire dans ce fichier par redirection, même avec Bash autorisé. Détails : GUIDE_PERMISSIONS.md § 8.

Au démarrage, MAIA indique (INFO) les fichiers chargés et le nombre de règles, ou qu'aucune règle n'est définie ; un JSON invalide ou une règle mal formée donne un WARN et est ignoré. Les deny sont du « meilleur effort » : ils ne remplacent pas un vrai bac à sable. Les commandes ! et les références @fichier tapées par l'utilisateur ne passent pas par ces règles.

Attention : ./.maia/settings.json d'un dépôt tiers peut contenir des règles allow larges ; relisez-le avant de lancer MAIA dans un projet que vous ne connaissez pas.

La commande /permissions affiche les règles actives, fichier par fichier, avec un badge ALLOW (vert), ASK (jaune) et DENY (rouge) suivi des règles du type, une par ligne ; seuls les types non vides sont montrés. Après avoir modifié un settings.json, /clear recharge la configuration.

Exemple pour éviter les confirmations sur les outils en lecture seule : voir settings.example.json.

Outils TodoWrite, WebFetch et CurrentModel

  • TodoWrite : liste de tâches de la session (pending / in_progress / completed, forme activeForm pour la tâche en cours). Chaque appel remplace la liste ; le résultat (☐ ▶ ☑) est affiché à l'utilisateur et renvoyé au modèle. Un avertissement est ajouté si plusieurs tâches sont « en cours ».
  • CurrentModel : renvoie le modèle LLM utilisé par la session (valeur à jour après un /model). Sans paramètre, sans confirmation ; le modèle est lu au moment de l'appel.
  • WebFetch : télécharge une URL http(s) (HTML converti en Markdown avec turndown, JSON mis en forme, texte brut ; 5 Mo et 30 s maximum, max_length caractères renvoyés). Pas de JavaScript exécuté, pas de PDF ni d'images.
    • Confirmation demandée par défaut ; règle allow possible : WebFetch(domain:docs.example.com) (voir « Permissions »).
    • Une redirection vers un autre domaine n'est pas suivie : l'outil renvoie la nouvelle URL, que le modèle doit rappeler (ce qui repasse par la confirmation).
    • Le contenu est signalé au modèle comme non fiable (risque d'injection de prompt) ; il ne passe par aucun second modèle de résumé.

Outils personnalisés (./.maia/tools)

L'utilisateur peut créer ses propres outils programmatiques : le LLM les appelle comme n'importe quel autre outil quand leur description correspond au besoin. Ils sont chargés au démarrage et rechargés par /clear et /new.

  • Où : uniquement dans ./.maia/tools du dossier de lancement (jamais le dossier parent ni le dossier de l'utilisateur). Un sous-dossier = un outil : ./.maia/tools/addition/TOOL.json (tool.json accepté aussi) et ses scripts. Un exemple complet est fourni dans examples/tools/addition ; exemple de sous-agent : examples/agents/code_reviewer (à copier dans ./.maia/agents).(à copier dans ./.maia/tools/).
  • TOOL.json :
{
  "name": "Addition",
  "description": "Faire une addition avec 3 chiffres. Renvoie un chiffre, la somme",
  "command": {
    "parameters": [
      { "name": "param1", "type": "number", "default": "0" },
      { "name": "param2", "type": "number", "default": "0" },
      { "name": "param3", "type": "number", "default": "0" }
    ],
    "run": "node ./test.js {{param1}} {{param2}} {{param3}}"
  }
}
Champ Règle
description Obligatoire : dit au LLM à quoi sert l'outil et quand l'appeler.
name Facultatif, le nom du dossier par défaut. Lettres, chiffres, _ et - (64 caractères maximum). Ne peut pas reprendre le nom d'un outil intégré (Read, Bash…) ni d'un autre outil personnalisé.
command.parameters Paramètres fournis par le LLM. type : "number" ou "string". default : valeur utilisée si le LLM n'en donne pas (ex. {{param2}} devient 0) ; sans default, le paramètre est obligatoire. description facultative par paramètre.
command.run Ligne de commande ; chaque {{paramètre}} est remplacé par sa valeur. ./ désigne le dossier de l'outil (./test.js = ./.maia/tools/addition/test.js) et c'est le dossier d'exécution.
  • Sécurité : pas de shell. La ligne run est découpée en arguments (guillemets simples ou doubles possibles) et exécutée directement : une valeur envoyée par le LLM reste toujours un seul argument (; rm -rf / ou $(cmd) sont reçus tels quels par le script, jamais interprétés). En contrepartie, run n'accepte ni tubes |, ni redirections >, ni variables $HOME ; pour cela, appeler un script (sh ./script.sh {{p}}). Le programme (premier mot) ne peut pas être un {{paramètre}}. Sous Windows, les scripts .cmd/.bat ne sont pas lancés directement (utiliser node, python, ou cmd /c ./script.cmd).
  • Exécution : timeout de 2 minutes, sortie limitée à 30 000 caractères ; un code de sortie non nul est renvoyé au LLM comme une erreur (avec la sortie d'erreur du script). Le script reçoit MAIA_CWD (dossier de lancement de MAIA, pour retrouver le projet) et MAIA_TOOL_DIR.
  • Permissions : un outil personnalisé exécute un programme, donc il demande confirmation par défaut. Les règles de permissions s'appliquent avec son nom : "Addition" dans allow l'autorise sans demander, "Addition(5)" cible une valeur de paramètre.
  • Erreurs : une définition invalide (description absente, type inconnu, {{param}} non déclaré, JSON cassé…) est signalée par un avertissement (WARN) et l'outil est ignoré ; les autres restent chargés. Une virgule en trop avant ] ou } est tolérée. Au chargement, les outils valides sont annoncés (INFO).

Exécution des outils : premier plan et parallèle

  • Le raisonnement du LLM reste bloquant : l'utilisateur attend la réponse, ce qui préserve l'intégrité du contexte.
  • 1 outil demandé : exécuté au premier plan (annonce, confirmation éventuelle, exécution, résultat).
  • 2 outils ou plus dans la même réponse : chaque outil part dans sa propre tâche, en arrière-plan, dès qu'il est autorisé. L'agent attend la fin de toutes les tâches, puis renvoie les résultats au LLM dans l'ordre où il les a demandés. Une erreur ou un refus n'annule pas les autres outils.
  • Le spinner indique « Outils en parallèle : n/total terminés (…) ».
  • Limites : ce sont des tâches asynchrones Node (pas des threads système ; les processus lancés par Bash ou les outils personnalisés tournent réellement en parallèle) ; 8 outils au plus en même temps (MAX_PARALLEL_TOOLS) ; les confirmations sont posées une par une ; les appels qui portent sur le même fichier (Read/Write/Edit) ou TodoWrite passent l'un après l'autre (Tool.lockKey).
  • Code : packages/core/src/tool-runner.ts, tests tool-runner.test.ts.

Compactage (/compact et automatique)

Le compactage résume toute la conversation de la session pour libérer du contexte.

  1. La conversation actuelle est sauvegardée telle quelle sous son nom.
  2. Le LLM reçoit la demande « Faire un résumé compacté de nos échanges… » (5 rubriques : Contexte du projet, Objectif actuel, Fichiers pertinents, Décisions/Conclusions, Prochaines étapes), limité à 4000 tokens. Les commandes ! et fichiers @ pas encore envoyés font partie de ce qui est résumé.
  3. Le contexte est supprimé comme avec /clear, mais MAIA.md / CLAUDE.md ne sont pas rechargés : le résumé les remplace comme contexte de départ (dans le prompt système). Outils, skills et permissions sont rechargés.
  4. La session continue sous le nom {nom d'origine}__{n} : xxx → xxx__1 → xxx__2… Après __1000, le suivant reprend à __1 et écrase ce fichier. Le fichier d'avant compactage reste intact ; le fichier xxx__n contient le résumé (startContext) et la suite de la conversation, et /load xxx__n le restaure.
  5. Le nombre de tokens est recalculé et la barre de progression mise à jour.

Déclenchement automatique : dès que le contexte dépasse Seuil_max = Max_Token_Context - (4000 + 500) (4000 = tokens du résumé, 500 = marge d'erreur). La taille maximale est celle demandée au LLM au choix du modèle ; si elle est inconnue (barre masquée), seul /compact fonctionne. Le contrôle a lieu avant l'envoi de chaque demande et après chaque tour.

L'appel est bloquant (pas de tâche de fond). Ctrl+C l'annule sans rien modifier ; en cas d'erreur, la conversation reste inchangée.

Code : packages/core/src/compact.ts, sessions.ts (nextCompactedName, startCompacted), packages/agent/src/init.ts (startContext), orchestration dans packages/cli/src/index.ts.

Contexte refusé par le serveur : si LiteLLM répond « Input is too long » (ou équivalent), MAIA compacte automatiquement la conversation (résultats d'outils tronqués à 1 500 caractères dans la demande de résumé, pour qu'elle tienne), puis reprend la demande une seule fois. Si le compactage échoue aussi, l'erreur d'origine est affichée (utilisez alors /new). Les sous-agents n'ont pas ce mécanisme.

Sous-agents (./.maia/agents)

MAIA peut confier une tâche à un sous-agent : un processus Node autonome, avec sa propre conversation (vide) et sa propre définition. L'agent principal le lance avec l'outil Agent (agent = nom, task = tâche complète : le sous-agent ne voit rien de la conversation principale). Seul le résumé final du sous-agent revient dans la conversation principale. Plusieurs appels Agent dans une même réponse s'exécutent en parallèle.

Définition : un dossier par agent, avec un fichier AGENT.md, dans ./.maia/agents/<nom>/ (dossier de lancement) ou ~/.maia/agents/<nom>/ (utilisateur) ; le projet l'emporte sur l'utilisateur à nom égal. Créer un agent : /createAgent (ou /createAgent mon_agent) demande le niveau (Projet / Global) et le nom, puis copie le modèle .ressources/AGENT.md dans <dossier agents>/<nom>/AGENT.md en y mettant le bon name: (jamais d'écrasement d'un agent existant). Le modèle explique chaque propriété en commentaires (#) ; les propriétés facultatives y sont commentées, supprimez le # pour les activer. Tapez ensuite /clear pour charger le nouvel agent. Le dossier .ressources est celui de MAIA (racine du projet MAIA, ou MAIA_RESSOURCES), pas celui de votre projet. Les commentaires # … en fin de ligne de l'en-tête sont acceptés (mettez la valeur entre guillemets si elle contient #).

Les agents chargés sont listés au démarrage (« Agents chargés : ») et avec /agents.

---
name: code_reviewer            # doit être le nom du dossier
description: Révise du code (sécurité, anti-patterns). Ne pas l'utiliser pour écrire du code.
model: sonnet                  # optionnel — défaut : modèle de l'agent principal
tools: Read, Grep, Write       # optionnel — défaut : mêmes outils que l'agent principal (sans Agent)
permissions:                   # optionnel — s'ajoutent aux règles héritées
  allow: [Read, Grep]
  deny: [Bash]
effort: medium                 # low | medium | high — défaut medium
skills: [api-docs, testing-guide]   # optionnel — défaut : tous les skills
omitMd: false                  # défaut false : MAIA.md / CLAUDE.md ajouté au contexte
trace: false                   # défaut false : true enregistre la conversation (voir plus bas)
---
# Instructions system
Tu es un expert en révision de code.

# Missions
- identifier les problèmes de sécurité
- détecter les anti-patterns
- suggérer des améliorations

# Output
Pour chaque problème : catégorie (CRITICAL / HIGH / MEDIUM / LOW), description, code concerné, solution recommandée.
  • Les trois sections « Instructions system », « Missions » et « Output » sont obligatoires (titres Markdown de n'importe quel niveau) ; un agent invalide est ignoré avec un avertissement qui dit pourquoi.
  • Héritage : sans tools, skills ou permissions, le sous-agent a les mêmes outils (y compris les outils personnalisés), skills et règles que l'agent principal. Les règles permissions de l'agent s'ajoutent aux règles héritées : deny > ask > allow reste vrai, un deny ou ask hérité n'est jamais levé. Un sous-agent ne peut pas lancer de sous-agent.
  • Résumé : la dernière réponse du sous-agent suit la section Output ; s'il épuise son budget d'étapes sans conclure, un appel supplémentaire lui demande ce résumé.
  • Effort : borne le nombre d'étapes du sous-agent (low 10, medium 25, high 50) et module son prompt.
  • Modèle : identifiant exact ou fragment (sonnet → premier modèle LiteLLM dont le nom contient « sonnet ») ; introuvable → modèle de l'agent principal, avec avertissement.
  • Autorisations : si le sous-agent doit demander une confirmation, la question s'affiche dans l'interface principale, préfixée [sous-agent nom] (une question à la fois). Ctrl+C interrompt aussi les sous-agents en cours.
  • Lancement par l'utilisateur : une saisie qui commence par & lance un sous-agent directement : &code_reviewer relis src/a.ts (le nom est reconnu sans tenir compte de la casse ; sans tâche, l'agent exécute sa mission telle que décrite). & seul liste les agents. Aucune confirmation de l'outil Agent n'est demandée (c'est vous qui lancez), mais les autorisations des outils du sous-agent suivent les règles habituelles. Le résumé s'affiche et est joint à votre prochaine demande, comme le résultat d'une commande !. Ctrl+C interrompt le sous-agent.
  • Fin de mission : un message INFO est affiché dans l'interface principale (durée, nombre d'appels d'outils) ; un échec est signalé par un avertissement et renvoyé à l'agent principal comme erreur d'outil. Délai maximal : 30 minutes.
  • Trace (trace: true) : la conversation du sous-agent est enregistrée dans ./.maia/.sessions/ sous le nom {session de l'agent principal}-{agent}, ex. session_2026-10-20_00_01_53-agent_1.json (_2, _3… si le nom est pris). Sans trace, rien n'est enregistré.

Serveurs MCP (distants)

MAIA peut utiliser les outils de serveurs MCP accessibles par une URL (Streamable HTTP, ou l'ancien transport SSE). Pas d'OAuth ; seuls les outils MCP sont utilisés (pas les ressources ni les prompts).

1. Déclarer les serveurs (global uniquement) : ~/.maia/mcp.json. La clé est le nom du serveur.

{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" },
      "excludeTools": ["delete_repository"]
    },
    "docs": { "url": "https://docs.example.com/mcp", "timeout": 60 }
  }
}
Propriété Rôle
url obligatoire (http:// ou https://)
type http (défaut, Streamable HTTP) ou sse (ancien transport)
headers en-têtes HTTP (jetons, clés d'API)
timeout secondes, défaut 30 : connexion et chaque appel
includeTools / excludeTools liste de noms d'outils (* accepté) retenus / retirés
enabled false : le serveur est ignoré
description commentaire libre

${VARIABLE} est remplacé par la variable d'environnement (dans url et headers) ; une variable absente donne un avertissement et une valeur vide.

2. Activer par projet : aucun serveur n'est actif par défaut. Il faut l'autoriser dans le settings.json du projet :

{ "permissions": { "allow": ["Mcp(github)"], "deny": ["Mcp(github,delete_repository)"] } }
  • Mcp(serveur) : active le serveur, tous ses outils sont autorisés sans confirmation ;
  • Mcp(serveur,outil) : un outil (* accepté, ex. Mcp(github,delete_*)) ; en deny l'outil n'est jamais proposé au modèle ; en ask il demande confirmation ;
  • Mcp(*) (= Mcp(*,*)) : tous les serveurs déclarés et tous leurs outils ; Mcp(*,outil) : cet outil sur tous les serveurs ;
  • allow: ["Mcp(github)"] + deny: ["Mcp(github,*)"] : serveur désactivé, MAIA ne cherche pas à s'y connecter.

Les permissions sont traitées avant toute connexion ; seuls les serveurs activés sont ensuite contactés (en parallèle). Un serveur en échec n'empêche pas MAIA de démarrer : avertissement, état FAIL.

3. Utilisation : les outils s'appellent mcp__serveur__outil. /mcp liste les serveurs activés : bandeau vert OK s'il est connecté, bandeau rouge FAIL (avec la cause en grisé) sinon. Les connexions sont coupées à la sortie de MAIA (/exit, Ctrl+D, SIGTERM/SIGHUP) et refaites à chaque /clear, /new, /load, /model et /compact. Les sous-agents n'utilisent pas les outils MCP.

Reste à faire

  • Provider LiteLLM : validation sur un vrai serveur (streaming SSE et appels d'outils testés seulement sur des flux simulés) ; compatibilité de @inkjs/ui avec Ink 7 à confirmer à l'usage (l'affichage des badges a été vu fonctionner par l'utilisateur).
  • Agent : hooks ; MCP : OAuth, ressources/prompts, outils MCP pour les sous-agents ; sous-agents : reasoning_effort du modèle non utilisé (l'effort borne les étapes), pas de sous-agent imbriqué.
  • Éventuellement rétablir sous .maia ce que supprimait l'abandon de .claude : variables d'environnement et slash-commandes personnalisées (le code des commandes existe encore dans packages/agent/src/commands.ts, il n'est plus appelé).
  • @fichier dans le corps d'une commande personnalisée ; autocomplétion des chemins @ à la saisie.
  • Liste à cocher (choix multiples) : n'existe pas encore ; elle recevrait le badge ASK.
  • Taille de contexte : lue en interrogeant le LLM (demande de l'utilisateur) ; la route /model/info de LiteLLM (max_input_tokens) serait plus fiable, et un réglage manuel dans settings.json pourrait corriger une réponse erronée.
  • Nerd Fonts (icônes) : proposé comme réglage optionnel dans settings.json, non réalisé.
  • Tests : les suites vitest et le typecheck sont à lancer après pnpm install (écrits sans avoir pu être exécutés dans l'environnement de développement).

Travaux réalisés

Journal des évolutions, dans l'ordre :

  1. Squelette : monorepo pnpm (TypeScript strict, tsup, vitest), contrats LLMProvider / Tool / AgentUI, boucle agent avec appels d'outils, agent par défaut de type « Claude Code » (outils Read, Write, Edit, Bash).
  2. Provider SAP AI Core puis choix du modèle au démarrage et commande /model. (Remplacé ensuite par LiteLLM, voir 12.)
  3. Outils Glob et Grep (JavaScript pur, sans ripgrep).
  4. Configuration de l'agent (lot 2) : fichiers de contexte, permissions, slash-commandes, skills. (Abandonné avec les dossiers .claude, voir 13.)
  5. §skill : appel direct d'un skill de l'agent.
  6. Dossier de lancement : tous les chemins relatifs partent du dossier où MAIA est lancé (MAIA_CWD > INIT_CWD > cwd).
  7. Interface Ink (remplace Clack.js) : bannière « MAIA » avec masque pixel-art et titre arc-en-ciel, messages en bleu clair, zone de saisie en pointillés greenBright avec spinner arrow3, attente « En réflexion » avec dots10 en yellowBright.
  8. Outils TodoWrite et WebFetch (lot 3, première partie).
  9. !commande (avec la syntaxe !'commande avec paramètres') et @fichier.
  10. Renommage en MAIA (titre « Maia ») et sessions : ./.maia/.sessions, sauvegarde automatique, /rename, /load (avec réaffichage du contenu), /new.
  11. Corrections : import manquant de normalizeSessionName, session.tools est une Map, en-tête de skill avec espace avant les deux-points (description : …).
  12. LiteLLM : un seul provider, requêtes au format OpenAI en streaming vers le endpoint OpenAI de LiteLLM ; configuration ~/.maia/settings.json (LiteLLM_URL, OpenAI_EndPoint, LiteLLM_Models, LiteLLM_API_Key optionnel). Suppression de SAP AI Core, Anthropic, OpenAI et Ollama.
  13. Renommage technique magiccode → maia (packages @maia/*, commande maia, variables MAIA_*, dossier .maia) ; textes « MagicCode » → « MAIA » ; bannière « Un seul agent, plusieurs modèles ».
  14. Skills dans .maia/skills (dossier de lancement, dossier parent, dossier utilisateur) ; les dossiers .claude ne sont plus lus.
  15. Badges @inkjs/ui : INFO, WARN, FAIL (texte d'erreur #DC8ADD) et ASK devant les questions.
  16. Suppression des modes et des extensions : un seul agent MAIA (packages/agent), contexte de départ MAIA.md sinon CLAUDE.md (message INFO, ou WARN si aucun).
  17. Outil CurrentModel : indique au modèle quel LLM est utilisé dans la session.
  18. /clear : supprime le contexte et recharge MAIA.md / CLAUDE.md (et les skills) comme au démarrage ; /new fait de même.
  19. Permissions granulaires : règles allow / ask / deny par outil (Outil ou Outil(motif)), évaluées dans l'ordre deny > ask > allow, ask par défaut ; définies dans ~/.maia/settings.json et ./.maia/settings.json (pas le dossier parent).
  20. /permissions : affiche les permissions chargées, par fichier et par type (badges ALLOW / ASK / DENY) ; les fichiers sont listés l'un sous l'autre au démarrage.
  21. Rendu markdown des réponses de l'agent via markdansi, dans un seul composant isolé (packages/ui/src/markdown.tsx), désactivable avec MAIA_NO_MARKDOWN=1.
  22. Image de la bannière : nouvelle image (visage à couronne), fond retiré, réduite à 32 × 32 en couleurs réelles (sans pixelisation ni réduction de palette), centrée verticalement à côté du titre ; données générées par packages/ui/scripts/make-mask.py.
  23. Barre de progression du contexte sous la zone de saisie (1 carré = 10 000 tokens) : taille maximale demandée au LLM après la sélection du modèle, contexte utilisé mis à jour après chaque réponse (consommation renvoyée par LiteLLM, sinon estimation) ; correction d'un import de type manquant dans ink-ui.tsx. Valeur alignée sur le contexte réel de la session : contexte en attente compté, tokens de raisonnement exclus, estimation calibrée sur le serveur et signalée par ~.
  24. Outils personnalisés : ./.maia/tools/<nom>/TOOL.json (dossier de lancement uniquement) ; paramètres number/string avec valeurs par défaut, run avec {{paramètre}} exécuté sans shell dans le dossier du tool (aucune injection possible), permission ask par défaut ; module packages/agent/src/custom-tools.ts, exemple examples/tools/addition ; exemple de sous-agent : examples/agents/code_reviewer (à copier dans ./.maia/agents).
  25. Threading async des outils : 1 outil au premier plan ; dès 2 outils, une tâche de fond par outil, attente de toutes les fins avant de répondre au LLM (résultats dans l'ordre demandé, 8 en parallèle au plus, verrou par fichier).
  26. Compactage : /compact et compactage automatique au seuil Max_Token_Context - (4000 + 500) ; résumé en 5 rubriques comme contexte de départ (à la place de MAIA.md / CLAUDE.md), session renommée xxx__n (cycle 1..1000), barre de contexte recalculée.
  27. Correction de la barre de contexte : comptage local avec js-tiktoken (o200k_base) de tout le contexte (système, outils, messages, contexte en attente), recalculé avant chaque saisie ; fin du mélange consommation du serveur / estimation calibrée (cause des variations) et du marqueur ~.
  28. Documentation : SPECS_TECH.md, spécifications techniques de toutes les fonctionnalités implémentées.
  29. Outil Write plus tolérant : message d'erreur qui liste les paramètres reçus (tous les outils), alias de content acceptés, description du paramètre précisée.
  30. Appels d'outils coupés ou mal assemblés : paramètre optionnel MaxOutputTokens, avertissement quand la réponse est coupée (max_tokens), assemblage plus tolérant des fragments d'arguments d'appels d'outils du flux SSE, journal de diagnostic MAIA_DEBUG_LLM.
  31. Coupure à 4096 tokens diagnostiquée (log MAIA_DEBUG_LLM) : détection d'une sortie tombant sur une limite usuelle avec un appel d'outil, message d'erreur de Write qui conseille d'écrire en plusieurs morceaux.
  32. Outil Bash via runShell : timeout et Ctrl+C arrêtent toute l'arborescence de processus (groupe de processus, SIGTERM puis SIGKILL) ; stdout et stderr fusionnés dans l'ordre d'arrivée, dernière ligne [code de sortie : N] ou état (délai dépassé, interrompu).
  33. Sous-agents : définitions ./.maia/agents/<nom>/AGENT.md et ~/.maia/agents (en-tête YAML : name, description, model, tools, permissions, effort, skills, omitMd, trace ; sections Instructions system / Missions / Output), outil Agent, processus autonome (fork + IPC), conversation vide, résumé final seul renvoyé au parent, confirmations relayées à l'interface principale, notification INFO de fin, trace optionnelle session_…-agent_1.json, commande /agents.
  34. Compactage sur « Input is too long » : réponse du serveur reconnue (isContextTooLongError), compactage d'urgence (résultats d'outils tronqués dans la demande de résumé), reprise de la demande une fois.
  35. Barre de contexte pendant le travail de l'agent : taille recomptée à chaque itération (après la demande, chaque réponse du LLM et chaque série de résultats d'outils) et barre affichée pendant les itérations, plus seulement à la saisie.
  36. Barre de contexte toujours affichée : en bas de l'écran en permanence (saisie, itérations, outils, confirmations, listes) ; si la taille maximale du contexte est inconnue, seul le nombre de tokens utilisés est montré.
  37. Zone de saisie toujours visible : grisée pendant le travail de l'agent (« Traitement en cours — Ctrl+C : interrompre »), et c'est elle qui porte les demandes d'autorisation (ASK … (o/n)), dans le même cadre en pointillés.
  38. Ligne d'état réservée au-dessus de la zone de saisie (« En réflexion… », outils en cours) : toujours présente, vide hors traitement, pour que la zone de saisie ne remonte ni ne descende.
  39. Limite d'itérations : 50 par défaut, réglable avec MaxIterations dans ~/.maia/settings.json ; à la limite, MAIA demande s'il faut continuer (o/n) pour une nouvelle série ; une demande tapée après un arrêt est fusionnée dans le dernier message utilisateur (alternance respectée).
  40. Guide des permissions : GUIDE_PERMISSIONS.md (syntaxe, exemples simples et complexes, interdiction d'une commande cachée dans un tube, limites).
  41. Signature dans la bannière : « Arnaud VINCENT (avec ClaudeCode) » sous « Un seul agent, plusieurs modèles » (ligne séparée par un espace ; nom en #EEF527, ClaudeCode en #F57D27).
  42. Lancement direct d'un sous-agent : une saisie qui commence par & (&Agent_1 tâche…) exécute le sous-agent sans passer par le LLM ; son résumé est affiché et ajouté au contexte.
  43. /createAgent (ex-/newAgent) : création guidée d'un sous-agent (niveau projet ou global) à partir du modèle commenté .ressources/AGENT.md ; commentaires de fin de ligne acceptés dans l'en-tête de AGENT.md, commentaires HTML des sections ignorés.
  44. Règles Redirect : autoriser, demander ou interdire les redirections de sortie (>, >>, 2>, &>) fichier par fichier ; sans règle, elles demandent confirmation ; 2>&1 et les > entre guillemets ne comptent pas ; guide mis à jour (§ 8).
  45. Serveurs MCP distants : déclaration globale ~/.maia/mcp.json (expansion ${VAR}, type http/sse, headers, timeout, includeTools/excludeTools), aucun serveur actif par défaut, activation par Mcp(serveur) dans le settings.json, Mcp(serveur,outil) pour retirer/confirmer un outil, Mcp(serveur,*) en deny désactive sans connexion ; connexion parallèle après traitement des permissions ; outils mcp__serveur__outil ; commande /mcp (bandeaux OK vert / FAIL rouge).