bots-garden/mini-mepublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. fd5d2a7 · on main · k33g · yesterday
guard-the-agent-with-hooks.md · 102 lines · 5.5 KBmarkdown
Blame HistoryOpen raw

Protéger l'agent avec des hooks

Ce guide montre comment lancer votre propre commande à des moments précis d'un tour : pour refuser un appel d'outil dangereux, arrêter une question avant que le modèle la voie, ajouter du contexte, ou journaliser ce que fait l'agent. Il suppose que vous avez un agent qui fonctionne, et un langage dans lequel écrire un petit script. Les exemples sont en Node.js ; tout programme qui lit stdin et écrit sur stdout convient.

Étapes

1. Choisir l'événement

Vous voulez… Événement
refuser certains appels d'outil, ou changer leurs arguments preToolCall
arrêter ou réécrire une question, ou lui ajouter du contexte preGeneration
ajouter une note après la sortie d'un outil postToolCall
journaliser ou notifier, sans rien changer postToolCall, postGeneration

2. Écrire le hook

Le hook lit un objet JSON sur stdin et, quand il a une décision, en écrit un sur stdout. Ne rien écrire laisse le tour continuer. Celui-ci refuse git push --force :

// hooks/no-force-push.js
const input = JSON.parse(require("fs").readFileSync(0, "utf8"));
const command = input.tool.input.command || "";

if (/\bgit\s+push\b.*(--force|-f\b)/.test(command)) {
  console.log(JSON.stringify({ action: "deny", reason: "force-push is not allowed in this project" }));
}

La raison est ce que le modèle lit à la place de la sortie de la commande : écrivez-la pour le modèle, afin qu'il puisse prévenir l'utilisateur ou essayer autre chose.

3. L'essayer sans mm

Donnez-lui l'entrée que mm enverrait :

echo '{"event":"preToolCall","cwd":"/tmp","tool":{"name":"bash","input":{"command":"git push -f origin main"}}}' \
  | node hooks/no-force-push.js

Vous devez voir :

{"action":"deny","reason":"force-push is not allowed in this project"}

Et rien du tout pour git push origin main.

4. Le déclarer

Dans le fichier YAML de l'agent, à côté du script :

hooks:
  preToolCall:
    - matcher: ^bash$
      command: node "$MM_CONFIG_DIR/hooks/no-force-push.js"
      onError: fail
  • matcher limite le hook à l'outil bash ; sans lui, le hook s'exécute pour chaque appel d'outil.
  • $MM_CONFIG_DIR est le dossier du fichier YAML : le script est trouvé quel que soit le dossier d'où mm est lancé.
  • onError: fail refuse l'appel quand le hook lui-même échoue (Node absent, erreur de syntaxe, délai dépassé). Utilisez-le pour les gardes ; gardez la valeur par défaut, warn, pour les hooks dont l'échec ne doit pas gêner.

5. Lancer l'agent et vérifier

mm agent.yaml

La bannière se termine par les hooks chargés :

… | yolo: on | hooks: preToolCall×1

Demandez quelque chose qui déclenche la règle (force-push the main branch). Le mode ligne affiche :

🪝 bash [denied by a hook: force-push is not allowed in this project]

et la réponse du modèle explique le refus.

