Référence : les hooks
Le bloc
hooks:du fichier de configuration : les événements, le JSON qu'un hook lit et écrit, l'environnement dans lequel il s'exécute, et ce que chaque décision fait au tour.
Configuration
hooks:
preGeneration: [<hook>, …]
preToolCall: [<hook>, …]
postToolCall: [<hook>, …]
postGeneration: [<hook>, …]
Chaque événement prend une liste ; les hooks d'un événement s'exécutent dans l'ordre où ils sont écrits. Un événement absent, ou une liste vide, n'exécute rien. Pas de bloc hooks: = aucun hook.
Entrée d'un hook
| Clé | Type | Défaut | Description |
|---|---|---|---|
command |
chaîne | — (obligatoire) | Exécutée par sh -c "<command>". Ne doit pas être vide ni blanche. |
matcher |
chaîne (expression régulière, syntaxe Go) | "" (tous les outils) |
preToolCall et postToolCall seulement. Le hook s'exécute pour les outils dont le nom correspond. Non ancrée : bash correspond aussi à my_bash ; ^bash$ ne correspond qu'à bash. |
timeout |
durée | 10s |
Durée maximale d'une exécution avant qu'elle soit tuée. 0 = la valeur par défaut. Doit être ≥ 0. |
onError |
fail | warn | ignore |
warn |
Ce que fait une exécution en échec (voir Échecs). |
Exemple
hooks:
preGeneration:
- command: node "$MM_CONFIG_DIR/hooks/no-secrets.js"
timeout: 5s
onError: fail
preToolCall:
- matcher: ^(bash|write_file|edit_file)$
command: ./hooks/guard.sh
postGeneration:
- command: python3 "$MM_CONFIG_DIR/hooks/log.py"
onError: ignore
Événements
| Événement | S'exécute | Outils concernés | Peut |
|---|---|---|---|
preGeneration |
une fois par question, avant le premier appel au modèle | — | réécrire la question, ajouter du contexte, arrêter le tour |
preToolCall |
avant chaque appel d'outil, avant la demande d'autorisation de l'outil | intégrés et MCP, filtrés par matcher |
refuser l'appel, remplacer ses arguments |
postToolCall |
après chaque appel d'outil exécuté | intégrés et MCP, filtrés par matcher |
ajouter du contexte après la sortie de l'outil |
postGeneration |
une fois le tour terminé : répondu, en échec, annulé ou arrêté par un hook | — | observer seulement |
Les quatre s'exécutent dans tous les front ends : le mode ligne, la fenêtre (-tui), un éditeur (-acp), -p, et un agent démarré par /ask, ask_agent ou mm --ask (avec les hooks de son propre fichier de configuration). Un hook preGeneration ne s'exécute pas sur le résumé d'une compression de contexte.
Exécution
| Aspect | Valeur |
|---|---|
| Commande | sh -c "<command>" |
| Dossier de travail | celui de la session : le dossier où mm s'exécute, ou le cwd de la session de l'éditeur en ACP |
| Environnement | celui de mm, plus les deux variables ci-dessous |
| stdin | un objet JSON (Entrée), puis fin de fichier |
| stdout | vide, ou un objet JSON (Réponse) ; 1 Mio au plus |
| stderr | cité (300 premiers octets) dans la ligne d'échec quand l'exécution échoue ; ignoré sinon |
| Concurrence | les hooks d'un événement s'exécutent l'un après l'autre ; preToolCall/postToolCall peuvent s'exécuter en parallèle pour deux appels d'outil d'une même réponse du modèle |
Variables d'environnement
| Variable | Valeur |
|---|---|
MM_HOOK_EVENT |
l'événement : preGeneration, preToolCall, postToolCall ou postGeneration |
MM_CONFIG_DIR |
dossier absolu du fichier de configuration qui a déclaré le hook |
Entrée
L'objet JSON écrit sur l'entrée standard du hook.
| Champ | Type | Événements | Description |
|---|---|---|---|
event |
chaîne | tous | le nom de l'événement |
cwd |
chaîne | tous | le dossier de travail de la session, absolu |
model |
chaîne | tous | la référence du modèle, <fournisseur>/<modèle> |
prompt |
chaîne | preGeneration, postGeneration |
la question telle que tapée — lignes @path et $skill comprises. Dans une chaîne, la question telle que le hook précédent l'a laissée. Dans postGeneration : la question que le modèle a lue, ou le marqueur [prompt blocked by a hook: <raison>] quand un hook a arrêté le tour. |
tool |
objet | preToolCall, postToolCall |
l'appel d'outil : id (la référence de l'appel, peut être vide), name, input (les arguments, tels que le modèle les a envoyés ou que le hook précédent les a remplacés) |
output |
chaîne | postToolCall |
la sortie de l'outil, telle que le modèle la lira (en JSON quand l'outil renvoie autre chose que du texte) |
answer |
chaîne | postGeneration |
le texte de la dernière réponse ; vide s'il n'y en a pas |
error |
chaîne | postGeneration |
l'erreur du tour ; absent quand le tour a réussi |
Les champs vides sont omis.
Exemples
{"event":"preGeneration","cwd":"/home/moi/projet","model":"llmman/hf.co/unsloth/gemma-4-E4B-it-GGUF:Q4_K_M","prompt":"list the files"}
{"event":"preToolCall","cwd":"/home/moi/projet","model":"llmman/…","tool":{"id":"call_1","name":"bash","input":{"command":"rm -rf build"}}}
Réponse
L'objet JSON qu'un hook peut écrire sur sa sortie standard. Une sortie vide (ou faite seulement d'espaces) vaut {"action":"continue"}. Tous les champs sont facultatifs.
| Champ | Type | Lu sur | Description |
|---|---|---|---|
action |
chaîne | tous | continue (par défaut, y compris quand il est absent), stop, deny ou error — voir plus bas |
prompt |
chaîne | preGeneration |
remplace la question. Absent = inchangée ; "" = une question vide. |
context |
chaîne | preGeneration, postToolCall |
du texte pour le modèle. preGeneration : ajouté après la question. postToolCall : ajouté après la sortie de l'outil. Chacun est introduit par la ligne [context added by a hook]. Blanc = ignoré. |
input |
toute valeur JSON | preToolCall |
remplace les arguments de l'appel d'outil. Absent = inchangés. |
reason |
chaîne | preGeneration (stop), preToolCall (deny) |
la raison. |
response |
chaîne | preGeneration (stop) |
ce que l'utilisateur lit à la place d'une réponse. Absente ou blanche : ⛔ This request was blocked by a hook: <raison>. |
error |
chaîne | tous (error) |
le message d'échec du hook lui-même. |
Les champs lus sur un autre événement sont ignorés.
Actions
| Action | Permise sur | Effet |
|---|---|---|
continue |
tous | le tour continue, avec le prompt, le context et l'input de ce hook appliqués |
stop |
preGeneration |
le tour se termine sans appeler le modèle ; les hooks suivants de l'événement ne s'exécutent pas |
deny |
preToolCall |
l'outil ne s'exécute pas ; les hooks suivants de l'événement ne s'exécutent pas |
error |
tous | un échec, traité selon onError |
toute autre valeur, ou stop/deny sur un autre événement |
— | un échec : action "<action>" is not valid for <événement> |
Effets sur le tour
preGeneration
| Décision | Le modèle lit | L'historique garde | L'utilisateur voit |
|---|---|---|---|
| aucun changement | la question | la question | — |
prompt et/ou context |
la question réécrite, puis chaque contexte | la question réécrite (la copie propre au front end n'est pas modifiée pendant le tour) | — |
stop |
rien : le modèle n'est pas appelé | [prompt blocked by a hook: <raison>] (ou [prompt blocked by a hook]) suivi de la response comme réponse |
la response, comme réponse |
En chaîne : chaque hook lit le prompt laissé par le précédent ; les contextes s'accumulent dans l'ordre des hooks.
preToolCall
| Décision | L'outil | Le modèle lit | L'utilisateur voit |
|---|---|---|---|
| aucun changement | s'exécute avec les arguments du modèle | la sortie | la ligne habituelle de l'outil |
input |
s'exécute avec les arguments remplacés | la sortie | la ligne habituelle de l'outil (avec les arguments remplacés) |
deny |
ne s'exécute pas, et ne demande pas d'autorisation | [denied by a hook: <raison>] comme sortie de l'outil |
mode ligne : 🪝 <outil> [denied by a hook: <raison>] ; fenêtre et éditeur : un appel d'outil en échec intitulé <outil> — denied by a hook |
L'historique garde l'appel d'outil tel que le modèle l'a écrit, même quand input a remplacé ses arguments.
postToolCall
Les context de tous les hooks qui correspondent sont réunis et ajoutés à la sortie : <sortie>\n\n[context added by a hook]\n<contexte>. La sortie de l'outil montrée à l'utilisateur ne change pas.
postGeneration
Aucun effet sur le tour. La réponse est décodée (une réponse invalide est un échec), puis ignorée.
Échecs
Une exécution échoue quand :
| Échec | Message |
|---|---|
| la commande se termine avec un code non nul | exit status <n>: <stderr> |
elle dure plus que timeout |
timed out after <timeout> |
le tour est annulé pendant qu'elle s'exécute (preGeneration, preToolCall, postToolCall) |
cancelled |
| stdout dépasse 1 Mio | stdout larger than 1048576 bytes |
| stdout n'est pas du JSON | invalid JSON on stdout: … |
action vaut error |
l'error de la réponse, ou the hook reported an error |
action est inconnue ou non permise sur l'événement |
action "<action>" is not valid for <événement> |
postGeneration n'est pas annulé avec le tour : il s'exécute aussi après une annulation (Échap, Ctrl-C, /abort), borné par son timeout.
La suite dépend d'onError :
onError |
preGeneration |
preToolCall |
postToolCall, postGeneration |
|---|---|---|---|
fail |
ligne d'échec, tour arrêté (response : ⛔ This request was blocked by a hook: hook failed: …) |
ligne d'échec, appel refusé (reason : hook failed: …) |
ligne d'échec, on continue |
warn |
ligne d'échec, hook suivant | ligne d'échec, hook suivant | ligne d'échec, on continue |
ignore |
hook suivant, sans rien dire | hook suivant, sans rien dire | on continue, sans rien dire |
La ligne d'échec est 🪝 <événement> hook \`: <échec>` : sur stdout en mode ligne, en ligne grisée dans la conversation de la fenêtre, sur stderr (les journaux de l'agent dans l'éditeur) en ACP.
Bannière de démarrage
Quand au moins un hook est déclaré, la bannière se termine par | hooks: <événement>×<nombre> …, une entrée par événement qui a des hooks, dans l'ordre preGeneration, preToolCall, postToolCall, postGeneration :
mm v0.4.0 | config: ../agent.yaml | … | yolo: on | hooks: preGeneration×2 preToolCall×1 postToolCall×1 postGeneration×1
Erreurs de validation
Signalées au démarrage sous la forme [config error: <chemin>: <message>], code de sortie 1.
| Message | Cause |
|---|---|
hooks: unknown event "<nom>" (want one of preGeneration, preToolCall, postToolCall, postGeneration) |
une clé de hooks: qui n'est pas un événement (une faute de frappe : preToolcall) |
hooks.<événement>[N]: command must not be empty |
pas de command, ou une commande blanche |
hooks.<événement>[N]: matcher is only for preToolCall and postToolCall |
un matcher sur preGeneration ou postGeneration |
hooks.<événement>[N]: matcher: error parsing regexp: … |
un matcher qui ne compile pas |
hooks.<événement>[N]: timeout must be >= 0 |
un timeout négatif |
hooks.<événement>[N]: onError must be "fail", "warn" or "ignore" |
toute autre valeur d'onError |
Voir aussi
- Protéger l'agent avec des hooks
- Les hooks — explication
- La démo :
demos/hooks/(quatre hooks Node.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
|