bots-garden/mini-mepublic Fork
⑂ v0.5.0
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. 5b2f986 · on v0.5.0 · k33g · 7d ago
acp.md · 164 lines · 15.7 KBmarkdown
Blame HistoryOpen raw

Référence : surface Agent Client Protocol

Description neutre de ce que mm -acp expose en JSON-RPC 2.0 sur stdio, via github.com/coder/acp-go-sdk v0.13.5.

Transport

Aspect Valeur
Entrée stdin, un message JSON-RPC par ligne.
Sortie stdout, messages JSON-RPC uniquement.
Journaux stderr : bannière, avertissements, lignes [acp] …, logger du SDK, lignes d'échec des hooks (🪝 <événement> hook …) et la notice d'un hook postAsk qui a remplacé une réponse (🪝 postAsk: …).
Durée de vie Jusqu'à ce que le client ferme stdin ou que le contexte du processus soit annulé.

Réponse à initialize

Champ Valeur
protocolVersion 1
agentCapabilities loadSession: true quand sessions.enabled est activé, absente (false) sinon. Tout le reste absent (false) : pas de prompts image ou audio, pas de MCP HTTP.
agentInfo.name acp.name de la configuration, bob par défaut.
agentInfo.title acp.title de la configuration, Bob (bash-first agent) par défaut ; absent quand vide.
agentInfo.version La version du binaire, la même chaîne que mm --version (par exemple v0.1.7).
authMethods []

Méthodes

Méthode Comportement
initialize Lit clientCapabilities.elicitation.form (présent et non nul = formulaires pris en charge), écrit sur stderr le nom du client, la version du protocole et elicitation(form)=true|false ; renvoie les valeurs ci-dessus.
authenticate Renvoie une réponse vide.
session/new Crée une session avec un identifiant triable par date (20260916-101530-4f2a9c1e) et le cwd de la requête, un historique neuf contenant le prompt système et un ensemble « toujours autoriser » vide. Les mcpServers sont comptés dans le journal et ignorés. Une fois la réponse écrite, envoie une notification available_commands_update listant les commandes slash (voir plus bas). Avec sessions.enabled, écrit <cwd>/<sessions.dir>/<id>.json immédiatement et le réécrit après chaque tour, /new et /compact.
session/prompt Exécute une génération sur l'historique de la session, ou exécute une commande slash sans consulter le modèle. Les tours sont sérialisés à l'échelle du processus.
session/cancel Annule le tour en cours pour cette session.
session/load Avec sessions.enabled : lit <cwd>/<sessions.dir>/<sessionId>.json, remplace son prompt système par l'actuel, enregistre la session (ensemble « toujours autoriser » vide), envoie le rejeu (ci-dessous) puis un available_commands_update, et répond {}. Identifiant inconnu ou fichier illisible : invalid params portant le sessionId et l'erreur. Sans sessions.enabled : method not found.
session/set_mode, session/set_config_option, session/list, session/resume, session/close, logout method not found.

Les skills comme commandes

ACP n'a aucune notion de skill. Un prompt porte du text, une image, de l'audio, une resource ou un resource_link — ce dernier étant ce qu'envoie le sélecteur @ d'un éditeur — et rien pour « exécute cette procédure ».

AvailableCommand est le bon créneau, et les exemples du protocole le disent : sa documentation propose create_plan et research_codebase, qui sont des procédures et non des commandes de session. Chaque skill du catalogue y est donc annoncé, et l'éditeur le liste dans son propre menu / :

/compact                                              Compress the history now, keeping the last turns
/new                                                  Clear the history and start a new session
/bg-jobs          [input: parameters, if …]           Start, wait on, inspect and stop a long-running background job…
/greetings        [input: parameters, if …]           Greet somebody with a festive message.
/vulcan-salute    [input: parameters, if …]           Send a vulcan salute to somebody.