Variantes

  • Arrêter une question. Sur preGeneration, lisez input.prompt et écrivez {"action":"stop","reason":"…","response":"…"}. L'utilisateur lit response ; le modèle n'est pas appelé, et la question est remplacée dans l'historique par [prompt blocked by a hook: <raison>].
  • Ajouter du contexte. Sur preGeneration, écrivez {"context":"…"} : le texte est ajouté après la question, sous [context added by a hook]. Sur postToolCall, le même champ est ajouté après la sortie de l'outil.
  • Réécrire. {"prompt":"…"} sur preGeneration remplace la question ; {"input":{…}} sur preToolCall remplace les arguments de l'outil — l'objet entier, donc recopiez les champs que vous gardez.
  • Journaliser. Sur postToolCall et postGeneration, ajoutez input à un fichier et n'écrivez rien ; mettez onError: ignore pour qu'un disque plein n'interrompe jamais une session. Un même script peut servir plusieurs événements : lisez input.event ou $MM_HOOK_EVENT.
  • Un autre langage. command est exécutée par sh -c : python3 "$MM_CONFIG_DIR/hooks/guard.py", "$MM_CONFIG_DIR/hooks/guard.sh" (exécutable), ou une commande jq en ligne fonctionnent tous.
  • Un hook sans vos droits, rien à installer. Écrivez-le comme module WebAssembly et déclarez-le avec wasm: au lieu de command: : voir Écrire un hook sous forme de module WebAssembly.
  • Plusieurs hooks sur un événement s'exécutent dans l'ordre écrit ; chacun voit la réécriture du précédent, et le premier stop ou deny arrête la chaîne.
  • Avec yolo: false, un hook preToolCall s'exécute avant la demande d'autorisation : un appel refusé n'est jamais soumis à l'utilisateur.
  • Voir quels hooks ont tourné. Mettez displayHooks: true : après chaque réponse, une ligne par exécution de hook indique son événement, sa commande et ce qu'il a décidé — context (178 chars), deny, failed… Dans un éditeur, chaque exécution est un appel d'outil. Voir hooks › Affichage.
  • Dans un éditeur (mm -acp), les hooks s'exécutent dans le dossier de travail de la session, et un appel refusé apparaît comme un appel d'outil en échec intitulé <outil>: <commande> — denied by a hook.

Voir aussi

  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
# Protéger l'agent avec des hooks

Ce guide montre comment lancer votre propre commande à des moments précis d'un tour : pour refuser un appel d'outil dangereux, arrêter une question avant que le modèle la voie, ajouter du contexte, ou journaliser ce que fait l'agent. Il suppose que vous avez un agent qui fonctionne, et un langage dans lequel écrire un petit script. Les exemples sont en Node.js ; tout programme qui lit stdin et écrit sur stdout convient.

## Étapes

### 1. Choisir l'événement

| Vous voulez… | Événement |
|--------------|-----------|
| refuser certains appels d'outil, ou changer leurs arguments | `preToolCall` |
| arrêter ou réécrire une question, ou lui ajouter du contexte | `preGeneration` |
| ajouter une note après la sortie d'un outil | `postToolCall` |
| journaliser ou notifier, sans rien changer | `postToolCall`, `postGeneration` |

### 2. Écrire le hook

Le hook lit un objet JSON sur stdin et, quand il a une décision, en écrit un sur stdout. Ne rien écrire laisse le tour continuer. Celui-ci refuse `git push --force` :

```js
// hooks/no-force-push.js
const input = JSON.parse(require("fs").readFileSync(0, "utf8"));
const command = input.tool.input.command || "";

if (/\bgit\s+push\b.*(--force|-f\b)/.test(command)) {
  console.log(JSON.stringify({ action: "deny", reason: "force-push is not allowed in this project" }));
}
```

La raison est ce que le modèle lit à la place de la sortie de la commande : écrivez-la pour le modèle, afin qu'il puisse prévenir l'utilisateur ou essayer autre chose.

### 3. L'essayer sans mm

Donnez-lui l'entrée que mm enverrait :

```bash
echo '{"event":"preToolCall","cwd":"/tmp","tool":{"name":"bash","input":{"command":"git push -f origin main"}}}' \
  | node hooks/no-force-push.js
```

Vous devez voir :

```
{"action":"deny","reason":"force-push is not allowed in this project"}
```

Et rien du tout pour `git push origin main`.

### 4. Le déclarer

Dans le fichier YAML de l'agent, à côté du script :

```yaml
hooks:
  preToolCall:
    - matcher: ^bash$
      command: node "$MM_CONFIG_DIR/hooks/no-force-push.js"
      onError: fail
```

