mini-me depuis Go : le paquet minime
Un client Go qui utilise mini-me comme bibliothèque en pilotant mm -acp par ACP (ticket 168). C'est le jumeau Go du client Node.js. C'est un module à part (minime), construit sur le SDK ACP que mm utilise lui-même, github.com/coder/acp-go-sdk v0.13.5, qui n'a lui-même aucune dépendance. Voir ../README.fr.md pour les messages ACP qui se cachent derrière.
agent, err := minime.Start(ctx, minime.Options{Config: "agent.yaml"})
if err != nil {
log.Fatal(err)
}
defer agent.Close()
res, err := agent.Complete(ctx, "Explain this repository") // sans stream
for ev, err := range agent.Stream(ctx, "And the tests?") { // en stream
if err != nil {
log.Fatal(err)
}
if ev.Kind == minime.KindText {
fmt.Print(ev.Text)
}
}
ctx, cancel := context.WithTimeout(ctx, 3*time.Second) // annulé au bout de 3 s
defer cancel()
res, err = agent.Complete(ctx, "A long task") // res.StopReason == "cancelled"
Lancer les exemples
llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
llmman serve
cd demo.acp.sdk/go
go run ./examples/complete # sans stream
go run ./examples/stream # en stream, avec les appels d'outils
go run ./examples/cancel # annulé au bout de 3 secondes
go run ./examples/permission # une commande refusée, puis autorisée
mm doit être dans le PATH, ou désigné par MM_BIN. Les exemples utilisent agent.yaml (Qwen3.5 4B sur llmman, bash seulement, yolo: false) ; tout fichier d'agent fonctionne. Go 1.24 ou plus récent (range-over-func).
Pour utiliser le paquet depuis un autre module, tant qu'il n'est pas publié :
go mod edit -require=minime@v0.0.0 -replace=minime=../chemin/vers/demo.acp.sdk/go
go mod tidy
L'API
minime.Start(ctx, minime.Options{...}) (*Agent, error) |
lance mm -acp -config <Config> (rendu absolu) dans Cwd, puis initialize et session/new {cwd}, en 60 s maximum. |
Options.Config |
le fichier d'agent. Obligatoire. |
Options.Cwd |
le répertoire de travail de la session, là où les outils s'exécutent. Par défaut : le répertoire courant. |
Options.MM |
le binaire mm. Par défaut : $MM_BIN, puis mm dans le PATH. |
Options.OnPermission func(ctx, ToolCall) Decision |
appelé quand l'agent veut lancer un outil qui demande une confirmation (yolo: false). Sans lui, chacun de ces appels est refusé. |
Options.OnStderr func(line string) |
le stderr de mm, ligne par ligne (bannière, avertissements, lignes [acp]). Sinon il est lu puis jeté ; sa dernière ligne est ajoutée aux erreurs. |
agent.Complete(ctx, prompt) (Result, error) |
un tour ; renvoie Result{Text, StopReason, Usage} une fois terminé. |
agent.Stream(ctx, prompt) iter.Seq2[Event, error] |
un tour sous forme de séquence range-over-func ; le dernier événement est KindDone. Sortir de la boucle avant la fin annule le tour. |
annuler ctx |
envoie session/cancel ; le tour se termine avec StopReason: "cancelled", le texte déjà reçu et une erreur nil. Si mm ne répond pas sous 30 s, le tour se termine par une erreur. |
agent.Reset(ctx) error |
une nouvelle session sur le même processus : la conversation repart de zéro. |
agent.Close() error |
ferme le stdin de mm et attend qu'il se termine (le tue au bout de 5 s). L'appeler deux fois est sans effet. |
agent.SessionID() string |
l'identifiant de session ACP. |
minime.ErrBusy |
renvoyée par un tour ou un Reset pendant qu'un autre tour tourne sur le même Agent. |
Decision, la réponse de OnPermission :
minime.Allow |
exécuter l'outil cette fois (allow_once) |
minime.Reject, ou la valeur zéro Decision{} |
le refuser cette fois (reject_once) |
minime.ChooseOption(id) |
une des options proposées par mm, par son id : "allow", "allow_always" (cet outil, pour la session), "reject", "never" (cet outil, pour la session). Un id que mm n'a pas proposé vaut Reject. |
Un callback qui panique vaut Reject. Son ctx est celui du tour.
Les événements (Event.Kind) :
| Kind | Champs remplis |
|---|---|
KindText |
Text : un morceau de la réponse |
KindThinking |
Text : un morceau de la réflexion d'un modèle qui « réfléchit » |
KindToolCall |
Call : ID, Title (par ex. bash: ls), Kind (execute, read, edit…), Input (les arguments) |
KindToolResult |
Call.ID, Status (completed ou failed), Output |
KindDone |
StopReason, Usage (nil quand mm n'en a pas envoyé), Text : toute la réponse |
Les règles à connaître :
- Un Agent = un processus mm = une conversation. Chaque appel la continue, comme dans un éditeur. Un seul tour à la fois par Agent (
ErrBusy) ; lancez plusieurs Agents pour travailler en parallèle. - Un tour que mm termine par une erreur la renvoie, avec les mots de mm et la dernière ligne de son stderr, par exemple
minime: turn failed: Internal error (-32603): the model repeated the same bash call 6 times…. Si mm a planté, l'erreur ditmm exited: exit status N. - mm retire de la conversation une question annulée : la question suivante n'en sait rien.
- Le client n'annonce aucune capacité fichiers, terminal ou formulaire : mm n'en a pas besoin, et son outil
ask_usern'est pas proposé.
Tests
cd demo.acp.sdk/go
go test ./... # 40 tests et sous-tests, sans modèle ni mm, ~1 s
Le binaire de test joue un faux mm (fakemm_test.go) : relancé avec MINIME_FAKE_MM=1, il répond en ACP avec les formats de messages de mm (quatre options de permission, rawOutput.output, usage dans la réponse au prompt) : des réponses, une réflexion, un appel d'outil avec sa permission, un tour lent à annuler, un tour sourd à l'annulation, une erreur, un plantage. go test -race demande cgo et un compilateur C.
Mesures (2026-11-01, llmman sur la machine de l'auteur, mm v1.0.3 compilé depuis l'arbre de travail)
| Exemple | Résultat |
|---|---|
complete |
réponse en une phrase, end_turn, 663 tokens, 8,3 s (modèle à froid) |
stream |
find examples -name "*.go" | wc -l autorisé par le callback (lecture seule), résultat 4, réponse streamée morceau par morceau, end_turn |
cancel |
histoire coupée au bout de 3,0 s (cancelled, 394 caractères gardés) ; la question suivante sur le même Agent a eu sa réponse aussitôt, sans connaître l'histoire, que mm a retirée |
permission |
pwd refusé : le modèle a inventé une réponse (/root) ; autorisé : le vrai répertoire. Un refus n'empêche pas un petit modèle de deviner. |
Quand le choisir, et quand ne pas le choisir
- Ce client a besoin du binaire
mm, mais il marche dès aujourd'hui sans rien changer à mm, et il isole chaque agent dans son propre processus (l'état global de mm ne pose aucun problème). - Le ticket 139 (un SDK en mémoire) n'aurait pas besoin du binaire, mais il faudrait d'abord faire du code de mm une bibliothèque importable.
Pas fait
- Pas de module Go publié : il faudrait livrer le binaire
mmavec, pour chaque plateforme. session/load/session/resume(sessions enregistrées), images et fichiers joints dans les prompts, formulairesask_user: non exposés.
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 |
|