C'est l'input.hint qui porte les paramètres : le protocole dit « tout le texte tapé après le nom de la commande est fourni en entrée », ce qui est exactement ce qu'attend un skill.

Aspect Comportement
Ce que fait /greetings Bob Pas intercepté. Réécrit en la notation $greetings Bob, et le tour se déroule normalement : c'est toujours le modèle qui fait l'appel read_skill, donc le détecteur de boucle, la demande de permission et le recap 📖 N skill(s) le voient tous, et mm ne charge jamais un skill dans le dos du modèle.
Une vraie commande gagne session/prompt tente l'interception d'abord, donc un skill nommé compact ou new ne peut jamais en masquer une. Un tel skill n'est pas annoncé non plus, et la bannière de démarrage le signale — sinon il serait silencieusement inatteignable depuis le menu.
Un /nom inconnu Laissé tel quel et passé au modèle, comme n'importe quel texte slash arbitraire avant.
Taper $greetings Bob à la main Marche aussi, dans l'éditeur comme dans le terminal : la notation est résolue dans session/prompt, que le menu l'ait écrite ou l'utilisateur.

À noter, l'invariant a changé avec ça : c'était « n'annoncer que ce que session/prompt intercepte », c'est maintenant « n'annoncer que ce que cette façade traite » — réécrire, c'est traiter.

Rejeu de session/load

Envoyé avant la réponse, une notification par élément de l'historique enregistré, dans l'ordre. Le prompt système n'est pas envoyé.

Élément enregistré Notification
Message utilisateur user_message_chunk avec son texte.
Résumé de compression (message de rôle utilisateur marqué par /compact) agent_message_chunk avec 🗜️ suivi du résumé.
Texte du modèle agent_message_chunk.
Appel d'outil avec sa réponse enregistrée tool_call avec toolCallId = la référence de l'appel, title et kind tels que l'outil en direct les donnerait (bash: <commande> execute, read_file: <chemin> read, write_file/edit_file edit, read_skill: <nom> read), status: completed, rawInput, rawOutput.output et un contenu text portant la sortie.
Appel d'outil sans réponse enregistrée tool_call avec status: failed et sans contenu.

Aucun outil ne s'exécute pendant un rejeu. Journalisé comme [acp] session <id> loaded from <dir>: N message(s) replayed.

session/prompt