- `matcher` limite le hook à l'outil `bash` ; sans lui, le hook s'exécute pour chaque appel d'outil.
- `$MM_CONFIG_DIR` est le dossier du fichier YAML : le script est trouvé quel que soit le dossier d'où `mm` est lancé.
- `onError: fail` refuse l'appel quand le hook lui-même échoue (Node absent, erreur de syntaxe, délai dépassé). Utilisez-le pour les gardes ; gardez la valeur par défaut, `warn`, pour les hooks dont l'échec ne doit pas gêner.

### 5. Lancer l'agent et vérifier

```bash
mm agent.yaml
```

La bannière se termine par les hooks chargés :

```
… | yolo: on | hooks: preToolCall×1
```

Demandez quelque chose qui déclenche la règle (`force-push the main branch`). Le mode ligne affiche :

```
🪝 bash [denied by a hook: force-push is not allowed in this project]
```

et la réponse du modèle explique le refus.

## Variantes

- **Arrêter une question.** Sur `preGeneration`, lisez `input.prompt` et écrivez `{"action":"stop","reason":"…","response":"…"}`. L'utilisateur lit `response` ; le modèle n'est pas appelé, et la question est remplacée dans l'historique par `[prompt blocked by a hook: <raison>]`.
- **Ajouter du contexte.** Sur `preGeneration`, écrivez `{"context":"…"}` : le texte est ajouté après la question, sous `[context added by a hook]`. Sur `postToolCall`, le même champ est ajouté après la sortie de l'outil.
- **Réécrire.** `{"prompt":"…"}` sur `preGeneration` remplace la question ; `{"input":{…}}` sur `preToolCall` remplace les arguments de l'outil — l'objet entier, donc recopiez les champs que vous gardez.
- **Journaliser.** Sur `postToolCall` et `postGeneration`, ajoutez `input` à un fichier et n'écrivez rien ; mettez `onError: ignore` pour qu'un disque plein n'interrompe jamais une session. Un même script peut servir plusieurs événements : lisez `input.event` ou `$MM_HOOK_EVENT`.
- **Un autre langage.** `command` est exécutée par `sh -c` : `python3 "$MM_CONFIG_DIR/hooks/guard.py"`, `"$MM_CONFIG_DIR/hooks/guard.sh"` (exécutable), ou une commande `jq` en ligne fonctionnent tous.
- **Un hook sans vos droits, rien à installer.** Écrivez-le comme module WebAssembly et déclarez-le avec `wasm:` au lieu de `command:` : voir [Écrire un hook sous forme de module WebAssembly](write-a-wasm-hook.md).
- **Plusieurs hooks sur un événement** s'exécutent dans l'ordre écrit ; chacun voit la réécriture du précédent, et le premier `stop` ou `deny` arrête la chaîne.
- **Avec `yolo: false`**, un hook `preToolCall` s'exécute avant la demande d'autorisation : un appel refusé n'est jamais soumis à l'utilisateur.
- **Voir quels hooks ont tourné.** Mettez `displayHooks: true` : après chaque réponse, une ligne par exécution de hook indique son événement, sa commande et ce qu'il a décidé — `context (178 chars)`, `deny`, `failed`… Dans un éditeur, chaque exécution est un appel d'outil. Voir [hooks › Affichage](../reference/hooks.md#affichage).
- **Dans un éditeur** (`mm -acp`), les hooks s'exécutent dans le dossier de travail de la session, et un appel refusé apparaît comme un appel d'outil en échec intitulé `<outil>: <commande> — denied by a hook`.

## Voir aussi

- Chaque champ, action, échec et limite : [Référence des hooks](../reference/hooks.md)
- Pourquoi les hooks sont des commandes externes, et où ils se branchent : [Les hooks — explication](../explanation/hooks.md)
- Un exemple complet avec quatre hooks : `demos/hooks/`