bots-garden/mini-mepublic Fork
⑂ 1fa0c42
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

Update petits-llms.adoc, config samples and open ticket 53

- docs/fr/explanation/petits-llms.adoc: fetch_content (tool list,
  error codes, output kept out of the context, URL repair), provider and
  model in session files, config.samples in p13/p15, measured prompt
  breakdown in p7, references table, revdate 2026-09-30.
- config.samples/*.yaml: fetchContentTool: true, as in agent.dmr.yaml.
- .tickets/issues/0053: tool order changes on every question (Genkit
  builds the tool list from a map; not fixed in v1.13.1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
k33g committed 2026-09-30T08:03:00+02:00 Browse files
1fa0c42 parent: 2d919bd
added .tickets/issues/0053-tool-order-changes-on-every-question.yaml +82 -0
new file mode 100644
@@ -0,0 +1,82 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 53
3+title: Tool order changes on every question (breaks the KV prefix cache)
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-30T05:52:16.000Z
9+updatedAt: 2026-09-30T05:57:31.000Z
10+labels:
11+ - kind::bug
12+ - priority::high
13+ - llama.cpp
14+related:
15+ - 43
16+body: |
17+ The `tools` array of the chat request comes in a different order on every
18+ `Generate` call, i.e. on every user question of the same session.
19+
20+ Cause: Genkit v1.10, `ai/generate.go` (GenerateWithRequest, ~l.236-273)
21+ puts the tool definitions in a map (`toolDefMap`) and builds `toolDefs` by
22+ ranging over it — Go map iteration order is random. Within one question the
23+ same request is reused, so the order only changes between questions.
24+
25+ Seen on 2026-09-30 with a fake OpenAI server capturing mm's requests
26+ (agent.dmr.yaml, 7 tools): a rotation of the same order on 6 runs of
27+ `mm -p hello`, and on the 4 questions of one `mm -acp` session.
28+
29+ Why it matters: llama.cpp only reuses its KV cache for the part of the
30+ prompt that is identical to the previous request. Templates that render the
31+ tools in the system block (Qwen-style) would then re-read the tools AND the
32+ whole history on every question — minutes of prefill on a 20k-token session
33+ with a local 27B. Not measured yet (needs `cached_tokens`, see
34+ petits-llms.adoc, piste p10).
35+
36+ Fix candidate: sort `req.Tools` by name in mm's `WrapModel` middleware in
37+ `engine.Generate` (internal/engine/engine.go), which already sees every
38+ request.
39+tasks:
40+ - id: 1
41+ title: Sort req.Tools by name in WrapModel, with a test (fake model, several Generate calls, same order)
42+ state: open
43+ priority: none
44+ author:
45+ name: k33g
46+ email: ph.charriere@gmail.com
47+ createdAt: 2026-09-30T05:52:16.000Z
48+ updatedAt: 2026-09-30T05:52:16.000Z
49+ - id: 2
50+ title: Measure cached_tokens / prefill time on llama-server before and after (same session, 3-4 questions)
51+ state: open
52+ priority: none
53+ author:
54+ name: k33g
55+ email: ph.charriere@gmail.com
56+ createdAt: 2026-09-30T05:52:16.000Z
57+ updatedAt: 2026-09-30T05:52:16.000Z
58+ - id: 3
59+ title: Document the result in petits-llms.adoc (p7 / p10)
60+ state: open
61+ priority: none
62+ author:
63+ name: k33g
64+ email: ph.charriere@gmail.com
65+ createdAt: 2026-09-30T05:52:16.000Z
66+ updatedAt: 2026-09-30T05:52:16.000Z
67+comments:
68+ - id: 1
69+ author:
70+ name: k33g
71+ email: ph.charriere@gmail.com
72+ createdAt: 2026-09-30T05:57:31.000Z
73+ body: |
74+ Upgrading Genkit does not fix it. Checked on 2026-09-30:
75+ - v1.13.1 (latest; same code under github.com/firebase/genkit/go and
76+ github.com/genkit-ai/genkit/go): ai/generate.go l.470-511 still builds
77+ toolDefs by ranging over toolDefMap.
78+ - main branch (raw generate.go of the day): same lines, unchanged.
79+ - plugins/compat_oai WithTools keeps the order it is given, and no Sort
80+ on tools anywhere in ai/ or compat_oai.
81+ So the fix stays on mm's side (sort req.Tools in WrapModel). An upstream
82+ issue/PR on genkit-ai/genkit could fix it for everyone.
new file mode 100644
@@ -0,0 +1,82 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 53
3+title: Tool order changes on every question (breaks the KV prefix cache)
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-30T05:52:16.000Z
9+updatedAt: 2026-09-30T05:57:31.000Z
10+labels:
11+ - kind::bug
12+ - priority::high
13+ - llama.cpp
14+related:
15+ - 43
16+body: |
17+ The `tools` array of the chat request comes in a different order on every
18+ `Generate` call, i.e. on every user question of the same session.
19+
20+ Cause: Genkit v1.10, `ai/generate.go` (GenerateWithRequest, ~l.236-273)
21+ puts the tool definitions in a map (`toolDefMap`) and builds `toolDefs` by
22+ ranging over it — Go map iteration order is random. Within one question the
23+ same request is reused, so the order only changes between questions.
24+
25+ Seen on 2026-09-30 with a fake OpenAI server capturing mm's requests
26+ (agent.dmr.yaml, 7 tools): a rotation of the same order on 6 runs of
27+ `mm -p hello`, and on the 4 questions of one `mm -acp` session.
28+
29+ Why it matters: llama.cpp only reuses its KV cache for the part of the
30+ prompt that is identical to the previous request. Templates that render the
31+ tools in the system block (Qwen-style) would then re-read the tools AND the
32+ whole history on every question — minutes of prefill on a 20k-token session
33+ with a local 27B. Not measured yet (needs `cached_tokens`, see
34+ petits-llms.adoc, piste p10).
35+
36+ Fix candidate: sort `req.Tools` by name in mm's `WrapModel` middleware in
37+ `engine.Generate` (internal/engine/engine.go), which already sees every
38+ request.
39+tasks:
40+ - id: 1
41+ title: Sort req.Tools by name in WrapModel, with a test (fake model, several Generate calls, same order)
42+ state: open
43+ priority: none
44+ author:
45+ name: k33g
46+ email: ph.charriere@gmail.com
47+ createdAt: 2026-09-30T05:52:16.000Z
48+ updatedAt: 2026-09-30T05:52:16.000Z
49+ - id: 2
50+ title: Measure cached_tokens / prefill time on llama-server before and after (same session, 3-4 questions)
51+ state: open
52+ priority: none
53+ author:
54+ name: k33g
55+ email: ph.charriere@gmail.com
56+ createdAt: 2026-09-30T05:52:16.000Z
57+ updatedAt: 2026-09-30T05:52:16.000Z
58+ - id: 3
59+ title: Document the result in petits-llms.adoc (p7 / p10)
60+ state: open
61+ priority: none
62+ author:
63+ name: k33g
64+ email: ph.charriere@gmail.com
65+ createdAt: 2026-09-30T05:52:16.000Z
66+ updatedAt: 2026-09-30T05:52:16.000Z
67+comments:
68+ - id: 1
69+ author:
70+ name: k33g
71+ email: ph.charriere@gmail.com
72+ createdAt: 2026-09-30T05:57:31.000Z
73+ body: |
74+ Upgrading Genkit does not fix it. Checked on 2026-09-30:
75+ - v1.13.1 (latest; same code under github.com/firebase/genkit/go and
76+ github.com/genkit-ai/genkit/go): ai/generate.go l.470-511 still builds
77+ toolDefs by ranging over toolDefMap.
78+ - main branch (raw generate.go of the day): same lines, unchanged.
79+ - plugins/compat_oai WithTools keeps the order it is given, and no Sort
80+ on tools anywhere in ai/ or compat_oai.
81+ So the fix stays on mm's side (sort req.Tools in WrapModel). An upstream
82+ issue/PR on genkit-ai/genkit could fix it for everyone.
modified config.samples/agent.dmr.gemma.yaml +5 -0
@@ -24,6 +24,11 @@ editTools: true
2424 # Set to false to disable plan creation and tracking via the `plan` tool.
2525 planTool: true
2626
27+# The built-in fetch_content tool: fetch a web page and get it as Markdown
28+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
29+# Honours the site's robots.txt. Off by default — it reaches the network.
30+fetchContentTool: true
31+
2732 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
2833 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
2934 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
@@ -24,6 +24,11 @@ editTools: true
24 # Set to false to disable plan creation and tracking via the `plan` tool.24 # Set to false to disable plan creation and tracking via the `plan` tool.
25 planTool: true25 planTool: true
26 26
27+# The built-in fetch_content tool: fetch a web page and get it as Markdown
28+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
29+# Honours the site's robots.txt. Off by default — it reaches the network.
30+fetchContentTool: true
31+
27 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).32 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
28 # The file extension picks the server. A server missing from the PATH is skipped with a warning.33 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
29 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):34 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
modified config.samples/agent.dmr.gpt-oss.yaml +5 -0
@@ -25,6 +25,11 @@ editTools: true
2525 # Set to false to disable plan creation and tracking via the `plan` tool.
2626 planTool: true
2727
28+# The built-in fetch_content tool: fetch a web page and get it as Markdown
29+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
30+# Honours the site's robots.txt. Off by default — it reaches the network.
31+fetchContentTool: true
32+
2833 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
2934 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
3035 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
@@ -25,6 +25,11 @@ editTools: true
25 # Set to false to disable plan creation and tracking via the `plan` tool.25 # Set to false to disable plan creation and tracking via the `plan` tool.
26 planTool: true26 planTool: true
27 27
28+# The built-in fetch_content tool: fetch a web page and get it as Markdown
29+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
30+# Honours the site's robots.txt. Off by default — it reaches the network.
31+fetchContentTool: true
32+
28 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).33 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
29 # The file extension picks the server. A server missing from the PATH is skipped with a warning.34 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
30 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):35 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
modified config.samples/agent.dmr.qwen3.6.yaml +5 -0
@@ -27,6 +27,11 @@ editTools: true
2727 # Set to false to disable plan creation and tracking via the `plan` tool.
2828 planTool: true
2929
30+# The built-in fetch_content tool: fetch a web page and get it as Markdown
31+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
32+# Honours the site's robots.txt. Off by default — it reaches the network.
33+fetchContentTool: true
34+
3035 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
3136 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
3237 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
@@ -27,6 +27,11 @@ editTools: true
27 # Set to false to disable plan creation and tracking via the `plan` tool.27 # Set to false to disable plan creation and tracking via the `plan` tool.
28 planTool: true28 planTool: true
29 29
30+# The built-in fetch_content tool: fetch a web page and get it as Markdown
31+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
32+# Honours the site's robots.txt. Off by default — it reaches the network.
33+fetchContentTool: true
34+
30 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).35 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
31 # The file extension picks the server. A server missing from the PATH is skipped with a warning.36 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
32 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):37 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
modified config.samples/agent.llamacpp.ornith.yaml +5 -0
@@ -30,6 +30,11 @@ editTools: true
3030 # Set to false to disable plan creation and tracking via the `plan` tool.
3131 planTool: true
3232
33+# The built-in fetch_content tool: fetch a web page and get it as Markdown
34+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
35+# Honours the site's robots.txt. Off by default — it reaches the network.
36+fetchContentTool: true
37+
3338 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
3439 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
3540 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
@@ -30,6 +30,11 @@ editTools: true
30 # Set to false to disable plan creation and tracking via the `plan` tool.30 # Set to false to disable plan creation and tracking via the `plan` tool.
31 planTool: true31 planTool: true
32 32
33+# The built-in fetch_content tool: fetch a web page and get it as Markdown
34+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
35+# Honours the site's robots.txt. Off by default — it reaches the network.
36+fetchContentTool: true
37+
33 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).38 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
34 # The file extension picks the server. A server missing from the PATH is skipped with a warning.39 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
35 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):40 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
modified config.samples/agent.llamacpp.qwen3.8.yaml +5 -0
@@ -30,6 +30,11 @@ editTools: true
3030 # Set to false to disable plan creation and tracking via the `plan` tool.
3131 planTool: true
3232
33+# The built-in fetch_content tool: fetch a web page and get it as Markdown
34+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
35+# Honours the site's robots.txt. Off by default — it reaches the network.
36+fetchContentTool: true
37+
3338 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
3439 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
3540 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
@@ -30,6 +30,11 @@ editTools: true
30 # Set to false to disable plan creation and tracking via the `plan` tool.30 # Set to false to disable plan creation and tracking via the `plan` tool.
31 planTool: true31 planTool: true
32 32
33+# The built-in fetch_content tool: fetch a web page and get it as Markdown
34+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
35+# Honours the site's robots.txt. Off by default — it reaches the network.
36+fetchContentTool: true
37+
33 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).38 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
34 # The file extension picks the server. A server missing from the PATH is skipped with a warning.39 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
35 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):40 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
modified docs/fr/explanation/petits-llms.adoc +43 -6
@@ -6,7 +6,7 @@
66 :icons: font
77 :source-highlighter: rouge
88 :lang: fr
9-:revdate: 2026-09-26
9+:revdate: 2026-09-30
1010
1111 [abstract]
1212 Mini Me (`mm`) a été conçu pour tourner avec des modèles *locaux* — typiquement 2 à 12 milliards de paramètres servis par Docker Model Runner ou llama-server.
@@ -56,6 +56,8 @@ L'agent est né autour d'un seul outil, `bash` (`internal/tools/tools.go`). Les
5656 * `read_skill` n'existe pas si `skillsDir` ne contient aucun `.md` ;
5757 * `lsp` n'existe pas si aucun serveur de langage n'est configuré *et* présent dans le `PATH`.
5858
59+Un dernier, `fetch_content` (voir <<fetch>>), est désactivé par défaut (`fetchContentTool: true` pour l'activer, comme dans `agent.dmr.yaml`) : c'est le seul outil intégré qui accède au réseau.
60+
5961 Chaque outil déclaré coûte des tokens à chaque requête et une option de plus parmi lesquelles un petit modèle doit choisir. Moins il y en a, mieux il choisit.
6062
6163 === Des descriptions écrites pour être suivies
@@ -66,6 +68,7 @@ Les descriptions d'outils ne décrivent pas seulement l'API, elles anticipent le
6668 * `read_file` : « edit_file needs the EXACT text of the file … Do not type it from memory. »
6769 * `edit_file` : six règles *numérotées*, dont « do not switch to write_file to force the change through ».
6870 * `write_file` : rappelle qu'il écrase tout et renvoie vers `edit_file`.
71+* `fetch_content` : donne des exemples de demandes en plusieurs langues (« récupère le contenu de https://example.com »), avec la consigne « do not use curl or wget through bash for that ».
6972
7073 Le prompt système des configurations livrées (`agent.dmr.yaml`, `agent.llamacpp.yaml`) répète ces règles en sections courtes en majuscules (`SKILLS`, `FILE EDITING`, `RULES`, `BACKGROUND JOBS`). La redondance est voulue : un petit modèle suit mieux une consigne qu'il lit deux fois, à deux endroits.
7174
@@ -85,7 +88,8 @@ Commande en échec (`[exit code: …]`), délai dépassé (`[error: 30s timeout
8588 Les messages sont rédigés comme des *consignes de réparation* :
8689
8790 * `read_skill` avec un nom inconnu répond `[skill: no skill named "x" — available: a, b, c]` ;
88-* `edit_file` avec un texte en double donne les numéros de ligne des occurrences et explique comment rendre `old` unique (`internal/fileedit/edits.go`).
91+* `edit_file` avec un texte en double donne les numéros de ligne des occurrences et explique comment rendre `old` unique (`internal/fileedit/edits.go`) ;
92+* `fetch_content` classe ses échecs en trois codes, `[fetch_content: error N (…) — raison]` : 1 = page non convertible, 2 = page inaccessible (hôte, statut HTTP, `robots.txt`), 3 = argument invalide. La description dit au modèle que le code 3 signifie « corrige ton appel », pour qu'il ne s'acharne pas sur un site en panne.
8993
9094 La seule exception est volontaire : l'arrêt par le détecteur de boucle (voir <<boucles>>).
9195
@@ -193,7 +197,7 @@ Test réel sur Docker Model Runner avec Mellum 12B, `max_tokens` volontairement
193197 * le mécanisme fonctionne : le modèle reprend la main deux fois, puis le message clair s'affiche ;
194198 * *le modèle a ignoré la consigne* les deux fois et a retenté le fichier entier, même avec un budget chiffré en caractères.
195199
196-La relance rend l'échec lisible et laisse une chance aux modèles qui suivent les instructions. Elle ne remplace pas un `max_tokens` adapté : 16 384 est une valeur raisonnable pour un modèle « thinking », avec une marge sous la fenêtre de contexte. Voir aussi la piste <<p15>>.
200+La relance rend l'échec lisible et laisse une chance aux modèles qui suivent les instructions. Elle ne remplace pas un `max_tokens` adapté : 16 384 est une valeur raisonnable pour un modèle « thinking », avec une marge sous la fenêtre de contexte. Les configurations de `config.samples/` montent à 32 768 (voir <<p13>>), alors que `agent.dmr.yaml` et `agent.llamacpp.yaml` restent à 4 096. Voir aussi la piste <<p15>>.
197201
198202 == Modèles à raisonnement
199203
@@ -208,6 +212,17 @@ Le greffon OpenAI de Genkit ignore `reasoning_content`. `mm` le lit lui-même, u
208212
209213 `truncate` limite chaque sortie d'outil à `maxOutput` caractères en conservant *le début et la fin* (la fin porte souvent l'erreur ou le résumé d'une commande). `bash` ne relit jamais plus d'1 Mio par extrémité depuis le disque.
210214
215+[[fetch]]
216+=== Une page web qui ne passe pas par le contexte
217+
218+`fetch_content` (`internal/tools/fetchcontent.go`, conversion dans `internal/webmd`) récupère une page et la rend en Markdown, après avoir retiré les scripts, la navigation, les publicités et les bandeaux de cookies. Trois choix visent directement les petits modèles :
219+
220+* *Enregistrer sans afficher.* Avec `output`, le Markdown est écrit dans le fichier et l'outil ne renvoie qu'une ligne (`[fetch_content: saved N lines … — the content is not shown]`). « Sauve-le dans hello.txt, ne l'affiche pas » ne coûte donc pas une page entière de contexte, et le modèle n'a pas de texte à recopier.
221+* *Une URL mal tapée est réparée.* `NormalizeURL` (`internal/webmd/fetch.go`) complète un nom de site sans schéma (`k33g.org` → `https://k33g.org`) et corrige un schéma sans deux-points (`https//k33g.org`), faute fréquente quand le modèle recopie la demande.
222+* *Une page vide est signalée, pas enregistrée* : le modèle ne confirme pas l'enregistrement d'un fichier vide.
223+
224+Sans `output`, la page passe par `truncate`, comme toute sortie d'outil. Vérifié le 29/09/2026 avec Mellum 12B (`mm -p`, Docker Model Runner) : pour « récupère le contenu de https//k33g.org », puis la même demande avec « ne l'affiche pas et sauve-le dans hello.txt », le modèle a choisi `fetch_content` les deux fois, a écrit le fichier et n'a pas recopié son contenu.
225+
211226 === Une compression hybride
212227
213228 `internal/compact` remplace les vieux tours par *un* résumé écrit par le modèle et garde les `keepLastTurns` derniers tours intacts (le modèle travaille sur eux). Points notables :
@@ -242,7 +257,7 @@ Un petit modèle choisit mal une procédure sur son seul nom. `internal/skills`
242257
243258 == Une méthode : mesurer
244259
245-La plupart des décisions sont justifiées dans le code par une *mesure datée* (« measured on qwen3.5-2b », « the demo audit of 2026-09-18 », « ticket 27 »). Le reste de `mm` est testé contre un moteur factice (rapide, déterministe) ; seul le routage des skills, qui est une propriété du modèle, est mesuré contre un vrai modèle. Garder cette discipline est la meilleure garantie que les améliorations ci-dessous en soient vraiment.
260+La plupart des décisions sont justifiées dans le code par une *mesure datée* (« measured on qwen3.5-2b », « the demo audit of 2026-09-18 », « ticket 27 »). Le reste de `mm` est testé contre un moteur factice (rapide, déterministe) ; seul le routage des skills, qui est une propriété du modèle, est mesuré contre un vrai modèle. Depuis le 27/09/2026, chaque fichier de session enregistre `provider` et `model`, et chaque erreur le modèle qui l'a produite (`internal/store`). Une session qui a échoué peut donc être rattachée au modèle en cause, même quand on en change en cours de route. Garder cette discipline est la meilleure garantie que les améliorations ci-dessous en soient vraiment.
246261
247262 [[pistes]]
248263 == Pistes d'amélioration
@@ -336,9 +351,28 @@ Le cas voisin des arguments qui ne sont pas du JSON valide (JSON coupé par `max
336351
337352 *Constat.* Mesuré sur `agent.dmr.yaml` : la première requête d'une session pèse ≈ 3,4k tokens avant toute question (prompt système ≈ 3,7k caractères, six descriptions d'outils et leurs schémas, catalogue de skills). La description de `plan` à elle seule fait ≈ 1,3k caractères, pour un outil utile seulement aux demandes en plusieurs étapes.
338353
354+Cette mesure date d'avant `fetch_content`. Le 30/09/2026, la première requête de `mm -p hello` avec `agent.dmr.yaml` a été capturée par un faux serveur OpenAI, avec et sans `fetchContentTool` :
355+
356+[cols="2,1,1,1",options="header"]
357+|===
358+| Élément (caractères) | Description | Schéma | Total
359+
360+| Prompt système | | | 3 436
361+| `edit_file` | 1 383 | 758 | 2 141
362+| `plan` | 1 335 | 842 | 2 177
363+| `fetch_content` | 1 037 | 1 068 | 2 105
364+| `read_skill` (catalogue de 7 skills) | 1 373 | 222 | 1 595
365+| `read_file` | 311 | 534 | 845
366+| `bash` | 575 | 164 | 739
367+| `write_file` | 348 | 307 | 655
368+| *Requête entière* (JSON) | | | *14 993* (12 762 sans `fetch_content`)
369+|===
370+
371+Les outils pèsent trois fois le prompt système (10 257 caractères contre 3 436). `fetch_content` ajoute 2 231 caractères à la requête, soit +17 %. À ≈ 3,4k tokens pour la version à six outils, cela fait ≈ 4k tokens. Ce chiffre est une estimation par proportion : le compte exact du serveur reste à relever. Le schéma de `fetch_content` est le plus lourd des sept, à cause des descriptions de ses quatre paramètres optionnels (`baseUrl`, `tables`, `strikethrough`, `taskList`).
372+
339373 *Proposition.*
340374
341-* Condenser la description de `plan` (l'exemple de workflow en sept étapes pourrait passer dans un skill), ou désactiver `planTool` par défaut dans les configurations pour petits modèles.
375+* Condenser la description de `plan` (l'exemple de workflow en sept étapes pourrait passer dans un skill), ou désactiver `planTool` par défaut dans les configurations pour petits modèles. La même question se pose pour `fetchContentTool` sur les petites fenêtres, et pour les paramètres optionnels de `fetch_content`, qu'un petit modèle n'utilise presque jamais.
342376 * Éviter les doublons entre prompt système et descriptions quand ils ne servent pas.
343377 * Mesurer chaque retrait avec `internal/eval` : la redondance est parfois ce qui fait marcher un 4B.
344378
@@ -401,7 +435,7 @@ Sur une machine locale, un préfixe en cache fait la différence entre une répo
401435 [[p13]]
402436 === Profils par modèle
403437
404-*Constat.* Les réglages utiles varient d'une famille à l'autre : pénalité de répétition, `top_k`, `min_p`, gestion du rôle `system`, longueur de prompt supportée. `tests/` contient déjà des variantes par modèle (`agent.dmr-qwen.yaml`, `agent.dmr-gemma.yaml`).
438+*Constat.* Les réglages utiles varient d'une famille à l'autre : pénalité de répétition, `top_k`, `min_p`, gestion du rôle `system`, longueur de prompt supportée. `tests/` contient déjà des variantes par modèle (`agent.dmr-qwen.yaml`, `agent.dmr-gemma.yaml`). Depuis le 28/09/2026, `config.samples/` en ajoute cinq, complètes, pour des modèles plus gros : Gemma 4 26B-A4B, gpt-oss 20B, Qwen3.6 35B-A3B (DMR), Ornith 1.5 35B-A3B et Qwen3.8 27B (llama-server). Toutes fixent `max_tokens: 32768`, `watchdogTimeout: 300s` et `summaryMaxTokens: 1200`. Ce sont des profils de fait, mais recopiés à la main : chaque changement de la configuration de base est à reporter dans chacun des fichiers (`fetchContentTool: true` y a été ajouté à la main le 30/09/2026).
405439
406440 *Proposition.* Une clé `profile:` (ou un fichier de profils) qui applique des valeurs par défaut connues par famille de modèle, surchargées par le YAML.
407441
@@ -448,5 +482,8 @@ Sur une machine locale, un préfixe en cache fait la différence entre une répo
448482 | Compression | `internal/compact/`
449483 | Skills et catalogue | `internal/skills/`, `skills/`
450484 | Évaluation du routage | `internal/eval/`
485+| Page web en Markdown (`fetch_content`) | `internal/tools/fetchcontent.go`, `internal/webmd/`
486+| Sessions (fournisseur et modèle enregistrés) | `internal/store/store.go`
487+| Configurations par modèle | `config.samples/`, `tests/`
451488 | Explications d'erreurs | `internal/engine/explain.go`, `internal/engine/openai_compat.go`
452489 |===
@@ -6,7 +6,7 @@
6 :icons: font6 :icons: font
7 :source-highlighter: rouge7 :source-highlighter: rouge
8 :lang: fr8 :lang: fr
9-:revdate: 2026-09-269+:revdate: 2026-09-30
10 10
11 [abstract]11 [abstract]
12 Mini Me (`mm`) a été conçu pour tourner avec des modèles *locaux* — typiquement 2 à 12 milliards de paramètres servis par Docker Model Runner ou llama-server.12 Mini Me (`mm`) a été conçu pour tourner avec des modèles *locaux* — typiquement 2 à 12 milliards de paramètres servis par Docker Model Runner ou llama-server.
@@ -56,6 +56,8 @@ L'agent est né autour d'un seul outil, `bash` (`internal/tools/tools.go`). Les
56 * `read_skill` n'existe pas si `skillsDir` ne contient aucun `.md` ;56 * `read_skill` n'existe pas si `skillsDir` ne contient aucun `.md` ;
57 * `lsp` n'existe pas si aucun serveur de langage n'est configuré *et* présent dans le `PATH`.57 * `lsp` n'existe pas si aucun serveur de langage n'est configuré *et* présent dans le `PATH`.
58 58
59+Un dernier, `fetch_content` (voir <<fetch>>), est désactivé par défaut (`fetchContentTool: true` pour l'activer, comme dans `agent.dmr.yaml`) : c'est le seul outil intégré qui accède au réseau.
60+
59 Chaque outil déclaré coûte des tokens à chaque requête et une option de plus parmi lesquelles un petit modèle doit choisir. Moins il y en a, mieux il choisit.61 Chaque outil déclaré coûte des tokens à chaque requête et une option de plus parmi lesquelles un petit modèle doit choisir. Moins il y en a, mieux il choisit.
60 62
61 === Des descriptions écrites pour être suivies63 === Des descriptions écrites pour être suivies
@@ -66,6 +68,7 @@ Les descriptions d'outils ne décrivent pas seulement l'API, elles anticipent le
66 * `read_file` : « edit_file needs the EXACT text of the file … Do not type it from memory. »68 * `read_file` : « edit_file needs the EXACT text of the file … Do not type it from memory. »
67 * `edit_file` : six règles *numérotées*, dont « do not switch to write_file to force the change through ».69 * `edit_file` : six règles *numérotées*, dont « do not switch to write_file to force the change through ».
68 * `write_file` : rappelle qu'il écrase tout et renvoie vers `edit_file`.70 * `write_file` : rappelle qu'il écrase tout et renvoie vers `edit_file`.
71+* `fetch_content` : donne des exemples de demandes en plusieurs langues (« récupère le contenu de https://example.com »), avec la consigne « do not use curl or wget through bash for that ».
69 72
70 Le prompt système des configurations livrées (`agent.dmr.yaml`, `agent.llamacpp.yaml`) répète ces règles en sections courtes en majuscules (`SKILLS`, `FILE EDITING`, `RULES`, `BACKGROUND JOBS`). La redondance est voulue : un petit modèle suit mieux une consigne qu'il lit deux fois, à deux endroits.73 Le prompt système des configurations livrées (`agent.dmr.yaml`, `agent.llamacpp.yaml`) répète ces règles en sections courtes en majuscules (`SKILLS`, `FILE EDITING`, `RULES`, `BACKGROUND JOBS`). La redondance est voulue : un petit modèle suit mieux une consigne qu'il lit deux fois, à deux endroits.
71 74
@@ -85,7 +88,8 @@ Commande en échec (`[exit code: …]`), délai dépassé (`[error: 30s timeout
85 Les messages sont rédigés comme des *consignes de réparation* :88 Les messages sont rédigés comme des *consignes de réparation* :
86 89
87 * `read_skill` avec un nom inconnu répond `[skill: no skill named "x" — available: a, b, c]` ;90 * `read_skill` avec un nom inconnu répond `[skill: no skill named "x" — available: a, b, c]` ;
88-* `edit_file` avec un texte en double donne les numéros de ligne des occurrences et explique comment rendre `old` unique (`internal/fileedit/edits.go`).91+* `edit_file` avec un texte en double donne les numéros de ligne des occurrences et explique comment rendre `old` unique (`internal/fileedit/edits.go`) ;
92+* `fetch_content` classe ses échecs en trois codes, `[fetch_content: error N (…) — raison]` : 1 = page non convertible, 2 = page inaccessible (hôte, statut HTTP, `robots.txt`), 3 = argument invalide. La description dit au modèle que le code 3 signifie « corrige ton appel », pour qu'il ne s'acharne pas sur un site en panne.
89 93
90 La seule exception est volontaire : l'arrêt par le détecteur de boucle (voir <<boucles>>).94 La seule exception est volontaire : l'arrêt par le détecteur de boucle (voir <<boucles>>).
91 95
@@ -193,7 +197,7 @@ Test réel sur Docker Model Runner avec Mellum 12B, `max_tokens` volontairement
193 * le mécanisme fonctionne : le modèle reprend la main deux fois, puis le message clair s'affiche ;197 * le mécanisme fonctionne : le modèle reprend la main deux fois, puis le message clair s'affiche ;
194 * *le modèle a ignoré la consigne* les deux fois et a retenté le fichier entier, même avec un budget chiffré en caractères.198 * *le modèle a ignoré la consigne* les deux fois et a retenté le fichier entier, même avec un budget chiffré en caractères.
195 199
196-La relance rend l'échec lisible et laisse une chance aux modèles qui suivent les instructions. Elle ne remplace pas un `max_tokens` adapté : 16 384 est une valeur raisonnable pour un modèle « thinking », avec une marge sous la fenêtre de contexte. Voir aussi la piste <<p15>>.200+La relance rend l'échec lisible et laisse une chance aux modèles qui suivent les instructions. Elle ne remplace pas un `max_tokens` adapté : 16 384 est une valeur raisonnable pour un modèle « thinking », avec une marge sous la fenêtre de contexte. Les configurations de `config.samples/` montent à 32 768 (voir <<p13>>), alors que `agent.dmr.yaml` et `agent.llamacpp.yaml` restent à 4 096. Voir aussi la piste <<p15>>.
197 201
198 == Modèles à raisonnement202 == Modèles à raisonnement
199 203
@@ -208,6 +212,17 @@ Le greffon OpenAI de Genkit ignore `reasoning_content`. `mm` le lit lui-même, u
208 212
209 `truncate` limite chaque sortie d'outil à `maxOutput` caractères en conservant *le début et la fin* (la fin porte souvent l'erreur ou le résumé d'une commande). `bash` ne relit jamais plus d'1 Mio par extrémité depuis le disque.213 `truncate` limite chaque sortie d'outil à `maxOutput` caractères en conservant *le début et la fin* (la fin porte souvent l'erreur ou le résumé d'une commande). `bash` ne relit jamais plus d'1 Mio par extrémité depuis le disque.
210 214
215+[[fetch]]
216+=== Une page web qui ne passe pas par le contexte
217+
218+`fetch_content` (`internal/tools/fetchcontent.go`, conversion dans `internal/webmd`) récupère une page et la rend en Markdown, après avoir retiré les scripts, la navigation, les publicités et les bandeaux de cookies. Trois choix visent directement les petits modèles :
219+
220+* *Enregistrer sans afficher.* Avec `output`, le Markdown est écrit dans le fichier et l'outil ne renvoie qu'une ligne (`[fetch_content: saved N lines … — the content is not shown]`). « Sauve-le dans hello.txt, ne l'affiche pas » ne coûte donc pas une page entière de contexte, et le modèle n'a pas de texte à recopier.
221+* *Une URL mal tapée est réparée.* `NormalizeURL` (`internal/webmd/fetch.go`) complète un nom de site sans schéma (`k33g.org` → `https://k33g.org`) et corrige un schéma sans deux-points (`https//k33g.org`), faute fréquente quand le modèle recopie la demande.
222+* *Une page vide est signalée, pas enregistrée* : le modèle ne confirme pas l'enregistrement d'un fichier vide.
223+
224+Sans `output`, la page passe par `truncate`, comme toute sortie d'outil. Vérifié le 29/09/2026 avec Mellum 12B (`mm -p`, Docker Model Runner) : pour « récupère le contenu de https//k33g.org », puis la même demande avec « ne l'affiche pas et sauve-le dans hello.txt », le modèle a choisi `fetch_content` les deux fois, a écrit le fichier et n'a pas recopié son contenu.
225+
211 === Une compression hybride226 === Une compression hybride
212 227
213 `internal/compact` remplace les vieux tours par *un* résumé écrit par le modèle et garde les `keepLastTurns` derniers tours intacts (le modèle travaille sur eux). Points notables :228 `internal/compact` remplace les vieux tours par *un* résumé écrit par le modèle et garde les `keepLastTurns` derniers tours intacts (le modèle travaille sur eux). Points notables :
@@ -242,7 +257,7 @@ Un petit modèle choisit mal une procédure sur son seul nom. `internal/skills`
242 257
243 == Une méthode : mesurer258 == Une méthode : mesurer
244 259
245-La plupart des décisions sont justifiées dans le code par une *mesure datée* (« measured on qwen3.5-2b », « the demo audit of 2026-09-18 », « ticket 27 »). Le reste de `mm` est testé contre un moteur factice (rapide, déterministe) ; seul le routage des skills, qui est une propriété du modèle, est mesuré contre un vrai modèle. Garder cette discipline est la meilleure garantie que les améliorations ci-dessous en soient vraiment.260+La plupart des décisions sont justifiées dans le code par une *mesure datée* (« measured on qwen3.5-2b », « the demo audit of 2026-09-18 », « ticket 27 »). Le reste de `mm` est testé contre un moteur factice (rapide, déterministe) ; seul le routage des skills, qui est une propriété du modèle, est mesuré contre un vrai modèle. Depuis le 27/09/2026, chaque fichier de session enregistre `provider` et `model`, et chaque erreur le modèle qui l'a produite (`internal/store`). Une session qui a échoué peut donc être rattachée au modèle en cause, même quand on en change en cours de route. Garder cette discipline est la meilleure garantie que les améliorations ci-dessous en soient vraiment.
246 261
247 [[pistes]]262 [[pistes]]
248 == Pistes d'amélioration263 == Pistes d'amélioration
@@ -336,9 +351,28 @@ Le cas voisin des arguments qui ne sont pas du JSON valide (JSON coupé par `max
336 351
337 *Constat.* Mesuré sur `agent.dmr.yaml` : la première requête d'une session pèse ≈ 3,4k tokens avant toute question (prompt système ≈ 3,7k caractères, six descriptions d'outils et leurs schémas, catalogue de skills). La description de `plan` à elle seule fait ≈ 1,3k caractères, pour un outil utile seulement aux demandes en plusieurs étapes.352 *Constat.* Mesuré sur `agent.dmr.yaml` : la première requête d'une session pèse ≈ 3,4k tokens avant toute question (prompt système ≈ 3,7k caractères, six descriptions d'outils et leurs schémas, catalogue de skills). La description de `plan` à elle seule fait ≈ 1,3k caractères, pour un outil utile seulement aux demandes en plusieurs étapes.
338 353
354+Cette mesure date d'avant `fetch_content`. Le 30/09/2026, la première requête de `mm -p hello` avec `agent.dmr.yaml` a été capturée par un faux serveur OpenAI, avec et sans `fetchContentTool` :
355+
356+[cols="2,1,1,1",options="header"]
357+|===
358+| Élément (caractères) | Description | Schéma | Total
359+
360+| Prompt système | | | 3 436
361+| `edit_file` | 1 383 | 758 | 2 141
362+| `plan` | 1 335 | 842 | 2 177
363+| `fetch_content` | 1 037 | 1 068 | 2 105
364+| `read_skill` (catalogue de 7 skills) | 1 373 | 222 | 1 595
365+| `read_file` | 311 | 534 | 845
366+| `bash` | 575 | 164 | 739
367+| `write_file` | 348 | 307 | 655
368+| *Requête entière* (JSON) | | | *14 993* (12 762 sans `fetch_content`)
369+|===
370+
371+Les outils pèsent trois fois le prompt système (10 257 caractères contre 3 436). `fetch_content` ajoute 2 231 caractères à la requête, soit +17 %. À ≈ 3,4k tokens pour la version à six outils, cela fait ≈ 4k tokens. Ce chiffre est une estimation par proportion : le compte exact du serveur reste à relever. Le schéma de `fetch_content` est le plus lourd des sept, à cause des descriptions de ses quatre paramètres optionnels (`baseUrl`, `tables`, `strikethrough`, `taskList`).
372+
339 *Proposition.*373 *Proposition.*
340 374
341-* Condenser la description de `plan` (l'exemple de workflow en sept étapes pourrait passer dans un skill), ou désactiver `planTool` par défaut dans les configurations pour petits modèles.375+* Condenser la description de `plan` (l'exemple de workflow en sept étapes pourrait passer dans un skill), ou désactiver `planTool` par défaut dans les configurations pour petits modèles. La même question se pose pour `fetchContentTool` sur les petites fenêtres, et pour les paramètres optionnels de `fetch_content`, qu'un petit modèle n'utilise presque jamais.
342 * Éviter les doublons entre prompt système et descriptions quand ils ne servent pas.376 * Éviter les doublons entre prompt système et descriptions quand ils ne servent pas.
343 * Mesurer chaque retrait avec `internal/eval` : la redondance est parfois ce qui fait marcher un 4B.377 * Mesurer chaque retrait avec `internal/eval` : la redondance est parfois ce qui fait marcher un 4B.
344 378
@@ -401,7 +435,7 @@ Sur une machine locale, un préfixe en cache fait la différence entre une répo
401 [[p13]]435 [[p13]]
402 === Profils par modèle436 === Profils par modèle
403 437
404-*Constat.* Les réglages utiles varient d'une famille à l'autre : pénalité de répétition, `top_k`, `min_p`, gestion du rôle `system`, longueur de prompt supportée. `tests/` contient déjà des variantes par modèle (`agent.dmr-qwen.yaml`, `agent.dmr-gemma.yaml`).438+*Constat.* Les réglages utiles varient d'une famille à l'autre : pénalité de répétition, `top_k`, `min_p`, gestion du rôle `system`, longueur de prompt supportée. `tests/` contient déjà des variantes par modèle (`agent.dmr-qwen.yaml`, `agent.dmr-gemma.yaml`). Depuis le 28/09/2026, `config.samples/` en ajoute cinq, complètes, pour des modèles plus gros : Gemma 4 26B-A4B, gpt-oss 20B, Qwen3.6 35B-A3B (DMR), Ornith 1.5 35B-A3B et Qwen3.8 27B (llama-server). Toutes fixent `max_tokens: 32768`, `watchdogTimeout: 300s` et `summaryMaxTokens: 1200`. Ce sont des profils de fait, mais recopiés à la main : chaque changement de la configuration de base est à reporter dans chacun des fichiers (`fetchContentTool: true` y a été ajouté à la main le 30/09/2026).
405 439
406 *Proposition.* Une clé `profile:` (ou un fichier de profils) qui applique des valeurs par défaut connues par famille de modèle, surchargées par le YAML.440 *Proposition.* Une clé `profile:` (ou un fichier de profils) qui applique des valeurs par défaut connues par famille de modèle, surchargées par le YAML.
407 441
@@ -448,5 +482,8 @@ Sur une machine locale, un préfixe en cache fait la différence entre une répo
448 | Compression | `internal/compact/`482 | Compression | `internal/compact/`
449 | Skills et catalogue | `internal/skills/`, `skills/`483 | Skills et catalogue | `internal/skills/`, `skills/`
450 | Évaluation du routage | `internal/eval/`484 | Évaluation du routage | `internal/eval/`
485+| Page web en Markdown (`fetch_content`) | `internal/tools/fetchcontent.go`, `internal/webmd/`
486+| Sessions (fournisseur et modèle enregistrés) | `internal/store/store.go`
487+| Configurations par modèle | `config.samples/`, `tests/`
451 | Explications d'erreurs | `internal/engine/explain.go`, `internal/engine/openai_compat.go`488 | Explications d'erreurs | `internal/engine/explain.go`, `internal/engine/openai_compat.go`
452 |===489 |===