Aspect Comportement
sessionId inconnu Erreur invalid params portant le sessionId.
Contenu du prompt Blocs text concaténés ; un resource_link devient \n[attached file: <chemin sans file://>] ; les autres types de blocs sont ignorés. Ensuite, chaque @chemin du texte qui désigne un fichier ou un répertoire existant sous le cwd de la session (ou un chemin absolu / ~) ajoute \n[attached file: <chemin absolu>] ou \n[attached directory: …], une fois par chemin, et est journalisé comme [acp] session <id>: attached <chemin>.
Commande slash Quand le texte aplati, débarrassé de ses espaces, est exactement /new ou /compact, aucune génération n'a lieu ; la commande répond par un agent_message_chunk et stopReason: end_turn.
/new L'historique de la session revient au seul prompt système, le dernier compte de tokens d'entrée du moteur est oublié, le fragment est 🆕 New session: N message(s) forgotten.. L'identifiant de session, le cwd et les autorisations « toujours autoriser » sont conservés. Avec sessions.enabled, la conversation écoulée est d'abord enregistrée sous un nouvel identifiant (journalisé previous conversation kept as <id> ; rien n'est enregistré si elle était vide) et le fichier de la session est réécrit avec l'historique neuf. Journalisé comme [acp] session <id>: new session, N message(s) forgotten.
/compact L'historique est compressé avec les réglages context, en mode forcé (le seuil est ignoré, keepLastTurns est respecté). Le fragment est 🗜️ compressed N messages → 1 summary + N kept (… tokens, …s), ou 🗜️ nothing to compact: N message(s), no turn older than the last K (historique inchangé), ou [compact: failed, history kept: <explication>] (historique inchangé). Le compte de tokens n'est oublié que si l'historique a changé. Attend la fin d'un tour en cours : les tours sont sérialisés. Journalisé comme [acp] session <id>: /compact — <ligne>.
Historique L'historique complet renvoyé par le moteur est conservé, même après un échec ou une annulation ; une question restée sans aucune réponse est retirée.
Succès stopReason: end_turn, plus usage (les totaux de la session : inputTokens, outputTokens, totalTokens, et thoughtTokens / cachedReadTokens quand le serveur les a donnés) dès que le serveur a rapporté un compte ; absent sinon. Aussi pour /new et /compact.
Annulé stopReason: cancelled, avec usage comme ci-dessus.
Échec internal error avec error: <explication en une ligne>.
Ligne de journal [acp] turn done: N command(s) · N file op(s) · N skill(s) sur stderr.

Notifications session/update envoyées

Mise à jour Quand
available_commands_update Une fois par session, juste après l'écriture de la réponse à session/new (l'éditeur doit connaître l'id de session d'abord, sinon il ignore la notification). Liste ce que cette façade traite : les commandes slash que session/prompt intercepte, puis une commande par skill. Envoyée aussi à la fin d'un rejeu session/load.
agent_message_chunk (texte) Chaque fragment diffusé de la réponse du modèle, espaces préservés ; aussi la ligne de confirmation d'une commande slash.
tool_call statut pending Avant l'exécution d'un outil ; porte title, kind, rawInput, et locations avec le chemin absolu quand un fichier est concerné.
tool_call_update statut in_progress Permission accordée, outil en cours d'exécution.
tool_call_update statut completed ou failed Outil terminé ; content est un diff pour les changements de fichier, sinon un bloc text avec la sortie ; rawOutput.output porte toujours la sortie.
tool_call puis tool_call_update statut failed, titre <outil>: <commande ou chemin> — denied by a hook Un hook preToolCall a refusé l'appel : l'outil ne s'est pas exécuté et aucune permission n'a été demandée ; le contenu text est [denied by a hook: <raison>].
usage_update (UNSTABLE dans ACP) Après chaque appel au modèle qui a rapporté un usage (used = ses tokens d'entrée + de sortie), et à la fin de chaque tour, de /new et de /compact (used = le plus grand de la dernière mesure et de l'estimation de l'historique). size est la fenêtre de contexte connue de l'agent (contextWindow ou la sonde), 0 si inconnue. Pas de cost : les serveurs sont locaux.

Commandes disponibles

name description input
new Clear the history and start a new session aucun
compact Compress the history now, keeping the last turns aucun
un par skill la description du skill { "hint": "parameters, if this skill needs any" }

Les clients affichent le nom précédé d'une barre oblique et le renvoient comme texte d'un session/prompt. Les deux commandes ci-dessus y sont interceptées ; la commande d'un skill est réécrite en la notation $skill et le tour se déroule — voir Les skills comme commandes.

Requêtes session/request_permission envoyées

Envoyées seulement quand yolo: false (par défaut true : aucune n'est envoyée pour les outils intégrés), avant l'exécution de bash, write_file, edit_file, fetch_content avec output, rename de lsp et des actions de plan qui modifient un plan ; jamais pour read_skill, read_file, fetch_content sans output ni les actions de navigation de lsp. Les outils MCP en envoient une quand yolo: false ou quand leur permissions vaut ask.

Identifiant d'option Genre Effet
allow allow_once Exécute l'outil.
allow_always allow_always Exécute l'outil et supprime le dialogue pour ce nom d'outil jusqu'à la fin de la session.
reject reject_once N'exécute pas ; le modèle reçoit [command rejected by the user] (ou [<outil> rejected by the user]).
never reject_always N'exécute pas, et chaque appel ultérieur de ce nom d'outil dans la session est refusé sans dialogue.

Les réponses mémorisées (allow_always, never) survivent à /new.

Une erreur de transport ou un résultat cancelled compte comme un refus.

Requêtes elicitation/create envoyées

Envoyées par l'outil ask_user (voir outils) quand le client a annoncé clientCapabilities.elicitation.form à initialize. Une requête par appel d'outil, en mode formulaire.

Paramètre Valeur
sessionId La session.
toolCallId L'appel d'outil ask_user, annoncé auparavant par une notification tool_call.
mode form
message La question quand il n'y en a qu'une, sinon The agent has N questions for you.
requestedSchema {"type": "object", "properties": {…}, "required": [tous les noms]}, une propriété par question, nommée q1, q2… : choice → {"type": "string", "title": <question>, "enum": [options]} ; multi → {"type": "array", "title": <question>, "items": {"type": "string", "enum": [options]}} ; text → {"type": "string", "title": <question>} ; confirm → {"type": "boolean", "title": <question>} ; plus default quand la question en a un (un booléen pour confirm).
_meta["mm.elicitationScope"] {"sessionId", "toolCallId"} : une copie du rattachement (acp-go-sdk v0.13.5 ne sait pas porter les champs de premier niveau ; mm les écrit à partir de cette copie, et un client mm qui relaie la question y lit l'appel d'outil).
Réponse Résultat pour le modèle
{"action": "accept", "content": {…}} Les réponses, lues dans content par nom de propriété (une chaîne, une liste de chaînes ou un booléen).
{"action": "decline"} Refusé.
{"action": "cancel"}, ou le tour annulé Annulé.
Une réponse d'erreur Personne ne peut être interrogé.

Sans prise en charge des formulaires, un formulaire dont toutes les questions sont des choice ou des confirm part sous forme d'un session/request_permission par question : toolCall.title ? <question>, toolCall.kind other, options 0, 1… portant le nom des choix (genre allow_once ; Yes/No pour un confirm) et decline nommée Decline (genre reject_once). Un résultat annulé annule le formulaire ; decline le refuse.

Quand mm est le client — un agent qui en interroge un autre par /ask, ask_agent ou mm --ask —, sa requête initialize annonce clientCapabilities.elicitation.form, et les requêtes elicitation/create de l'agent interrogé sont posées à l'utilisateur de l'agent qui interroge, par sa propre interface. Une interface qui ne peut pas interroger (mm --ask) répond par une erreur.

Genres d'outils

Genre mm ToolKind ACP Outils
execute execute bash (et tout genre inconnu)
read read read_skill, read_file
edit edit write_file, edit_file
fetch fetch fetch_content
other other ask_agent, ask_user

Identifiants d'appel d'outil

call_1, call_2, … numérotés par processus et jamais remis à zéro.

  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
# Référence : surface Agent Client Protocol

> Description neutre de ce que `mm -acp` expose en JSON-RPC 2.0 sur stdio, via `github.com/coder/acp-go-sdk` v0.13.5.

## Transport

| Aspect | Valeur |
|--------|--------|
| Entrée | stdin, un message JSON-RPC par ligne. |
| Sortie | stdout, messages JSON-RPC uniquement. |
| Journaux | stderr : bannière, avertissements, lignes `[acp] …`, logger du SDK, lignes d'échec des [hooks](hooks.md#échecs) (`🪝 <événement> hook …`) et la notice d'un hook `postAsk` qui a remplacé une réponse (`🪝 postAsk: …`). |
| Durée de vie | Jusqu'à ce que le client ferme stdin ou que le contexte du processus soit annulé. |

## Réponse à `initialize`

| Champ | Valeur |
|-------|--------|
| `protocolVersion` | `1` |
| `agentCapabilities` | `loadSession: true` quand `sessions.enabled` est activé, absente (false) sinon. Tout le reste absent (false) : pas de prompts image ou audio, pas de MCP HTTP. |
| `agentInfo.name` | `acp.name` de la configuration, `bob` par défaut. |
| `agentInfo.title` | `acp.title` de la configuration, `Bob (bash-first agent)` par défaut ; absent quand vide. |
| `agentInfo.version` | La version du binaire, la même chaîne que `mm --version` (par exemple `v0.1.7`). |
| `authMethods` | `[]` |

## Méthodes

| Méthode | Comportement |
|---------|--------------|
| `initialize` | Lit `clientCapabilities.elicitation.form` (présent et non nul = formulaires pris en charge), écrit sur stderr le nom du client, la version du protocole et `elicitation(form)=true\|false` ; renvoie les valeurs ci-dessus. |
| `authenticate` | Renvoie une réponse vide. |
| `session/new` | Crée une session avec un identifiant triable par date (`20260916-101530-4f2a9c1e`) et le `cwd` de la requête, un historique neuf contenant le prompt système et un ensemble « toujours autoriser » vide. Les `mcpServers` sont comptés dans le journal et ignorés. Une fois la réponse écrite, envoie une notification `available_commands_update` listant les commandes slash (voir plus bas). Avec `sessions.enabled`, écrit `<cwd>/<sessions.dir>/<id>.json` immédiatement et le réécrit après chaque tour, `/new` et `/compact`. |
| `session/prompt` | Exécute une génération sur l'historique de la session, ou exécute une commande slash sans consulter le modèle. Les tours sont sérialisés à l'échelle du processus. |
| `session/cancel` | Annule le tour en cours pour cette session. |
| `session/load` | Avec `sessions.enabled` : lit `<cwd>/<sessions.dir>/<sessionId>.json`, remplace son prompt système par l'actuel, enregistre la session (ensemble « toujours autoriser » vide), envoie le rejeu (ci-dessous) puis un `available_commands_update`, et répond `{}`. Identifiant inconnu ou fichier illisible : `invalid params` portant le `sessionId` et l'erreur. Sans `sessions.enabled` : `method not found`. |
| `session/set_mode`, `session/set_config_option`, `session/list`, `session/resume`, `session/close`, `logout` | `method not found`. |

## Les skills comme commandes

ACP n'a aucune notion de skill. Un prompt porte du `text`, une `image`, de l'`audio`, une `resource` ou un `resource_link` — ce dernier étant ce qu'envoie le sélecteur `@` d'un éditeur — et rien pour « exécute cette procédure ».

`AvailableCommand` est le bon créneau, et les exemples du protocole le disent : sa documentation propose `create_plan` et `research_codebase`, qui sont des procédures et non des commandes de session. Chaque skill du catalogue y est donc annoncé, et l'éditeur le liste dans son propre menu `/` :

```
/compact                                              Compress the history now, keeping the last turns
/new                                                  Clear the history and start a new session
/bg-jobs          [input: parameters, if …]           Start, wait on, inspect and stop a long-running background job…
/greetings        [input: parameters, if …]           Greet somebody with a festive message.
/vulcan-salute    [input: parameters, if …]           Send a vulcan salute to somebody.
```

C'est l'`input.hint` qui porte les paramètres : le protocole dit « tout le texte tapé après le nom de la commande est fourni en entrée », ce qui est exactement ce qu'attend un skill.

| Aspect | Comportement |
|---|---|
| Ce que fait `/greetings Bob` | **Pas intercepté.** Réécrit en la notation `$greetings Bob`, et le tour se déroule normalement : c'est toujours le modèle qui fait l'appel `read_skill`, donc le détecteur de boucle, la demande de permission et le recap `📖 N skill(s)` le voient tous, et `mm` ne charge jamais un skill dans le dos du modèle. |
| Une vraie commande gagne | `session/prompt` tente l'interception d'abord, donc un skill nommé `compact` ou `new` ne peut jamais en masquer une. Un tel skill n'est **pas annoncé** non plus, et la bannière de démarrage le signale — sinon il serait silencieusement inatteignable depuis le menu. |
| Un `/nom` inconnu | Laissé tel quel et passé au modèle, comme n'importe quel texte slash arbitraire avant. |
| Taper `$greetings Bob` à la main | Marche aussi, dans l'éditeur comme dans le terminal : la notation est résolue dans `session/prompt`, que le menu l'ait écrite ou l'utilisateur. |

À noter, l'invariant a changé avec ça : c'était « n'annoncer que ce que `session/prompt` intercepte », c'est maintenant « n'annoncer que ce que cette façade **traite** » — réécrire, c'est traiter.

## Rejeu de `session/load`

Envoyé avant la réponse, une notification par élément de l'historique enregistré, dans l'ordre. Le prompt système n'est pas envoyé.

| Élément enregistré | Notification |
|--------------------|--------------|
| Message utilisateur | `user_message_chunk` avec son texte. |
| Résumé de compression (message de rôle utilisateur marqué par `/compact`) | `agent_message_chunk` avec `🗜️ ` suivi du résumé. |
| Texte du modèle | `agent_message_chunk`. |
| Appel d'outil avec sa réponse enregistrée | `tool_call` avec `toolCallId` = la référence de l'appel, `title` et `kind` tels que l'outil en direct les donnerait (`bash: <commande>` execute, `read_file: <chemin>` read, `write_file`/`edit_file` edit, `read_skill: <nom>` read), `status: completed`, `rawInput`, `rawOutput.output` et un contenu `text` portant la sortie. |
| Appel d'outil sans réponse enregistrée | `tool_call` avec `status: failed` et sans contenu. |

Aucun outil ne s'exécute pendant un rejeu. Journalisé comme `[acp] session <id> loaded from <dir>: N message(s) replayed`.

## `session/prompt`

| Aspect | Comportement |
|--------|--------------|
| `sessionId` inconnu | Erreur `invalid params` portant le `sessionId`. |
| Contenu du prompt | Blocs `text` concaténés ; un `resource_link` devient `\n[attached file: <chemin sans file://>]` ; les autres types de blocs sont ignorés. Ensuite, chaque `@chemin` du texte qui désigne un fichier ou un répertoire existant sous le `cwd` de la session (ou un chemin absolu / `~`) ajoute `\n[attached file: <chemin absolu>]` ou `\n[attached directory: …]`, une fois par chemin, et est journalisé comme `[acp] session <id>: attached <chemin>`. |
| Commande slash | Quand le texte aplati, débarrassé de ses espaces, est exactement `/new` ou `/compact`, aucune génération n'a lieu ; la commande répond par un `agent_message_chunk` et `stopReason: end_turn`. |
| `/new` | L'historique de la session revient au seul prompt système, le dernier compte de tokens d'entrée du moteur est oublié, le fragment est `🆕 New session: N message(s) forgotten.`. L'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. Avec `sessions.enabled`, la conversation écoulée est d'abord enregistrée sous un nouvel identifiant (journalisé `previous conversation kept as <id>` ; rien n'est enregistré si elle était vide) et le fichier de la session est réécrit avec l'historique neuf. Journalisé comme `[acp] session <id>: new session, N message(s) forgotten`. |
| `/compact` | L'historique est compressé avec les réglages `context`, en mode forcé (le seuil est ignoré, `keepLastTurns` est respecté). Le fragment est `🗜️ compressed N messages → 1 summary + N kept (… tokens, …s)`, ou `🗜️ nothing to compact: N message(s), no turn older than the last K` (historique inchangé), ou `[compact: failed, history kept: <explication>]` (historique inchangé). Le compte de tokens n'est oublié que si l'historique a changé. Attend la fin d'un tour en cours : les tours sont sérialisés. Journalisé comme `[acp] session <id>: /compact — <ligne>`. |
| Historique | L'historique complet renvoyé par le moteur est conservé, même après un échec ou une annulation ; une question restée sans aucune réponse est retirée. |
| Succès | `stopReason: end_turn`, plus `usage` (les **totaux** de la session : `inputTokens`, `outputTokens`, `totalTokens`, et `thoughtTokens` / `cachedReadTokens` quand le serveur les a donnés) dès que le serveur a rapporté un compte ; absent sinon. Aussi pour `/new` et `/compact`. |
| Annulé | `stopReason: cancelled`, avec `usage` comme ci-dessus. |
| Échec | `internal error` avec `error: <explication en une ligne>`. |
| Ligne de journal | `[acp] turn done: N command(s) · N file op(s) · N skill(s)` sur stderr. |

## Notifications `session/update` envoyées

| Mise à jour | Quand |
|-------------|-------|
| `available_commands_update` | Une fois par session, juste après l'écriture de la réponse à `session/new` (l'éditeur doit connaître l'id de session d'abord, sinon il ignore la notification). Liste ce que cette façade **traite** : les commandes slash que `session/prompt` intercepte, puis une commande par **skill**. Envoyée aussi à la fin d'un rejeu `session/load`. |
| `agent_message_chunk` (texte) | Chaque fragment diffusé de la réponse du modèle, espaces préservés ; aussi la ligne de confirmation d'une commande slash. |
| `tool_call` statut `pending` | Avant l'exécution d'un outil ; porte `title`, `kind`, `rawInput`, et `locations` avec le chemin absolu quand un fichier est concerné. |
| `tool_call_update` statut `in_progress` | Permission accordée, outil en cours d'exécution. |
| `tool_call_update` statut `completed` ou `failed` | Outil terminé ; `content` est un `diff` pour les changements de fichier, sinon un bloc `text` avec la sortie ; `rawOutput.output` porte toujours la sortie. |
| `tool_call` puis `tool_call_update` statut `failed`, titre `<outil>: <commande ou chemin> — denied by a hook` | Un [hook](hooks.md) `preToolCall` a refusé l'appel : l'outil ne s'est pas exécuté et aucune permission n'a été demandée ; le contenu `text` est `[denied by a hook: <raison>]`. |
| `usage_update` (UNSTABLE dans ACP) | Après chaque appel au modèle qui a rapporté un usage (`used` = ses tokens d'entrée + de sortie), et à la fin de chaque tour, de `/new` et de `/compact` (`used` = le plus grand de la dernière mesure et de l'estimation de l'historique). `size` est la fenêtre de contexte connue de l'agent (`contextWindow` ou la sonde), `0` si inconnue. Pas de `cost` : les serveurs sont locaux. |

## Commandes disponibles

| `name` | `description` | `input` |
|--------|---------------|---------|
| `new` | `Clear the history and start a new session` | aucun |
| `compact` | `Compress the history now, keeping the last turns` | aucun |
| un par skill | la `description` du skill | `{ "hint": "parameters, if this skill needs any" }` |

Les clients affichent le nom précédé d'une barre oblique et le renvoient comme texte d'un `session/prompt`. Les deux commandes ci-dessus y sont interceptées ; la commande d'un skill est réécrite en la notation `$skill` et le tour se déroule — voir [Les skills comme commandes](#les-skills-comme-commandes).

## Requêtes `session/request_permission` envoyées

Envoyées seulement quand `yolo: false` (par défaut `true` : aucune n'est envoyée pour les outils intégrés), avant l'exécution de `bash`, `write_file`, `edit_file`, `fetch_content` avec `output`, `rename` de `lsp` et des actions de `plan` qui modifient un plan ; jamais pour `read_skill`, `read_file`, `fetch_content` sans `output` ni les actions de navigation de `lsp`. Les outils MCP en envoient une quand `yolo: false` ou quand leur `permissions` vaut `ask`.

| Identifiant d'option | Genre | Effet |
|----------------------|-------|-------|
| `allow` | `allow_once` | Exécute l'outil. |
| `allow_always` | `allow_always` | Exécute l'outil et supprime le dialogue pour ce nom d'outil jusqu'à la fin de la session. |
| `reject` | `reject_once` | N'exécute pas ; le modèle reçoit `[command rejected by the user]` (ou `[<outil> rejected by the user]`). |
| `never` | `reject_always` | N'exécute pas, et chaque appel ultérieur de ce nom d'outil dans la session est refusé sans dialogue. |

Les réponses mémorisées (`allow_always`, `never`) survivent à `/new`.

Une erreur de transport ou un résultat `cancelled` compte comme un refus.

## Requêtes `elicitation/create` envoyées

Envoyées par l'outil `ask_user` (voir [outils](tools.md#ask_user)) quand le client a annoncé `clientCapabilities.elicitation.form` à `initialize`. Une requête par appel d'outil, en mode formulaire.

| Paramètre | Valeur |
|-----------|--------|
| `sessionId` | La session. |
| `toolCallId` | L'appel d'outil `ask_user`, annoncé auparavant par une notification `tool_call`. |
| `mode` | `form` |
| `message` | La question quand il n'y en a qu'une, sinon `The agent has N questions for you.` |
| `requestedSchema` | `{"type": "object", "properties": {…}, "required": [tous les noms]}`, une propriété par question, nommée `q1`, `q2`… : `choice` → `{"type": "string", "title": <question>, "enum": [options]}` ; `multi` → `{"type": "array", "title": <question>, "items": {"type": "string", "enum": [options]}}` ; `text` → `{"type": "string", "title": <question>}` ; `confirm` → `{"type": "boolean", "title": <question>}` ; plus `default` quand la question en a un (un booléen pour `confirm`). |
| `_meta["mm.elicitationScope"]` | `{"sessionId", "toolCallId"}` : une copie du rattachement (acp-go-sdk v0.13.5 ne sait pas porter les champs de premier niveau ; mm les écrit à partir de cette copie, et un client mm qui relaie la question y lit l'appel d'outil). |

| Réponse | Résultat pour le modèle |
|---------|-------------------------|
| `{"action": "accept", "content": {…}}` | Les réponses, lues dans `content` par nom de propriété (une chaîne, une liste de chaînes ou un booléen). |
| `{"action": "decline"}` | Refusé. |
| `{"action": "cancel"}`, ou le tour annulé | Annulé. |
| Une réponse d'erreur | Personne ne peut être interrogé. |

Sans prise en charge des formulaires, un formulaire dont toutes les questions sont des `choice` ou des `confirm` part sous forme d'un `session/request_permission` par question : `toolCall.title` `? <question>`, `toolCall.kind` `other`, options `0`, `1`… portant le nom des choix (genre `allow_once` ; `Yes`/`No` pour un `confirm`) et `decline` nommée `Decline` (genre `reject_once`). Un résultat annulé annule le formulaire ; `decline` le refuse.

Quand mm est le **client** — un agent qui en interroge un autre par `/ask`, `ask_agent` ou `mm --ask` —, sa requête `initialize` annonce `clientCapabilities.elicitation.form`, et les requêtes `elicitation/create` de l'agent interrogé sont posées à l'utilisateur de l'agent qui interroge, par sa propre interface. Une interface qui ne peut pas interroger (`mm --ask`) répond par une erreur.

## Genres d'outils

| Genre `mm` | `ToolKind` ACP | Outils |
|------------|----------------|--------|
| `execute` | `execute` | `bash` (et tout genre inconnu) |
| `read` | `read` | `read_skill`, `read_file` |
| `edit` | `edit` | `write_file`, `edit_file` |
| `fetch` | `fetch` | `fetch_content` |
| `other` | `other` | `ask_agent`, `ask_user` |

## Identifiants d'appel d'outil

`call_1`, `call_2`, … numérotés par processus et jamais remis à zéro.