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

📦 Mini Me

k33g committed 2026-10-26T15:46:04+01:00 Browse files
b859ec4 parent: b044d58
modified .tickets/issues/0165-how-to-unload-models-when-using-decision-models.yaml +2 -2
@@ -6,10 +6,10 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-26T05:00:19.639Z
9-updatedAt: 2026-10-26T05:01:18.763Z
9+updatedAt: 2026-10-26T14:41:47.750Z
1010 labels:
1111 - decision models
12-milestone: v1.0.2
12+milestone: v1.0.3
1313 epic: Decision models
1414 body: ""
1515 tasks: []
@@ -6,10 +6,10 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-26T05:00:19.639Z8 createdAt: 2026-10-26T05:00:19.639Z
9-updatedAt: 2026-10-26T05:01:18.763Z9+updatedAt: 2026-10-26T14:41:47.750Z
10 labels:10 labels:
11 - decision models11 - decision models
12-milestone: v1.0.212+milestone: v1.0.3
13 epic: Decision models13 epic: Decision models
14 body: ""14 body: ""
15 tasks: []15 tasks: []
modified .tickets/issues/0166-think-on-how-to-simplify-usage-of-decision-model.yaml +2 -2
@@ -6,10 +6,10 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-26T05:01:03.034Z
9-updatedAt: 2026-10-26T05:01:40.912Z
9+updatedAt: 2026-10-26T14:42:01.348Z
1010 labels:
1111 - decision models
12-milestone: v1.0.2
12+milestone: v1.0.3
1313 epic: Decision models
1414 body: ""
1515 tasks: []
@@ -6,10 +6,10 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-26T05:01:03.034Z8 createdAt: 2026-10-26T05:01:03.034Z
9-updatedAt: 2026-10-26T05:01:40.912Z9+updatedAt: 2026-10-26T14:42:01.348Z
10 labels:10 labels:
11 - decision models11 - decision models
12-milestone: v1.0.212+milestone: v1.0.3
13 epic: Decision models13 epic: Decision models
14 body: ""14 body: ""
15 tasks: []15 tasks: []
added .tickets/issues/0176-openai-sampling-settings-bug.yaml +239 -0
new file mode 100644
@@ -0,0 +1,239 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 176
3+title: openai sampling settings bug
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-26T13:50:22.583Z
9+updatedAt: 2026-10-26T14:40:39.691Z
10+labels:
11+ - kind::bug
12+ - To be tested
13+milestone: v1.0.2
14+body: |
15+ I generated the `agent.yaml` file with this command: `mm init`
16+ the result is:
17+ ```yaml
18+ # gpt-5.4 — a coding agent: reads and changes the project, runs commands.
19+ #
20+ # Written by `mm init`. Every value says why it has that value; change
21+ # them as you watch the agent work (guides/agent-settings-for-small-models.md).
22+ #
23+ # model family: openai
24+ # usage: developer
25+ # machine: small — a laptop, models up to about 8B
26+ # model facts: none — openai said nothing about this model beyond its name
27+ #
28+ # Where the numbers come from (the tag before their comment):
29+ # [card] from the model card or the GGUF, not measured — re-read the new card
30+ # [guide] the settings guide's row for this machine
31+ # [rule] a rule of thumb, not specific to this model
32+ #
33+ # mm -config <this file> or mm -tui <this file>
34+
35+ provider: openai
36+ # The model. Chosen for it (family openai): sampling (temperature,
37+ # max_completion_tokens). Change the model and redo them together — running
38+ # mm init again is the quick way.
39+ model: "gpt-5.4-mini"
40+ # No baseUrl: openai's defaults apply. For the record:
41+ # baseUrl: "https://api.openai.com/v1"
42+ # (openai has no fallback address)
43+ # The key, when the server needs one, is read from OPENAI_API_KEY (or the
44+ # variable apiKeyEnv names): export it — it never goes in this file.
45+ # The default is the OpenAI API: set baseUrl for any other OpenAI-compatible server.
46+
47+ # 0 = unknown: the generic openai provider cannot ask the server for its
48+ # window, so compression counts messages (context.maxMessages) instead — set
49+ # the window your server serves
50+ contextWindow: 0
51+ # your answer: every command and file change waits for your answer
52+ yolo: false
53+ # usage developer
54+ bashTool: true
55+ editTools: true
56+ planTool: true
57+ askUserTool: true
58+ # no skills: add a skills/ folder and set skillsDir to use some (see
59+ # docs/en/how-to/add-a-skill.md)
60+ skillsDir: no-skills
61+ # usage developer
62+ maxTurns: 40
63+ # [guide] settings guide, row "Small model (≤ 8B), short tasks"
64+ maxOutput: 8000
65+ firstTokenTimeout: 3m
66+ # [guide] aligned on bashTimeout, as the settings guide says when bash is on
67+ # (section 3)
68+ watchdogTimeout: 30s
69+ # [guide] settings guide, row "Small model (≤ 8B), short tasks"
70+ bashTimeout: 30s
71+
72+ sampling:
73+ # [card] precise temperature for a developer agent without reasoning (family
74+ # openai)
75+ temperature: 1.0
76+ # [rule] room for an answer or a whole file in one tool call (family openai
77+ # names it max_completion_tokens)
78+ max_completion_tokens: 4096
79+ # one tool call per reply: small models run a check before the change it
80+ # checks has landed otherwise
81+ parallel_tool_calls: false
82+
83+ context:
84+ # compress the history before the window is full (settings guide, every row)
85+ enabled: true
86+ # mm's default: 75 % of the window the server serves, a quarter left for the
87+ # next question and its tool outputs
88+ threshold: 75
89+
90+ agentsMd:
91+ # An AGENTS.md in the project directory is added to the system prompt.
92+ enabled: true
93+
94+ acp:
95+ name: gpt-5.4
96+ title: "Gpt-5.4 (developer)"
97+
98+ system: |
99+ You are a coding agent working in a terminal, in the user's project.
100+ You have a bash tool, file tools (read_file, write_file, edit_file) and a
101+ plan tool.
102+ Read before you change: look at the files and run the commands that tell
103+ you how the project works. Make the smallest change that does the job,
104+ then run the tests or the build to check it.
105+ Never state the output of a command you did not run in this answer.
106+ When a task has several steps, make a plan and update it as you go.
107+
108+ # This model is a hosted OpenAI model: its reasoning models (gpt-5, the
109+ # o-series) accept only the default temperature and take
110+ # max_completion_tokens instead of max_tokens — remove a sampling key the
111+ # API refuses.
112+ # The server said nothing about this model beyond its name: the settings
113+ # come from the family table.
114+
115+ # Next: skills (docs/en/how-to/add-a-skill.md), MCP servers (docs/en/tutorials/mcp-getting-started.md),
116+ # hooks (docs/en/how-to/guard-the-agent-with-hooks.md), sessions (docs/en/how-to/keep-sessions.md).
117+
118+ ```
119+ you can find the file here: `/Users/k33g/kDrive/Rickub/bots-garden/mini-me/demos/open-ai`
120+
121+ when I start mini me and ask fore something: I get this error messag:
122+
123+ ```
124+ [error: failed to create completion: POST "https://api.openai.com/v1/chat/completions": 400 Bad Request {
125+ "message": "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.",
126+ "type": "invalid_request_error",
127+ "param": "max_tokens",
128+ "code": "unsupported_parameter"
129+ }]
130+ ```
131+
132+ - you need to fix `mm init``
133+
134+ ---
135+
136+ ## Where the bug was
137+
138+ **Not in `mm init`**: the file it wrote was right — `sampling:` held `temperature: 1.0`, `max_completion_tokens: 4096` and `parallel_tool_calls: false`, and no `max_tokens`.
139+
140+ The bug was in **loading the config** (`config.Load`, `internal/config/config.go`). mm keeps default sampling values in a map: `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`. The file was decoded *into* that map, and yaml.v3 adds the file's keys to an existing map instead of replacing it. So the request carried the file's keys **plus every default the file did not name**:
141+
142+ ```
143+ written in the file: temperature: 1.0, max_completion_tokens: 4096, parallel_tool_calls: false
144+ sent to OpenAI: temperature: 1, max_completion_tokens: 4096, parallel_tool_calls: false,
145+ max_tokens: 4096, top_p: 0.9 ← never written, added by mm
146+ ```
147+
148+ The gpt-5 models refuse `max_tokens`, hence the 400. `top_p: 0.9` would have been the next refusal.
149+
150+ Two other places had the same problem:
151+ - the **context compression** (`Summarize`, `internal/engine/engine.go`) always set `max_tokens` on its summary request: the first compaction with this model would have failed with the same 400;
152+ - the message shown when a tool call is cut off ("the model hit max_tokens … while writing a tool call") only knew the name `max_tokens`.
153+
154+ And a side bug found on the way: decoding into the default map **modified the defaults themselves**, so a second `Load` (in a test, for instance) started from the previous file's keys.
155+
156+ How wide: of the 107 agent files in the repository with a `sampling:` block, 41 send `top_p: 0.9` and 14 send `max_tokens: 4096` without writing them. The demos were measured that way, which is why the merge is kept for the other providers.
157+
158+ ## What was changed
159+
160+ Decision (2026-10-26): change only for `provider: openai`.
161+
162+ | File | Change |
163+ |---|---|
164+ | `internal/config/sampling.go` (new) | `ownSampling` reads the `sampling:` block exactly as the file writes it; `applyProviderSampling` uses it instead of the merged map when the provider is `openai` (also when `AGENT_PROVIDER=openai` sets it). No block → no sampling key at all: the server's own defaults apply. |
165+ | `internal/config/config.go` | `Load` calls the two functions above (after the environment overrides), and copies the default map before decoding into it, so the defaults are never modified. |
166+ | `internal/engine/toolargs.go` | `limitKey`: the name the config gives the answer limit — `max_completion_tokens` when it is there, `max_tokens` otherwise. The cut-off tool call message reads and names that key. |
167+ | `internal/engine/engine.go` | The compression's summary request sets its budget under `limitKey` instead of always `max_tokens`. |
168+ | `internal/agentinit/presets.yaml` | `mm init`, openai family: the note now says the temperature must stay 1.0 and that mm sends the block as written (backup: `internal/agentinit/backups/presets.20261026-152158.ticket-176.yaml`). |
169+ | `docs/{en,fr}/reference/configuration.md` | The `sampling` row: merge with the defaults, the openai exception, `max_completion_tokens`. |
170+ | `docs/{en,fr}/how-to/run-with-an-openai-compatible-server.md` | The OpenAI API variant: what to write in `sampling`, and that mm adds nothing. |
171+
172+ Every other provider behaves exactly as before.
173+
174+ ## How it was checked
175+
176+ - `internal/config/sampling_test.go`, 5 tests: openai gets exactly the file's block; openai without a block gets nothing; `AGENT_PROVIDER=openai` too; another provider still merges the defaults; loading a file leaves the defaults untouched. They fail without the fix (checked).
177+ - `internal/engine/toolargs_test.go`, 2 tests: `limitKey`, and the message naming `max_completion_tokens`.
178+ - `go test ./...`, `go vet ./...`, gofmt: clean.
179+ - On the wire: a file generated by `mm init --yes --provider openai --model gpt-5.4-mini`, pointed at a local server that records the requests (`AGENT_BASE_URL`), sends `model`, `max_completion_tokens: 4096`, `temperature: 1`, `parallel_tool_calls: false` and the stream options — no `max_tokens`, no `top_p`.
180+ - **Not checked against the real OpenAI API** (no key): task 5. If gpt-5.4-mini still refuses `temperature` or `parallel_tool_calls`, remove that key from the file: mm adds nothing back.
181+tasks:
182+ - id: 1
183+ title: "config: provider openai sends exactly the file's sampling block (no defaults merged)"
184+ state: closed
185+ priority: none
186+ author:
187+ name: k33g
188+ email: ph.charriere@gmail.com
189+ createdAt: 2026-10-26T15:40:00.000Z
190+ - id: 2
191+ title: Compaction summary and the cut-off tool call message use max_completion_tokens when the config does
192+ state: closed
193+ priority: none
194+ author:
195+ name: k33g
196+ email: ph.charriere@gmail.com
197+ createdAt: 2026-10-26T15:40:00.000Z
198+ - id: 3
199+ title: "mm init: openai family note updated (presets.yaml, backed up first)"
200+ state: closed
201+ priority: none
202+ author:
203+ name: k33g
204+ email: ph.charriere@gmail.com
205+ createdAt: 2026-10-26T15:40:00.000Z
206+ - id: 4
207+ title: "Docs EN + FR: configuration reference (sampling), how-to run with an OpenAI-compatible server"
208+ state: closed
209+ priority: none
210+ author:
211+ name: k33g
212+ email: ph.charriere@gmail.com
213+ createdAt: 2026-10-26T15:40:00.000Z
214+ - id: 5
215+ title: Check against the real OpenAI API with gpt-5.4-mini (needs a key)
216+ state: closed
217+ priority: none
218+ author:
219+ name: k33g
220+ email: ph.charriere@gmail.com
221+ createdAt: 2026-10-26T15:40:00.000Z
222+ updatedAt: 2026-10-26T14:40:36.419Z
223+ closedAt: 2026-10-26T14:40:36.419Z
224+comments:
225+ - id: 1
226+ author:
227+ name: k33g
228+ email: ph.charriere@gmail.com
229+ createdAt: 2026-10-26T15:40:00.000Z
230+ body: |
231+ The cause was not `mm init` (it already wrote `max_completion_tokens`) but the config loading: the `sampling:` block was decoded INTO mm's default map (temperature 0.0, top_p 0.9, max_tokens 4096), so yaml.v3 merged them — the request carried `max_tokens: 4096` next to `max_completion_tokens`, and `top_p: 0.9` too. Measured in the repo: of 107 agent files with a sampling block, 41 send `top_p: 0.9` and 14 `max_tokens: 4096` without writing them.
232+
233+ Fix, as decided: only for `provider: openai` (also via AGENT_PROVIDER), the request carries exactly the file's block; no block = no sampling key. Every other provider keeps the merge (the demos' measurements rely on it).
234+ - `internal/config/sampling.go` (`ownSampling`, `applyProviderSampling`), called in `Load` after `applyEnv`; `Load` now clones the default map before decoding into it (it was mutated in place: a second Load or a test started from the previous file's keys).
235+ - `internal/engine`: `limitKey` — the compaction summary sets its budget under `max_completion_tokens` when the config uses that name (it forced `max_tokens`: the same 400 at the first compaction), and the "hit … while writing a tool call" message names that key.
236+ - `mm init`: the openai family note in `presets.yaml` (backup `internal/agentinit/backups/presets.20261026-152158.ticket-176.yaml`).
237+ - Tests: config 5 (`sampling_test.go`; they fail without the fix), engine 2 (`limitKey`, the message). `go test ./...`, `go vet ./...`, gofmt clean.
238+ - Checked on the wire: the file generated by `mm init --yes --provider openai --model gpt-5.4-mini`, pointed at a local server recording the request (AGENT_BASE_URL), sends `model`, `max_completion_tokens: 4096`, `temperature: 1`, `parallel_tool_calls: false`, the stream options — no `max_tokens`, no `top_p`.
239+ - NOT checked against the real OpenAI API (no key, no network in the sandbox): task 5. If gpt-5.4-mini then refuses `temperature` or `parallel_tool_calls`, remove that key from the file — mm adds nothing back.
new file mode 100644
@@ -0,0 +1,239 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 176
3+title: openai sampling settings bug
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-26T13:50:22.583Z
9+updatedAt: 2026-10-26T14:40:39.691Z
10+labels:
11+ - kind::bug
12+ - To be tested
13+milestone: v1.0.2
14+body: |
15+ I generated the `agent.yaml` file with this command: `mm init`
16+ the result is:
17+ ```yaml
18+ # gpt-5.4 — a coding agent: reads and changes the project, runs commands.
19+ #
20+ # Written by `mm init`. Every value says why it has that value; change
21+ # them as you watch the agent work (guides/agent-settings-for-small-models.md).
22+ #
23+ # model family: openai
24+ # usage: developer
25+ # machine: small — a laptop, models up to about 8B
26+ # model facts: none — openai said nothing about this model beyond its name
27+ #
28+ # Where the numbers come from (the tag before their comment):
29+ # [card] from the model card or the GGUF, not measured — re-read the new card
30+ # [guide] the settings guide's row for this machine
31+ # [rule] a rule of thumb, not specific to this model
32+ #
33+ # mm -config <this file> or mm -tui <this file>
34+
35+ provider: openai
36+ # The model. Chosen for it (family openai): sampling (temperature,
37+ # max_completion_tokens). Change the model and redo them together — running
38+ # mm init again is the quick way.
39+ model: "gpt-5.4-mini"
40+ # No baseUrl: openai's defaults apply. For the record:
41+ # baseUrl: "https://api.openai.com/v1"
42+ # (openai has no fallback address)
43+ # The key, when the server needs one, is read from OPENAI_API_KEY (or the
44+ # variable apiKeyEnv names): export it — it never goes in this file.
45+ # The default is the OpenAI API: set baseUrl for any other OpenAI-compatible server.
46+
47+ # 0 = unknown: the generic openai provider cannot ask the server for its
48+ # window, so compression counts messages (context.maxMessages) instead — set
49+ # the window your server serves
50+ contextWindow: 0
51+ # your answer: every command and file change waits for your answer
52+ yolo: false
53+ # usage developer
54+ bashTool: true
55+ editTools: true
56+ planTool: true
57+ askUserTool: true
58+ # no skills: add a skills/ folder and set skillsDir to use some (see
59+ # docs/en/how-to/add-a-skill.md)
60+ skillsDir: no-skills
61+ # usage developer
62+ maxTurns: 40
63+ # [guide] settings guide, row "Small model (≤ 8B), short tasks"
64+ maxOutput: 8000
65+ firstTokenTimeout: 3m
66+ # [guide] aligned on bashTimeout, as the settings guide says when bash is on
67+ # (section 3)
68+ watchdogTimeout: 30s
69+ # [guide] settings guide, row "Small model (≤ 8B), short tasks"
70+ bashTimeout: 30s
71+
72+ sampling:
73+ # [card] precise temperature for a developer agent without reasoning (family
74+ # openai)
75+ temperature: 1.0
76+ # [rule] room for an answer or a whole file in one tool call (family openai
77+ # names it max_completion_tokens)
78+ max_completion_tokens: 4096
79+ # one tool call per reply: small models run a check before the change it
80+ # checks has landed otherwise
81+ parallel_tool_calls: false
82+
83+ context:
84+ # compress the history before the window is full (settings guide, every row)
85+ enabled: true
86+ # mm's default: 75 % of the window the server serves, a quarter left for the
87+ # next question and its tool outputs
88+ threshold: 75
89+
90+ agentsMd:
91+ # An AGENTS.md in the project directory is added to the system prompt.
92+ enabled: true
93+
94+ acp:
95+ name: gpt-5.4
96+ title: "Gpt-5.4 (developer)"
97+
98+ system: |
99+ You are a coding agent working in a terminal, in the user's project.
100+ You have a bash tool, file tools (read_file, write_file, edit_file) and a
101+ plan tool.
102+ Read before you change: look at the files and run the commands that tell
103+ you how the project works. Make the smallest change that does the job,
104+ then run the tests or the build to check it.
105+ Never state the output of a command you did not run in this answer.
106+ When a task has several steps, make a plan and update it as you go.
107+
108+ # This model is a hosted OpenAI model: its reasoning models (gpt-5, the
109+ # o-series) accept only the default temperature and take
110+ # max_completion_tokens instead of max_tokens — remove a sampling key the
111+ # API refuses.
112+ # The server said nothing about this model beyond its name: the settings
113+ # come from the family table.
114+
115+ # Next: skills (docs/en/how-to/add-a-skill.md), MCP servers (docs/en/tutorials/mcp-getting-started.md),
116+ # hooks (docs/en/how-to/guard-the-agent-with-hooks.md), sessions (docs/en/how-to/keep-sessions.md).
117+
118+ ```
119+ you can find the file here: `/Users/k33g/kDrive/Rickub/bots-garden/mini-me/demos/open-ai`
120+
121+ when I start mini me and ask fore something: I get this error messag:
122+
123+ ```
124+ [error: failed to create completion: POST "https://api.openai.com/v1/chat/completions": 400 Bad Request {
125+ "message": "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.",
126+ "type": "invalid_request_error",
127+ "param": "max_tokens",
128+ "code": "unsupported_parameter"
129+ }]
130+ ```
131+
132+ - you need to fix `mm init``
133+
134+ ---
135+
136+ ## Where the bug was
137+
138+ **Not in `mm init`**: the file it wrote was right — `sampling:` held `temperature: 1.0`, `max_completion_tokens: 4096` and `parallel_tool_calls: false`, and no `max_tokens`.
139+
140+ The bug was in **loading the config** (`config.Load`, `internal/config/config.go`). mm keeps default sampling values in a map: `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`. The file was decoded *into* that map, and yaml.v3 adds the file's keys to an existing map instead of replacing it. So the request carried the file's keys **plus every default the file did not name**:
141+
142+ ```
143+ written in the file: temperature: 1.0, max_completion_tokens: 4096, parallel_tool_calls: false
144+ sent to OpenAI: temperature: 1, max_completion_tokens: 4096, parallel_tool_calls: false,
145+ max_tokens: 4096, top_p: 0.9 ← never written, added by mm
146+ ```
147+
148+ The gpt-5 models refuse `max_tokens`, hence the 400. `top_p: 0.9` would have been the next refusal.
149+
150+ Two other places had the same problem:
151+ - the **context compression** (`Summarize`, `internal/engine/engine.go`) always set `max_tokens` on its summary request: the first compaction with this model would have failed with the same 400;
152+ - the message shown when a tool call is cut off ("the model hit max_tokens … while writing a tool call") only knew the name `max_tokens`.
153+
154+ And a side bug found on the way: decoding into the default map **modified the defaults themselves**, so a second `Load` (in a test, for instance) started from the previous file's keys.
155+
156+ How wide: of the 107 agent files in the repository with a `sampling:` block, 41 send `top_p: 0.9` and 14 send `max_tokens: 4096` without writing them. The demos were measured that way, which is why the merge is kept for the other providers.
157+
158+ ## What was changed
159+
160+ Decision (2026-10-26): change only for `provider: openai`.
161+
162+ | File | Change |
163+ |---|---|
164+ | `internal/config/sampling.go` (new) | `ownSampling` reads the `sampling:` block exactly as the file writes it; `applyProviderSampling` uses it instead of the merged map when the provider is `openai` (also when `AGENT_PROVIDER=openai` sets it). No block → no sampling key at all: the server's own defaults apply. |
165+ | `internal/config/config.go` | `Load` calls the two functions above (after the environment overrides), and copies the default map before decoding into it, so the defaults are never modified. |
166+ | `internal/engine/toolargs.go` | `limitKey`: the name the config gives the answer limit — `max_completion_tokens` when it is there, `max_tokens` otherwise. The cut-off tool call message reads and names that key. |
167+ | `internal/engine/engine.go` | The compression's summary request sets its budget under `limitKey` instead of always `max_tokens`. |
168+ | `internal/agentinit/presets.yaml` | `mm init`, openai family: the note now says the temperature must stay 1.0 and that mm sends the block as written (backup: `internal/agentinit/backups/presets.20261026-152158.ticket-176.yaml`). |
169+ | `docs/{en,fr}/reference/configuration.md` | The `sampling` row: merge with the defaults, the openai exception, `max_completion_tokens`. |
170+ | `docs/{en,fr}/how-to/run-with-an-openai-compatible-server.md` | The OpenAI API variant: what to write in `sampling`, and that mm adds nothing. |
171+
172+ Every other provider behaves exactly as before.
173+
174+ ## How it was checked
175+
176+ - `internal/config/sampling_test.go`, 5 tests: openai gets exactly the file's block; openai without a block gets nothing; `AGENT_PROVIDER=openai` too; another provider still merges the defaults; loading a file leaves the defaults untouched. They fail without the fix (checked).
177+ - `internal/engine/toolargs_test.go`, 2 tests: `limitKey`, and the message naming `max_completion_tokens`.
178+ - `go test ./...`, `go vet ./...`, gofmt: clean.
179+ - On the wire: a file generated by `mm init --yes --provider openai --model gpt-5.4-mini`, pointed at a local server that records the requests (`AGENT_BASE_URL`), sends `model`, `max_completion_tokens: 4096`, `temperature: 1`, `parallel_tool_calls: false` and the stream options — no `max_tokens`, no `top_p`.
180+ - **Not checked against the real OpenAI API** (no key): task 5. If gpt-5.4-mini still refuses `temperature` or `parallel_tool_calls`, remove that key from the file: mm adds nothing back.
181+tasks:
182+ - id: 1
183+ title: "config: provider openai sends exactly the file's sampling block (no defaults merged)"
184+ state: closed
185+ priority: none
186+ author:
187+ name: k33g
188+ email: ph.charriere@gmail.com
189+ createdAt: 2026-10-26T15:40:00.000Z
190+ - id: 2
191+ title: Compaction summary and the cut-off tool call message use max_completion_tokens when the config does
192+ state: closed
193+ priority: none
194+ author:
195+ name: k33g
196+ email: ph.charriere@gmail.com
197+ createdAt: 2026-10-26T15:40:00.000Z
198+ - id: 3
199+ title: "mm init: openai family note updated (presets.yaml, backed up first)"
200+ state: closed
201+ priority: none
202+ author:
203+ name: k33g
204+ email: ph.charriere@gmail.com
205+ createdAt: 2026-10-26T15:40:00.000Z
206+ - id: 4
207+ title: "Docs EN + FR: configuration reference (sampling), how-to run with an OpenAI-compatible server"
208+ state: closed
209+ priority: none
210+ author:
211+ name: k33g
212+ email: ph.charriere@gmail.com
213+ createdAt: 2026-10-26T15:40:00.000Z
214+ - id: 5
215+ title: Check against the real OpenAI API with gpt-5.4-mini (needs a key)
216+ state: closed
217+ priority: none
218+ author:
219+ name: k33g
220+ email: ph.charriere@gmail.com
221+ createdAt: 2026-10-26T15:40:00.000Z
222+ updatedAt: 2026-10-26T14:40:36.419Z
223+ closedAt: 2026-10-26T14:40:36.419Z
224+comments:
225+ - id: 1
226+ author:
227+ name: k33g
228+ email: ph.charriere@gmail.com
229+ createdAt: 2026-10-26T15:40:00.000Z
230+ body: |
231+ The cause was not `mm init` (it already wrote `max_completion_tokens`) but the config loading: the `sampling:` block was decoded INTO mm's default map (temperature 0.0, top_p 0.9, max_tokens 4096), so yaml.v3 merged them — the request carried `max_tokens: 4096` next to `max_completion_tokens`, and `top_p: 0.9` too. Measured in the repo: of 107 agent files with a sampling block, 41 send `top_p: 0.9` and 14 `max_tokens: 4096` without writing them.
232+
233+ Fix, as decided: only for `provider: openai` (also via AGENT_PROVIDER), the request carries exactly the file's block; no block = no sampling key. Every other provider keeps the merge (the demos' measurements rely on it).
234+ - `internal/config/sampling.go` (`ownSampling`, `applyProviderSampling`), called in `Load` after `applyEnv`; `Load` now clones the default map before decoding into it (it was mutated in place: a second Load or a test started from the previous file's keys).
235+ - `internal/engine`: `limitKey` — the compaction summary sets its budget under `max_completion_tokens` when the config uses that name (it forced `max_tokens`: the same 400 at the first compaction), and the "hit … while writing a tool call" message names that key.
236+ - `mm init`: the openai family note in `presets.yaml` (backup `internal/agentinit/backups/presets.20261026-152158.ticket-176.yaml`).
237+ - Tests: config 5 (`sampling_test.go`; they fail without the fix), engine 2 (`limitKey`, the message). `go test ./...`, `go vet ./...`, gofmt clean.
238+ - Checked on the wire: the file generated by `mm init --yes --provider openai --model gpt-5.4-mini`, pointed at a local server recording the request (AGENT_BASE_URL), sends `model`, `max_completion_tokens: 4096`, `temperature: 1`, `parallel_tool_calls: false`, the stream options — no `max_tokens`, no `top_p`.
239+ - NOT checked against the real OpenAI API (no key, no network in the sandbox): task 5. If gpt-5.4-mini then refuses `temperature` or `parallel_tool_calls`, remove that key from the file — mm adds nothing back.
modified .tickets/milestones.yaml +3 -0
@@ -44,3 +44,6 @@ milestones:
4444 - title: v1.0.2
4545 state: open
4646 dueOn: 2026-10-31
47+ - title: v1.0.3
48+ state: open
49+ dueOn: 2026-10-31
@@ -44,3 +44,6 @@ milestones:
44 - title: v1.0.244 - title: v1.0.2
45 state: open45 state: open
46 dueOn: 2026-10-3146 dueOn: 2026-10-31
47+ - title: v1.0.3
48+ state: open
49+ dueOn: 2026-10-31
added demos/open-ai/agent.yaml +100 -0
new file mode 100644
@@ -0,0 +1,100 @@
1+# developer — a coding agent: reads and changes the project, runs commands.
2+#
3+# Written by `mm init`. Every value says why it has that value; change
4+# them as you watch the agent work (guides/agent-settings-for-small-models.md).
5+#
6+# model family: openai
7+# usage: developer
8+# machine: small — a laptop, models up to about 8B
9+# model facts: none — openai said nothing about this model beyond its name
10+#
11+# Where the numbers come from (the tag before their comment):
12+# [card] from the model card or the GGUF, not measured — re-read the new card
13+# [guide] the settings guide's row for this machine
14+# [rule] a rule of thumb, not specific to this model
15+#
16+# mm -config <this file> or mm -tui <this file>
17+
18+provider: openai
19+# The model. Chosen for it (family openai): sampling (temperature,
20+# max_completion_tokens). Change the model and redo them together — running
21+# mm init again is the quick way.
22+model: "gpt-5.4"
23+# No baseUrl: openai's defaults apply. For the record:
24+# baseUrl: "https://api.openai.com/v1"
25+# (openai has no fallback address)
26+# The key, when the server needs one, is read from OPENAI_API_KEY (or the
27+# variable apiKeyEnv names): export it — it never goes in this file.
28+# The default is the OpenAI API: set baseUrl for any other OpenAI-compatible server.
29+
30+# 0 = unknown: the generic openai provider cannot ask the server for its
31+# window, so compression counts messages (context.maxMessages) instead — set
32+# the window your server serves
33+contextWindow: 0
34+# your answer: every command and file change waits for your answer
35+yolo: false
36+# usage developer
37+bashTool: true
38+editTools: true
39+planTool: true
40+askUserTool: true
41+# no skills: add a skills/ folder and set skillsDir to use some (see
42+# docs/en/how-to/add-a-skill.md)
43+skillsDir: no-skills
44+# usage developer
45+maxTurns: 40
46+# [guide] settings guide, row "Small model (≤ 8B), short tasks"
47+maxOutput: 8000
48+firstTokenTimeout: 3m
49+# [guide] aligned on bashTimeout, as the settings guide says when bash is on
50+# (section 3)
51+watchdogTimeout: 30s
52+# [guide] settings guide, row "Small model (≤ 8B), short tasks"
53+bashTimeout: 30s
54+
55+sampling:
56+ # [card] precise temperature for a developer agent without reasoning (family
57+ # openai)
58+ temperature: 1.0
59+ # [rule] room for an answer or a whole file in one tool call (family openai
60+ # names it max_completion_tokens)
61+ max_completion_tokens: 4096
62+ # one tool call per reply: small models run a check before the change it
63+ # checks has landed otherwise
64+ parallel_tool_calls: false
65+
66+context:
67+ # compress the history before the window is full (settings guide, every row)
68+ enabled: true
69+ # mm's default: 75 % of the window the server serves, a quarter left for the
70+ # next question and its tool outputs
71+ threshold: 75
72+
73+agentsMd:
74+ # An AGENTS.md in the project directory is added to the system prompt.
75+ enabled: true
76+
77+acp:
78+ name: developer
79+ title: "Developer (developer)"
80+
81+system: |
82+ You are a coding agent working in a terminal, in the user's project.
83+ You have a bash tool, file tools (read_file, write_file, edit_file) and a
84+ plan tool.
85+ Read before you change: look at the files and run the commands that tell
86+ you how the project works. Make the smallest change that does the job,
87+ then run the tests or the build to check it.
88+ Never state the output of a command you did not run in this answer.
89+ When a task has several steps, make a plan and update it as you go.
90+
91+# This model is a hosted OpenAI model: its reasoning models (gpt-5, the
92+# o-series) accept only the default temperature (1.0) and take
93+# max_completion_tokens instead of max_tokens. With provider openai, mm
94+# sends this sampling block as written and adds none of its defaults —
95+# remove a key the API still refuses.
96+# The server said nothing about this model beyond its name: the settings
97+# come from the family table.
98+
99+# Next: skills (docs/en/how-to/add-a-skill.md), MCP servers (docs/en/tutorials/mcp-getting-started.md),
100+# hooks (docs/en/how-to/guard-the-agent-with-hooks.md), sessions (docs/en/how-to/keep-sessions.md).
new file mode 100644
@@ -0,0 +1,100 @@
1+# developer — a coding agent: reads and changes the project, runs commands.
2+#
3+# Written by `mm init`. Every value says why it has that value; change
4+# them as you watch the agent work (guides/agent-settings-for-small-models.md).
5+#
6+# model family: openai
7+# usage: developer
8+# machine: small — a laptop, models up to about 8B
9+# model facts: none — openai said nothing about this model beyond its name
10+#
11+# Where the numbers come from (the tag before their comment):
12+# [card] from the model card or the GGUF, not measured — re-read the new card
13+# [guide] the settings guide's row for this machine
14+# [rule] a rule of thumb, not specific to this model
15+#
16+# mm -config <this file> or mm -tui <this file>
17+
18+provider: openai
19+# The model. Chosen for it (family openai): sampling (temperature,
20+# max_completion_tokens). Change the model and redo them together — running
21+# mm init again is the quick way.
22+model: "gpt-5.4"
23+# No baseUrl: openai's defaults apply. For the record:
24+# baseUrl: "https://api.openai.com/v1"
25+# (openai has no fallback address)
26+# The key, when the server needs one, is read from OPENAI_API_KEY (or the
27+# variable apiKeyEnv names): export it — it never goes in this file.
28+# The default is the OpenAI API: set baseUrl for any other OpenAI-compatible server.
29+
30+# 0 = unknown: the generic openai provider cannot ask the server for its
31+# window, so compression counts messages (context.maxMessages) instead — set
32+# the window your server serves
33+contextWindow: 0
34+# your answer: every command and file change waits for your answer
35+yolo: false
36+# usage developer
37+bashTool: true
38+editTools: true
39+planTool: true
40+askUserTool: true
41+# no skills: add a skills/ folder and set skillsDir to use some (see
42+# docs/en/how-to/add-a-skill.md)
43+skillsDir: no-skills
44+# usage developer
45+maxTurns: 40
46+# [guide] settings guide, row "Small model (≤ 8B), short tasks"
47+maxOutput: 8000
48+firstTokenTimeout: 3m
49+# [guide] aligned on bashTimeout, as the settings guide says when bash is on
50+# (section 3)
51+watchdogTimeout: 30s
52+# [guide] settings guide, row "Small model (≤ 8B), short tasks"
53+bashTimeout: 30s
54+
55+sampling:
56+ # [card] precise temperature for a developer agent without reasoning (family
57+ # openai)
58+ temperature: 1.0
59+ # [rule] room for an answer or a whole file in one tool call (family openai
60+ # names it max_completion_tokens)
61+ max_completion_tokens: 4096
62+ # one tool call per reply: small models run a check before the change it
63+ # checks has landed otherwise
64+ parallel_tool_calls: false
65+
66+context:
67+ # compress the history before the window is full (settings guide, every row)
68+ enabled: true
69+ # mm's default: 75 % of the window the server serves, a quarter left for the
70+ # next question and its tool outputs
71+ threshold: 75
72+
73+agentsMd:
74+ # An AGENTS.md in the project directory is added to the system prompt.
75+ enabled: true
76+
77+acp:
78+ name: developer
79+ title: "Developer (developer)"
80+
81+system: |
82+ You are a coding agent working in a terminal, in the user's project.
83+ You have a bash tool, file tools (read_file, write_file, edit_file) and a
84+ plan tool.
85+ Read before you change: look at the files and run the commands that tell
86+ you how the project works. Make the smallest change that does the job,
87+ then run the tests or the build to check it.
88+ Never state the output of a command you did not run in this answer.
89+ When a task has several steps, make a plan and update it as you go.
90+
91+# This model is a hosted OpenAI model: its reasoning models (gpt-5, the
92+# o-series) accept only the default temperature (1.0) and take
93+# max_completion_tokens instead of max_tokens. With provider openai, mm
94+# sends this sampling block as written and adds none of its defaults —
95+# remove a key the API still refuses.
96+# The server said nothing about this model beyond its name: the settings
97+# come from the family table.
98+
99+# Next: skills (docs/en/how-to/add-a-skill.md), MCP servers (docs/en/tutorials/mcp-getting-started.md),
100+# hooks (docs/en/how-to/guard-the-agent-with-hooks.md), sessions (docs/en/how-to/keep-sessions.md).
modified docs/en/how-to/run-with-an-openai-compatible-server.md +1 -1
@@ -33,7 +33,7 @@ This guide shows how to run mini-me on a model server that has no dedicated prov
3333
3434 ## Variants
3535
36-- **The OpenAI API.** No `baseUrl`, `OPENAI_API_KEY` exported, a model such as `gpt-5-mini`. Its reasoning models accept only the default temperature and take `max_completion_tokens` instead of `max_tokens`: write those in `sampling`, and remove a key the API refuses. (Not measured in this repository.)
36+- **The OpenAI API.** No `baseUrl`, `OPENAI_API_KEY` exported, a model such as `gpt-5-mini`. Its reasoning models accept only the default temperature and take `max_completion_tokens` instead of `max_tokens`: write `temperature: 1.0` and `max_completion_tokens` in `sampling`. With `provider: openai` mm sends that block as written, without its own defaults (`top_p`, `max_tokens`), so remove a key the API still refuses and nothing else gets in the way. `mm init` writes such a block. (The request's content was checked against a local server; no call to the OpenAI API was made in this repository.)
3737 - **The key is refused.** The start-up warning says `openai: <baseUrl> refused the key (HTTP 401) — export OPENAI_API_KEY`.
3838 - **Nothing answers.** The warning says `openai: nothing answers at <baseUrl> — check baseUrl: …`: the address must be the OpenAI-compatible one, usually ending in `/v1`.
3939 - **A server mm has a provider for.** Prefer it (`dmr`, `llamacpp`, `llmman`, `ollama`): those know how the server reads a long prompt, turns reasoning off for a summary, unloads a model, and tell the window size. The generic provider does none of that.
@@ -33,7 +33,7 @@ This guide shows how to run mini-me on a model server that has no dedicated prov
33 33
34 ## Variants34 ## Variants
35 35
36-- **The OpenAI API.** No `baseUrl`, `OPENAI_API_KEY` exported, a model such as `gpt-5-mini`. Its reasoning models accept only the default temperature and take `max_completion_tokens` instead of `max_tokens`: write those in `sampling`, and remove a key the API refuses. (Not measured in this repository.)36+- **The OpenAI API.** No `baseUrl`, `OPENAI_API_KEY` exported, a model such as `gpt-5-mini`. Its reasoning models accept only the default temperature and take `max_completion_tokens` instead of `max_tokens`: write `temperature: 1.0` and `max_completion_tokens` in `sampling`. With `provider: openai` mm sends that block as written, without its own defaults (`top_p`, `max_tokens`), so remove a key the API still refuses and nothing else gets in the way. `mm init` writes such a block. (The request's content was checked against a local server; no call to the OpenAI API was made in this repository.)
37 - **The key is refused.** The start-up warning says `openai: <baseUrl> refused the key (HTTP 401) — export OPENAI_API_KEY`.37 - **The key is refused.** The start-up warning says `openai: <baseUrl> refused the key (HTTP 401) — export OPENAI_API_KEY`.
38 - **Nothing answers.** The warning says `openai: nothing answers at <baseUrl> — check baseUrl: …`: the address must be the OpenAI-compatible one, usually ending in `/v1`.38 - **Nothing answers.** The warning says `openai: nothing answers at <baseUrl> — check baseUrl: …`: the address must be the OpenAI-compatible one, usually ending in `/v1`.
39 - **A server mm has a provider for.** Prefer it (`dmr`, `llamacpp`, `llmman`, `ollama`): those know how the server reads a long prompt, turns reasoning off for a summary, unloads a model, and tell the window size. The generic provider does none of that.39 - **A server mm has a provider for.** Prefer it (`dmr`, `llamacpp`, `llmman`, `ollama`): those know how the server reads a long prompt, turns reasoning off for a summary, unloads a model, and tell the window size. The generic provider does none of that.
modified docs/en/reference/configuration.md +1 -1
@@ -33,7 +33,7 @@
3333 | `saveAnswerTool` | bool | `false` | Declare `save_answer`, which writes an answer the model already gave to a new file: the model sends a file name, not the text again. Off by default. The `/save` command exists either way. See [tools](tools.md#save_answer). |
3434 | `unloadModels` | bool | `true` | The `/unload [model]` command (terminal, window, editor) and the `unload_model` tool, which free a model from the server's memory: llmman and Ollama, llama-server in router mode. `false` removes both. The tool is never declared beside a `structuredOutput` or a `decision` block. See [tools](tools.md#unload_model) and [How to free a model's memory](../how-to/unload-a-model.md). |
3535 | `autoPull` | bool | `false` | At start-up, download the models this agent will use and the server does not list: its own, its `mmConfigs` agents', its `skillRouting` / `toolRouting` deciders'. The progress goes to stderr — one line rewritten in place on a terminal (`⬇ pulling <model> — 52 MB / 145 MB (35 %)`), a line per tenth elsewhere — then `⬇ pulled <model> in 0.8s`. The agent's own model failing stops `mm` with the server's message (`[engine error: autoPull: <model>: the server says: …]`); another model failing, a server that does not answer, or a provider that cannot pull is a `[warning: autoPull: …]`. An agent started by `/ask` or `ask_agent` pulls nothing; `mm --ask` pulls nothing either. A model the server already lists is never pulled (a name without a tag also matches its `:latest` entry). By provider: `llmman` and `ollama` — `POST /api/pull`; `dmr` — `POST /models/create` on the runner (the base URL without `/engines/…`), the pull counting as done once the runner lists the model; `llamacpp` — no download over the API: the warning says to start llama-server with `-hf <repo>:<quant>`, or to put the GGUF in the router's models directory; `openai` — a warning. |
36-| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |
36+| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The file's block is merged with these defaults: a default the file does not name is still sent. **Except with `provider: openai`**: the request carries exactly the file's block, nothing added (no block: no sampling key, the server's defaults apply) — the hosted OpenAI reasoning models refuse `max_tokens` and accept only the default temperature, so a default left in would be a 400. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. A config that names the limit `max_completion_tokens` (the OpenAI API's name for its newer models) is read the same way: that name is used in this message and for the summary request of the context compression. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |
3737 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
3838 | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |
3939 | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |
@@ -33,7 +33,7 @@
33 | `saveAnswerTool` | bool | `false` | Declare `save_answer`, which writes an answer the model already gave to a new file: the model sends a file name, not the text again. Off by default. The `/save` command exists either way. See [tools](tools.md#save_answer). |33 | `saveAnswerTool` | bool | `false` | Declare `save_answer`, which writes an answer the model already gave to a new file: the model sends a file name, not the text again. Off by default. The `/save` command exists either way. See [tools](tools.md#save_answer). |
34 | `unloadModels` | bool | `true` | The `/unload [model]` command (terminal, window, editor) and the `unload_model` tool, which free a model from the server's memory: llmman and Ollama, llama-server in router mode. `false` removes both. The tool is never declared beside a `structuredOutput` or a `decision` block. See [tools](tools.md#unload_model) and [How to free a model's memory](../how-to/unload-a-model.md). |34 | `unloadModels` | bool | `true` | The `/unload [model]` command (terminal, window, editor) and the `unload_model` tool, which free a model from the server's memory: llmman and Ollama, llama-server in router mode. `false` removes both. The tool is never declared beside a `structuredOutput` or a `decision` block. See [tools](tools.md#unload_model) and [How to free a model's memory](../how-to/unload-a-model.md). |
35 | `autoPull` | bool | `false` | At start-up, download the models this agent will use and the server does not list: its own, its `mmConfigs` agents', its `skillRouting` / `toolRouting` deciders'. The progress goes to stderr — one line rewritten in place on a terminal (`⬇ pulling <model> — 52 MB / 145 MB (35 %)`), a line per tenth elsewhere — then `⬇ pulled <model> in 0.8s`. The agent's own model failing stops `mm` with the server's message (`[engine error: autoPull: <model>: the server says: …]`); another model failing, a server that does not answer, or a provider that cannot pull is a `[warning: autoPull: …]`. An agent started by `/ask` or `ask_agent` pulls nothing; `mm --ask` pulls nothing either. A model the server already lists is never pulled (a name without a tag also matches its `:latest` entry). By provider: `llmman` and `ollama` — `POST /api/pull`; `dmr` — `POST /models/create` on the runner (the base URL without `/engines/…`), the pull counting as done once the runner lists the model; `llamacpp` — no download over the API: the warning says to start llama-server with `-hf <repo>:<quant>`, or to put the GGUF in the router's models directory; `openai` — a warning. |35 | `autoPull` | bool | `false` | At start-up, download the models this agent will use and the server does not list: its own, its `mmConfigs` agents', its `skillRouting` / `toolRouting` deciders'. The progress goes to stderr — one line rewritten in place on a terminal (`⬇ pulling <model> — 52 MB / 145 MB (35 %)`), a line per tenth elsewhere — then `⬇ pulled <model> in 0.8s`. The agent's own model failing stops `mm` with the server's message (`[engine error: autoPull: <model>: the server says: …]`); another model failing, a server that does not answer, or a provider that cannot pull is a `[warning: autoPull: …]`. An agent started by `/ask` or `ask_agent` pulls nothing; `mm --ask` pulls nothing either. A model the server already lists is never pulled (a name without a tag also matches its `:latest` entry). By provider: `llmman` and `ollama` — `POST /api/pull`; `dmr` — `POST /models/create` on the runner (the base URL without `/engines/…`), the pull counting as done once the runner lists the model; `llamacpp` — no download over the API: the warning says to start llama-server with `-hf <repo>:<quant>`, or to put the GGUF in the router's models directory; `openai` — a warning. |
36-| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |36+| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The file's block is merged with these defaults: a default the file does not name is still sent. **Except with `provider: openai`**: the request carries exactly the file's block, nothing added (no block: no sampling key, the server's defaults apply) — the hosted OpenAI reasoning models refuse `max_tokens` and accept only the default temperature, so a default left in would be a 400. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. A config that names the limit `max_completion_tokens` (the OpenAI API's name for its newer models) is read the same way: that name is used in this message and for the summary request of the context compression. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |
37 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |37 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
38 | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |38 | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |
39 | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |39 | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |
modified docs/fr/how-to/run-with-an-openai-compatible-server.md +1 -1
@@ -33,7 +33,7 @@ Ce guide montre comment faire tourner mini-me sur un serveur de modèles qui n'a
3333
3434 ## Variantes
3535
36-- **L'API d'OpenAI.** Pas de `baseUrl`, `OPENAI_API_KEY` exportée, un modèle comme `gpt-5-mini`. Ses modèles à raisonnement n'acceptent que la température par défaut et prennent `max_completion_tokens` au lieu de `max_tokens` : écrivez-les dans `sampling`, et retirez une clé que l'API refuse. (Non mesuré dans ce dépôt.)
36+- **L'API d'OpenAI.** Pas de `baseUrl`, `OPENAI_API_KEY` exportée, un modèle comme `gpt-5-mini`. Ses modèles à raisonnement n'acceptent que la température par défaut et prennent `max_completion_tokens` au lieu de `max_tokens` : écrivez `temperature: 1.0` et `max_completion_tokens` dans `sampling`. Avec `provider: openai`, mm envoie ce bloc tel quel, sans ses propres valeurs par défaut (`top_p`, `max_tokens`) : retirez seulement une clé que l'API refuserait encore. `mm init` écrit un tel bloc. (Le contenu de la requête a été vérifié contre un serveur local ; aucun appel à l'API OpenAI n'a été fait dans ce dépôt.)
3737 - **La clé est refusée.** L'avertissement de démarrage indique `openai: <baseUrl> refused the key (HTTP 401) — export OPENAI_API_KEY`.
3838 - **Rien ne répond.** L'avertissement indique `openai: nothing answers at <baseUrl> — check baseUrl: …` : l'adresse doit être celle compatible OpenAI, qui finit généralement par `/v1`.
3939 - **Un serveur pour lequel mm a un fournisseur.** Préférez-le (`dmr`, `llamacpp`, `llmman`, `ollama`) : ceux-là savent comment le serveur lit un long prompt, coupent le raisonnement pour un résumé, déchargent un modèle et donnent la taille de la fenêtre. Le fournisseur générique ne fait rien de tout cela.
@@ -33,7 +33,7 @@ Ce guide montre comment faire tourner mini-me sur un serveur de modèles qui n'a
33 33
34 ## Variantes34 ## Variantes
35 35
36-- **L'API d'OpenAI.** Pas de `baseUrl`, `OPENAI_API_KEY` exportée, un modèle comme `gpt-5-mini`. Ses modèles à raisonnement n'acceptent que la température par défaut et prennent `max_completion_tokens` au lieu de `max_tokens` : écrivez-les dans `sampling`, et retirez une clé que l'API refuse. (Non mesuré dans ce dépôt.)36+- **L'API d'OpenAI.** Pas de `baseUrl`, `OPENAI_API_KEY` exportée, un modèle comme `gpt-5-mini`. Ses modèles à raisonnement n'acceptent que la température par défaut et prennent `max_completion_tokens` au lieu de `max_tokens` : écrivez `temperature: 1.0` et `max_completion_tokens` dans `sampling`. Avec `provider: openai`, mm envoie ce bloc tel quel, sans ses propres valeurs par défaut (`top_p`, `max_tokens`) : retirez seulement une clé que l'API refuserait encore. `mm init` écrit un tel bloc. (Le contenu de la requête a été vérifié contre un serveur local ; aucun appel à l'API OpenAI n'a été fait dans ce dépôt.)
37 - **La clé est refusée.** L'avertissement de démarrage indique `openai: <baseUrl> refused the key (HTTP 401) — export OPENAI_API_KEY`.37 - **La clé est refusée.** L'avertissement de démarrage indique `openai: <baseUrl> refused the key (HTTP 401) — export OPENAI_API_KEY`.
38 - **Rien ne répond.** L'avertissement indique `openai: nothing answers at <baseUrl> — check baseUrl: …` : l'adresse doit être celle compatible OpenAI, qui finit généralement par `/v1`.38 - **Rien ne répond.** L'avertissement indique `openai: nothing answers at <baseUrl> — check baseUrl: …` : l'adresse doit être celle compatible OpenAI, qui finit généralement par `/v1`.
39 - **Un serveur pour lequel mm a un fournisseur.** Préférez-le (`dmr`, `llamacpp`, `llmman`, `ollama`) : ceux-là savent comment le serveur lit un long prompt, coupent le raisonnement pour un résumé, déchargent un modèle et donnent la taille de la fenêtre. Le fournisseur générique ne fait rien de tout cela.39 - **Un serveur pour lequel mm a un fournisseur.** Préférez-le (`dmr`, `llamacpp`, `llmman`, `ollama`) : ceux-là savent comment le serveur lit un long prompt, coupent le raisonnement pour un résumé, déchargent un modèle et donnent la taille de la fenêtre. Le fournisseur générique ne fait rien de tout cela.
modified docs/fr/reference/configuration.md +1 -1
@@ -33,7 +33,7 @@
3333 | `saveAnswerTool` | bool | `false` | Déclare `save_answer`, qui écrit dans un nouveau fichier une réponse que le modèle a déjà donnée : le modèle envoie un nom de fichier, pas le texte une seconde fois. Désactivé par défaut. La commande `/save` existe dans tous les cas. Voir les [outils](tools.md#save_answer). |
3434 | `unloadModels` | bool | `true` | La commande `/unload [modèle]` (terminal, fenêtre, éditeur) et l'outil `unload_model`, qui libèrent un modèle de la mémoire du serveur : llmman et Ollama, llama-server en mode router. `false` retire les deux. L'outil n'est jamais déclaré à côté d'une `structuredOutput` ni d'un bloc `decision`. Voir les [outils](tools.md#unload_model) et [Libérer la mémoire d'un modèle](../how-to/unload-a-model.md). |
3535 | `autoPull` | booléen | `false` | Au démarrage, télécharge les modèles que cet agent utilisera et que le serveur ne liste pas : le sien, ceux de ses agents `mmConfigs`, ceux de ses deciders `skillRouting` / `toolRouting`. La progression va sur stderr — une ligne réécrite sur place dans un terminal (`⬇ pulling <modèle> — 52 MB / 145 MB (35 %)`), une ligne par dixième ailleurs — puis `⬇ pulled <modèle> in 0.8s`. Un échec sur le modèle de l'agent arrête `mm` avec le message du serveur (`[engine error: autoPull: <modèle>: the server says: …]`) ; un échec sur un autre modèle, un serveur qui ne répond pas ou un fournisseur qui ne sait pas télécharger donne un `[warning: autoPull: …]`. Un agent lancé par `/ask` ou `ask_agent` ne télécharge rien ; `mm --ask` non plus. Un modèle que le serveur liste déjà n'est jamais téléchargé (un nom sans tag correspond aussi à son entrée `:latest`). Par fournisseur : `llmman` et `ollama` — `POST /api/pull` ; `dmr` — `POST /models/create` sur le runner (l'URL de base sans `/engines/…`), le téléchargement ne comptant comme fait qu'une fois le modèle listé par le runner ; `llamacpp` — pas de téléchargement par l'API : l'avertissement dit de lancer llama-server avec `-hf <dépôt>:<quant>`, ou de mettre le GGUF dans le dossier de modèles du router ; `openai` — un avertissement. |
36-| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. Une clé que l'API OpenAI ne déclare pas est envoyée telle quelle, au premier niveau de la requête — par exemple `chat_template_kwargs: {enable_thinking: false}`, qui demande à un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`) de ne pas raisonner ; `reasoning_effort: none` fait de même avec Ollama. `messages`, `tools`, `tool_choice` (et les anciens `functions`, `function_call`) sont ignorées : Genkit les construit à partir de la conversation. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |
36+| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. Le bloc du fichier est fusionné avec ces valeurs par défaut : une valeur par défaut que le fichier ne nomme pas est quand même envoyée. **Sauf avec `provider: openai`** : la requête porte exactement le bloc du fichier, rien d'ajouté (pas de bloc : aucune clé de génération, les valeurs par défaut du serveur s'appliquent) — les modèles de raisonnement hébergés par OpenAI refusent `max_tokens` et n'acceptent que la température par défaut, une valeur par défaut laissée là donnerait une erreur 400. Une clé que l'API OpenAI ne déclare pas est envoyée telle quelle, au premier niveau de la requête — par exemple `chat_template_kwargs: {enable_thinking: false}`, qui demande à un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`) de ne pas raisonner ; `reasoning_effort: none` fait de même avec Ollama. `messages`, `tools`, `tool_choice` (et les anciens `functions`, `function_call`) sont ignorées : Genkit les construit à partir de la conversation. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. Une configuration qui nomme la limite `max_completion_tokens` (le nom de l'API OpenAI pour ses modèles récents) est lue de la même façon : ce nom est repris dans ce message et dans la requête de résumé de la compression du contexte. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |
3737 | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. |
3838 | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`, `llmman` — dont les modèles GGUF locaux tournent dans llama-server), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. Ollama n'envoie pas d'avancement : cette seule attente couvre tout le prompt (et le chargement du modèle). À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). |
3939 | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. |
@@ -33,7 +33,7 @@
33 | `saveAnswerTool` | bool | `false` | Déclare `save_answer`, qui écrit dans un nouveau fichier une réponse que le modèle a déjà donnée : le modèle envoie un nom de fichier, pas le texte une seconde fois. Désactivé par défaut. La commande `/save` existe dans tous les cas. Voir les [outils](tools.md#save_answer). |33 | `saveAnswerTool` | bool | `false` | Déclare `save_answer`, qui écrit dans un nouveau fichier une réponse que le modèle a déjà donnée : le modèle envoie un nom de fichier, pas le texte une seconde fois. Désactivé par défaut. La commande `/save` existe dans tous les cas. Voir les [outils](tools.md#save_answer). |
34 | `unloadModels` | bool | `true` | La commande `/unload [modèle]` (terminal, fenêtre, éditeur) et l'outil `unload_model`, qui libèrent un modèle de la mémoire du serveur : llmman et Ollama, llama-server en mode router. `false` retire les deux. L'outil n'est jamais déclaré à côté d'une `structuredOutput` ni d'un bloc `decision`. Voir les [outils](tools.md#unload_model) et [Libérer la mémoire d'un modèle](../how-to/unload-a-model.md). |34 | `unloadModels` | bool | `true` | La commande `/unload [modèle]` (terminal, fenêtre, éditeur) et l'outil `unload_model`, qui libèrent un modèle de la mémoire du serveur : llmman et Ollama, llama-server en mode router. `false` retire les deux. L'outil n'est jamais déclaré à côté d'une `structuredOutput` ni d'un bloc `decision`. Voir les [outils](tools.md#unload_model) et [Libérer la mémoire d'un modèle](../how-to/unload-a-model.md). |
35 | `autoPull` | booléen | `false` | Au démarrage, télécharge les modèles que cet agent utilisera et que le serveur ne liste pas : le sien, ceux de ses agents `mmConfigs`, ceux de ses deciders `skillRouting` / `toolRouting`. La progression va sur stderr — une ligne réécrite sur place dans un terminal (`⬇ pulling <modèle> — 52 MB / 145 MB (35 %)`), une ligne par dixième ailleurs — puis `⬇ pulled <modèle> in 0.8s`. Un échec sur le modèle de l'agent arrête `mm` avec le message du serveur (`[engine error: autoPull: <modèle>: the server says: …]`) ; un échec sur un autre modèle, un serveur qui ne répond pas ou un fournisseur qui ne sait pas télécharger donne un `[warning: autoPull: …]`. Un agent lancé par `/ask` ou `ask_agent` ne télécharge rien ; `mm --ask` non plus. Un modèle que le serveur liste déjà n'est jamais téléchargé (un nom sans tag correspond aussi à son entrée `:latest`). Par fournisseur : `llmman` et `ollama` — `POST /api/pull` ; `dmr` — `POST /models/create` sur le runner (l'URL de base sans `/engines/…`), le téléchargement ne comptant comme fait qu'une fois le modèle listé par le runner ; `llamacpp` — pas de téléchargement par l'API : l'avertissement dit de lancer llama-server avec `-hf <dépôt>:<quant>`, ou de mettre le GGUF dans le dossier de modèles du router ; `openai` — un avertissement. |35 | `autoPull` | booléen | `false` | Au démarrage, télécharge les modèles que cet agent utilisera et que le serveur ne liste pas : le sien, ceux de ses agents `mmConfigs`, ceux de ses deciders `skillRouting` / `toolRouting`. La progression va sur stderr — une ligne réécrite sur place dans un terminal (`⬇ pulling <modèle> — 52 MB / 145 MB (35 %)`), une ligne par dixième ailleurs — puis `⬇ pulled <modèle> in 0.8s`. Un échec sur le modèle de l'agent arrête `mm` avec le message du serveur (`[engine error: autoPull: <modèle>: the server says: …]`) ; un échec sur un autre modèle, un serveur qui ne répond pas ou un fournisseur qui ne sait pas télécharger donne un `[warning: autoPull: …]`. Un agent lancé par `/ask` ou `ask_agent` ne télécharge rien ; `mm --ask` non plus. Un modèle que le serveur liste déjà n'est jamais téléchargé (un nom sans tag correspond aussi à son entrée `:latest`). Par fournisseur : `llmman` et `ollama` — `POST /api/pull` ; `dmr` — `POST /models/create` sur le runner (l'URL de base sans `/engines/…`), le téléchargement ne comptant comme fait qu'une fois le modèle listé par le runner ; `llamacpp` — pas de téléchargement par l'API : l'avertissement dit de lancer llama-server avec `-hf <dépôt>:<quant>`, ou de mettre le GGUF dans le dossier de modèles du router ; `openai` — un avertissement. |
36-| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. Une clé que l'API OpenAI ne déclare pas est envoyée telle quelle, au premier niveau de la requête — par exemple `chat_template_kwargs: {enable_thinking: false}`, qui demande à un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`) de ne pas raisonner ; `reasoning_effort: none` fait de même avec Ollama. `messages`, `tools`, `tool_choice` (et les anciens `functions`, `function_call`) sont ignorées : Genkit les construit à partir de la conversation. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |36+| `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. Le bloc du fichier est fusionné avec ces valeurs par défaut : une valeur par défaut que le fichier ne nomme pas est quand même envoyée. **Sauf avec `provider: openai`** : la requête porte exactement le bloc du fichier, rien d'ajouté (pas de bloc : aucune clé de génération, les valeurs par défaut du serveur s'appliquent) — les modèles de raisonnement hébergés par OpenAI refusent `max_tokens` et n'acceptent que la température par défaut, une valeur par défaut laissée là donnerait une erreur 400. Une clé que l'API OpenAI ne déclare pas est envoyée telle quelle, au premier niveau de la requête — par exemple `chat_template_kwargs: {enable_thinking: false}`, qui demande à un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`) de ne pas raisonner ; `reasoning_effort: none` fait de même avec Ollama. `messages`, `tools`, `tool_choice` (et les anciens `functions`, `function_call`) sont ignorées : Genkit les construit à partir de la conversation. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. Une configuration qui nomme la limite `max_completion_tokens` (le nom de l'API OpenAI pour ses modèles récents) est lue de la même façon : ce nom est repris dans ce message et dans la requête de résumé de la compression du contexte. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |
37 | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. |37 | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. |
38 | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`, `llmman` — dont les modèles GGUF locaux tournent dans llama-server), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. Ollama n'envoie pas d'avancement : cette seule attente couvre tout le prompt (et le chargement du modèle). À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). |38 | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`, `llmman` — dont les modèles GGUF locaux tournent dans llama-server), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. Ollama n'envoie pas d'avancement : cette seule attente couvre tout le prompt (et le chargement du modèle). À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). |
39 | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. |39 | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. |
added internal/agentinit/backups/presets.20261026-152158.ticket-176.yaml +305 -0
new file mode 100644
@@ -0,0 +1,305 @@
1+# presets.yaml — what `mm init` knows about model families, usages and
2+# machines (ticket 97). ONE table, shared on purpose: `mm init` embeds it, and
3+# the Claude skill of ticket 111 reads this same file. Every value says where
4+# it comes from: a model card, or a measured demo of this repository.
5+#
6+# Families are matched against the lower-cased model id, in order: the first
7+# entry whose `match` words all appear wins, so a specific family (qwen +
8+# coder) comes before its general one (qwen). `other` matches anything.
9+#
10+# Sampling: `general` is the author's recommendation for normal use (thinking
11+# off); `thinking` is used when the agent reasons; `precise` is the
12+# temperature for tool-heavy, thinking-off agents (developer, docs, JSON…).
13+# When the model server tells the GGUF's own sampling (llmman, Ollama), it
14+# replaces `general` and `thinking` — the GGUF is the most specific source.
15+#
16+# `origin` says, for `general`, `thinking` and `precise`, how each was
17+# obtained: `measured` (a demo of this repository ran with it), `card` (copied
18+# from the model card, the GGUF or the API reference, not measured here) or
19+# `rule` (a generic rule of thumb, nothing specific to the family). The
20+# generated file tags its numbers with it (ticket 121): after a model or
21+# quantization change, a measured number is re-measured, a card number re-read.
22+
23+families:
24+ - name: qwen-coder
25+ match: [qwen, coder]
26+ reasoning: none
27+ general: {temperature: 1.0, top_p: 0.95, top_k: 40}
28+ precise: 0.2
29+ source: "demos/qwen-style (Qwen3-Coder-Next, measured: 1.0 / 0.95 / 40 from the GGUF)"
30+ origin: {general: measured, precise: rule}
31+
32+ - name: qwen
33+ match: [qwen]
34+ reasoning: enable_thinking
35+ general: {temperature: 0.7, top_p: 0.8, top_k: 20}
36+ thinking: {temperature: 0.6, top_p: 0.95, top_k: 20}
37+ precise: 0.0
38+ source: "Qwen model cards (thinking 0.6 / 0.95 / 20, non-thinking 0.7 / 0.8 / 20); precise 0.0 measured with Qwen3.5 4B, thinking off (demos/docs-search, memory-search, plans)"
39+ origin: {general: card, thinking: card, precise: measured}
40+
41+ - name: gemma
42+ match: [gemma]
43+ reasoning: enable_thinking
44+ general: {temperature: 1.0, top_p: 0.95, top_k: 64}
45+ thinking: {temperature: 1.0, top_p: 0.95, top_k: 64}
46+ precise: 0.2
47+ source: "Gemma 4 GGUF sampling 1.0 / 0.95 / 64 (demos/gemini-style); precise 0.2 measured with gemma-4-E4B (demos/hooks, guards); enable_thinking is off by default in the template"
48+ origin: {general: card, thinking: card, precise: measured}
49+
50+ - name: mellum-thinking
51+ match: [mellum, thinking]
52+ reasoning: enable_thinking
53+ general: {temperature: 0.6, top_p: 0.95}
54+ thinking: {temperature: 0.6, top_p: 0.95}
55+ precise: 0.2
56+ source: "demos/coding-helpers (Mellum2 12B Thinking: 0.6 / 0.95, thinking on); demos/programming-answers (Mellum2.1: 0.2 / 0.9)"
57+ origin: {general: measured, thinking: measured, precise: measured}
58+
59+ - name: mellum
60+ match: [mellum]
61+ reasoning: none
62+ general: {temperature: 0.2, top_p: 0.9}
63+ precise: 0.0
64+ source: "demos/little-coder (Mellum2 12B Instruct, measured 0.2 / 0.9); agent.dmr.yaml (0.0 / 0.9)"
65+ origin: {general: measured, precise: rule}
66+
67+ - name: gpt-oss
68+ match: [gpt-oss]
69+ reasoning: reasoning_effort
70+ effortValues: [low, medium, high]
71+ general: {temperature: 1.0, top_p: 1.0}
72+ thinking: {temperature: 1.0, top_p: 1.0}
73+ precise: 1.0
74+ source: "demos/codex-style (gpt-oss 20B: 1.0 / 1.0, harmony format, reasoning_effort low | medium | high, default medium) — the model always reasons: never a low temperature"
75+ origin: {general: measured, thinking: measured, precise: measured}
76+
77+ - name: openai
78+ match: [gpt-]
79+ reasoning: none
80+ maxTokensKey: max_completion_tokens
81+ general: {temperature: 1.0}
82+ precise: 1.0
83+ note: "a hosted OpenAI model: its reasoning models (gpt-5, the o-series) accept only the default temperature and take max_completion_tokens instead of max_tokens — remove a sampling key the API refuses"
84+ source: "OpenAI API reference; not measured in this repository"
85+ origin: {general: card, precise: card}
86+
87+ - name: minicpm
88+ match: [minicpm]
89+ reasoning: enable_thinking
90+ general: {temperature: 0.3, top_p: 0.95}
91+ thinking: {temperature: 0.6, top_p: 0.95}
92+ precise: 0.3
93+ source: "demos/coding-helpers (MiniCPM5 2B: 0.3 / 0.95, enable_thinking false — its template has no default: always set it)"
94+ origin: {general: measured, thinking: rule, precise: measured}
95+
96+ - name: ornith
97+ match: [ornith]
98+ reasoning: enable_thinking
99+ general: {temperature: 0.3, top_p: 0.8, top_k: 20}
100+ thinking: {temperature: 0.6, top_p: 0.95, top_k: 20}
101+ precise: 0.3
102+ source: "demos/team (Ornith 1.5 9B: 0.3–0.4 / 0.8 / 20 thinking off, 0.6 / 0.95 / 20 thinking on)"
103+ origin: {general: measured, thinking: measured, precise: measured}
104+
105+ - name: granite-guardian
106+ match: [granite, guardian]
107+ reasoning: none
108+ general: {temperature: 0.0}
109+ precise: 0.0
110+ note: "a guard model: it answers Yes / No about a risk named in the system message, it is not a general agent — see demos/guard"
111+ source: "demos/guard (Granite Guardian 3.2 3B-A800M: temperature 0.0)"
112+ origin: {general: measured, precise: measured}
113+
114+ - name: menlo
115+ match: [menlo]
116+ reasoning: none
117+ general: {temperature: 0.0, top_p: 0.9}
118+ precise: 0.0
119+ source: "demos/llmman (Jan-nano, Lucy: 0.0 / 0.9)"
120+ origin: {general: measured, precise: measured}
121+
122+ - name: llama
123+ match: [llama]
124+ reasoning: none
125+ general: {temperature: 0.6, top_p: 0.9}
126+ precise: 0.2
127+ source: "Llama 3.1 8B Instruct generation_config.json (0.6 / 0.9); no measured demo in this repository"
128+ origin: {general: card, precise: rule}
129+
130+ - name: mistral
131+ match: [mistral]
132+ reasoning: none
133+ general: {temperature: 0.15}
134+ precise: 0.15
135+ source: "Mistral Small 3.1 model card (\"a relatively low temperature, such as temperature=0.15\"); no measured demo in this repository"
136+ origin: {general: card, precise: card}
137+
138+ - name: other
139+ match: []
140+ reasoning: none
141+ general: {temperature: 0.7, top_p: 0.9}
142+ precise: 0.2
143+ source: "skills/create-agent rule when nothing is published: 0.7 / 0.9, 0.2 for a code agent without reasoning"
144+ origin: {general: rule, precise: rule}
145+
146+# Usages: what the agent is for. `tools` are the YAML switches; `thinking`
147+# asks for reasoning when the family has a switch; `precise` picks the
148+# family's precise temperature (when not thinking); `docs` turns on the
149+# documentation search; `json` writes a structuredOutput example (which
150+# forbids every tool).
151+usages:
152+ - name: developer
153+ description: "a coding agent: reads and changes the project, runs commands"
154+ tools: {bashTool: true, editTools: true, planTool: true, askUserTool: true}
155+ thinking: true
156+ precise: true
157+ maxTurns: 40
158+ yolo: false
159+ system: |
160+ You are a coding agent working in a terminal, in the user's project.
161+ You have a bash tool, file tools (read_file, write_file, edit_file) and a
162+ plan tool.
163+ Read before you change: look at the files and run the commands that tell
164+ you how the project works. Make the smallest change that does the job,
165+ then run the tests or the build to check it.
166+ Never state the output of a command you did not run in this answer.
167+ When a task has several steps, make a plan and update it as you go.
168+
169+ - name: reviewer
170+ description: "reviews code and changes, suggests fixes, changes nothing unasked"
171+ tools: {bashTool: true, editTools: true, planTool: false, askUserTool: true}
172+ thinking: true
173+ precise: true
174+ maxTurns: 30
175+ yolo: false
176+ system: |
177+ You are a code reviewer working in a terminal, in the user's project.
178+ Read the code and the changes (git diff, git log) with your tools, and
179+ report: bugs first, then risks, then style — each with the file and line.
180+ Do not change any file unless the user asks you to.
181+ Never state the output of a command you did not run in this answer.
182+
183+ - name: translator
184+ description: "translates texts between languages, no tools"
185+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: false}
186+ thinking: false
187+ precise: true
188+ maxTurns: 3
189+ yolo: true
190+ system: |
191+ You are a translator. Translate the text the user gives you into the
192+ language they ask for (English ↔ French when they do not say).
193+ Keep the meaning, the tone and the formatting (Markdown, code blocks,
194+ lists). Do not translate code, file names or product names.
195+ Answer with the translation only, no comment.
196+
197+ - name: writer
198+ description: "writes documents and saves them to files"
199+ tools: {bashTool: false, editTools: true, planTool: false, askUserTool: true, saveAnswerTool: true}
200+ thinking: false
201+ precise: false
202+ maxTurns: 10
203+ yolo: false
204+ system: |
205+ You are a technical writer. Write clear, well-structured documents in
206+ Markdown: short paragraphs, headings, examples.
207+ When the user asks you to save a document you just wrote, call
208+ save_answer with the file name; to change an existing file, read it
209+ first, then edit it.
210+
211+ - name: assistant
212+ description: "answers questions, no tools"
213+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: true}
214+ thinking: false
215+ precise: false
216+ maxTurns: 5
217+ yolo: true
218+ system: |
219+ You are a helpful assistant. Answer clearly and briefly, in the user's
220+ language. Say when you do not know; never invent facts, names or
221+ figures.
222+
223+ - name: researcher
224+ description: "reads web pages and summarises them"
225+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: true, fetchContentTool: true, saveAnswerTool: true}
226+ thinking: false
227+ precise: false
228+ maxTurns: 15
229+ yolo: false
230+ system: |
231+ You are a research assistant. To answer, read the web pages the user
232+ names with fetch_content, then summarise what they say, citing each
233+ page's address. Never state what a page says without having read it in
234+ this answer.
235+
236+ - name: docs
237+ description: "answers from a documentation folder (docs/), with links to the passages"
238+ tools: {bashTool: false, editTools: true, planTool: false, askUserTool: false}
239+ docs: true
240+ thinking: false
241+ precise: true
242+ maxTurns: 8
243+ yolo: true
244+ system: |
245+ You answer questions about the project from its documentation, in the
246+ folder docs/. For every question the documentation may cover, call
247+ search_docs first, with the words the documentation is likely to use
248+ (often English, names of commands and types). Answer from the passages
249+ it returns, and end with the link of the passage you used (path:line).
250+ If nothing is found, try other words once, then say the documentation
251+ does not cover it.
252+
253+ - name: json
254+ description: "answers in JSON shaped by Go types (structuredOutput), no tools"
255+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: false}
256+ json: true
257+ thinking: false
258+ precise: true
259+ maxTurns: 3
260+ yolo: true
261+ system: |
262+ You extract facts from the user's text and answer in the JSON shape you
263+ are given. Fill every field from the text; use null when the text does
264+ not say.
265+
266+ - name: coordinator
267+ description: "hands each question to other mm agents (mmConfigs) and relays their answers"
268+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: false}
269+ coordinator: true
270+ thinking: false
271+ precise: true
272+ maxTurns: 4
273+ yolo: true
274+ system: |
275+ You are a coordinator. You do not answer the questions yourself: for
276+ each question, call ask_agent with the agent whose description fits,
277+ then give its answer to the user, in the user's language.
278+ For a greeting or a question about you, answer in one short sentence,
279+ without any tool.
280+
281+# Machines: from guides/agent-settings-for-small-models.md, "Summary:
282+# typical profiles". With bash on, watchdogTimeout is aligned on bashTimeout
283+# (section 3 of the guide).
284+machines:
285+ - name: small
286+ description: "a laptop, models up to about 8B"
287+ firstTokenTimeout: 3m
288+ watchdogTimeout: 20s
289+ bashTimeout: 30s
290+ maxOutput: 8000
291+ source: "settings guide, row \"Small model (≤ 8B), short tasks\""
292+ - name: medium
293+ description: "a workstation, models up to about 30B"
294+ firstTokenTimeout: 5m
295+ watchdogTimeout: 2m
296+ bashTimeout: 2m
297+ maxOutput: 16000
298+ source: "settings guide, row \"Small/medium model, general coding agent\""
299+ - name: large
300+ description: "a big model, or a modest machine running on the CPU"
301+ firstTokenTimeout: 10m
302+ watchdogTimeout: 5m
303+ bashTimeout: 5m
304+ maxOutput: 24000
305+ source: "settings guide, row \"Larger model or modest machine (CPU)\""
new file mode 100644
@@ -0,0 +1,305 @@
1+# presets.yaml — what `mm init` knows about model families, usages and
2+# machines (ticket 97). ONE table, shared on purpose: `mm init` embeds it, and
3+# the Claude skill of ticket 111 reads this same file. Every value says where
4+# it comes from: a model card, or a measured demo of this repository.
5+#
6+# Families are matched against the lower-cased model id, in order: the first
7+# entry whose `match` words all appear wins, so a specific family (qwen +
8+# coder) comes before its general one (qwen). `other` matches anything.
9+#
10+# Sampling: `general` is the author's recommendation for normal use (thinking
11+# off); `thinking` is used when the agent reasons; `precise` is the
12+# temperature for tool-heavy, thinking-off agents (developer, docs, JSON…).
13+# When the model server tells the GGUF's own sampling (llmman, Ollama), it
14+# replaces `general` and `thinking` — the GGUF is the most specific source.
15+#
16+# `origin` says, for `general`, `thinking` and `precise`, how each was
17+# obtained: `measured` (a demo of this repository ran with it), `card` (copied
18+# from the model card, the GGUF or the API reference, not measured here) or
19+# `rule` (a generic rule of thumb, nothing specific to the family). The
20+# generated file tags its numbers with it (ticket 121): after a model or
21+# quantization change, a measured number is re-measured, a card number re-read.
22+
23+families:
24+ - name: qwen-coder
25+ match: [qwen, coder]
26+ reasoning: none
27+ general: {temperature: 1.0, top_p: 0.95, top_k: 40}
28+ precise: 0.2
29+ source: "demos/qwen-style (Qwen3-Coder-Next, measured: 1.0 / 0.95 / 40 from the GGUF)"
30+ origin: {general: measured, precise: rule}
31+
32+ - name: qwen
33+ match: [qwen]
34+ reasoning: enable_thinking
35+ general: {temperature: 0.7, top_p: 0.8, top_k: 20}
36+ thinking: {temperature: 0.6, top_p: 0.95, top_k: 20}
37+ precise: 0.0
38+ source: "Qwen model cards (thinking 0.6 / 0.95 / 20, non-thinking 0.7 / 0.8 / 20); precise 0.0 measured with Qwen3.5 4B, thinking off (demos/docs-search, memory-search, plans)"
39+ origin: {general: card, thinking: card, precise: measured}
40+
41+ - name: gemma
42+ match: [gemma]
43+ reasoning: enable_thinking
44+ general: {temperature: 1.0, top_p: 0.95, top_k: 64}
45+ thinking: {temperature: 1.0, top_p: 0.95, top_k: 64}
46+ precise: 0.2
47+ source: "Gemma 4 GGUF sampling 1.0 / 0.95 / 64 (demos/gemini-style); precise 0.2 measured with gemma-4-E4B (demos/hooks, guards); enable_thinking is off by default in the template"
48+ origin: {general: card, thinking: card, precise: measured}
49+
50+ - name: mellum-thinking
51+ match: [mellum, thinking]
52+ reasoning: enable_thinking
53+ general: {temperature: 0.6, top_p: 0.95}
54+ thinking: {temperature: 0.6, top_p: 0.95}
55+ precise: 0.2
56+ source: "demos/coding-helpers (Mellum2 12B Thinking: 0.6 / 0.95, thinking on); demos/programming-answers (Mellum2.1: 0.2 / 0.9)"
57+ origin: {general: measured, thinking: measured, precise: measured}
58+
59+ - name: mellum
60+ match: [mellum]
61+ reasoning: none
62+ general: {temperature: 0.2, top_p: 0.9}
63+ precise: 0.0
64+ source: "demos/little-coder (Mellum2 12B Instruct, measured 0.2 / 0.9); agent.dmr.yaml (0.0 / 0.9)"
65+ origin: {general: measured, precise: rule}
66+
67+ - name: gpt-oss
68+ match: [gpt-oss]
69+ reasoning: reasoning_effort
70+ effortValues: [low, medium, high]
71+ general: {temperature: 1.0, top_p: 1.0}
72+ thinking: {temperature: 1.0, top_p: 1.0}
73+ precise: 1.0
74+ source: "demos/codex-style (gpt-oss 20B: 1.0 / 1.0, harmony format, reasoning_effort low | medium | high, default medium) — the model always reasons: never a low temperature"
75+ origin: {general: measured, thinking: measured, precise: measured}
76+
77+ - name: openai
78+ match: [gpt-]
79+ reasoning: none
80+ maxTokensKey: max_completion_tokens
81+ general: {temperature: 1.0}
82+ precise: 1.0
83+ note: "a hosted OpenAI model: its reasoning models (gpt-5, the o-series) accept only the default temperature and take max_completion_tokens instead of max_tokens — remove a sampling key the API refuses"
84+ source: "OpenAI API reference; not measured in this repository"
85+ origin: {general: card, precise: card}
86+
87+ - name: minicpm
88+ match: [minicpm]
89+ reasoning: enable_thinking
90+ general: {temperature: 0.3, top_p: 0.95}
91+ thinking: {temperature: 0.6, top_p: 0.95}
92+ precise: 0.3
93+ source: "demos/coding-helpers (MiniCPM5 2B: 0.3 / 0.95, enable_thinking false — its template has no default: always set it)"
94+ origin: {general: measured, thinking: rule, precise: measured}
95+
96+ - name: ornith
97+ match: [ornith]
98+ reasoning: enable_thinking
99+ general: {temperature: 0.3, top_p: 0.8, top_k: 20}
100+ thinking: {temperature: 0.6, top_p: 0.95, top_k: 20}
101+ precise: 0.3
102+ source: "demos/team (Ornith 1.5 9B: 0.3–0.4 / 0.8 / 20 thinking off, 0.6 / 0.95 / 20 thinking on)"
103+ origin: {general: measured, thinking: measured, precise: measured}
104+
105+ - name: granite-guardian
106+ match: [granite, guardian]
107+ reasoning: none
108+ general: {temperature: 0.0}
109+ precise: 0.0
110+ note: "a guard model: it answers Yes / No about a risk named in the system message, it is not a general agent — see demos/guard"
111+ source: "demos/guard (Granite Guardian 3.2 3B-A800M: temperature 0.0)"
112+ origin: {general: measured, precise: measured}
113+
114+ - name: menlo
115+ match: [menlo]
116+ reasoning: none
117+ general: {temperature: 0.0, top_p: 0.9}
118+ precise: 0.0
119+ source: "demos/llmman (Jan-nano, Lucy: 0.0 / 0.9)"
120+ origin: {general: measured, precise: measured}
121+
122+ - name: llama
123+ match: [llama]
124+ reasoning: none
125+ general: {temperature: 0.6, top_p: 0.9}
126+ precise: 0.2
127+ source: "Llama 3.1 8B Instruct generation_config.json (0.6 / 0.9); no measured demo in this repository"
128+ origin: {general: card, precise: rule}
129+
130+ - name: mistral
131+ match: [mistral]
132+ reasoning: none
133+ general: {temperature: 0.15}
134+ precise: 0.15
135+ source: "Mistral Small 3.1 model card (\"a relatively low temperature, such as temperature=0.15\"); no measured demo in this repository"
136+ origin: {general: card, precise: card}
137+
138+ - name: other
139+ match: []
140+ reasoning: none
141+ general: {temperature: 0.7, top_p: 0.9}
142+ precise: 0.2
143+ source: "skills/create-agent rule when nothing is published: 0.7 / 0.9, 0.2 for a code agent without reasoning"
144+ origin: {general: rule, precise: rule}
145+
146+# Usages: what the agent is for. `tools` are the YAML switches; `thinking`
147+# asks for reasoning when the family has a switch; `precise` picks the
148+# family's precise temperature (when not thinking); `docs` turns on the
149+# documentation search; `json` writes a structuredOutput example (which
150+# forbids every tool).
151+usages:
152+ - name: developer
153+ description: "a coding agent: reads and changes the project, runs commands"
154+ tools: {bashTool: true, editTools: true, planTool: true, askUserTool: true}
155+ thinking: true
156+ precise: true
157+ maxTurns: 40
158+ yolo: false
159+ system: |
160+ You are a coding agent working in a terminal, in the user's project.
161+ You have a bash tool, file tools (read_file, write_file, edit_file) and a
162+ plan tool.
163+ Read before you change: look at the files and run the commands that tell
164+ you how the project works. Make the smallest change that does the job,
165+ then run the tests or the build to check it.
166+ Never state the output of a command you did not run in this answer.
167+ When a task has several steps, make a plan and update it as you go.
168+
169+ - name: reviewer
170+ description: "reviews code and changes, suggests fixes, changes nothing unasked"
171+ tools: {bashTool: true, editTools: true, planTool: false, askUserTool: true}
172+ thinking: true
173+ precise: true
174+ maxTurns: 30
175+ yolo: false
176+ system: |
177+ You are a code reviewer working in a terminal, in the user's project.
178+ Read the code and the changes (git diff, git log) with your tools, and
179+ report: bugs first, then risks, then style — each with the file and line.
180+ Do not change any file unless the user asks you to.
181+ Never state the output of a command you did not run in this answer.
182+
183+ - name: translator
184+ description: "translates texts between languages, no tools"
185+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: false}
186+ thinking: false
187+ precise: true
188+ maxTurns: 3
189+ yolo: true
190+ system: |
191+ You are a translator. Translate the text the user gives you into the
192+ language they ask for (English ↔ French when they do not say).
193+ Keep the meaning, the tone and the formatting (Markdown, code blocks,
194+ lists). Do not translate code, file names or product names.
195+ Answer with the translation only, no comment.
196+
197+ - name: writer
198+ description: "writes documents and saves them to files"
199+ tools: {bashTool: false, editTools: true, planTool: false, askUserTool: true, saveAnswerTool: true}
200+ thinking: false
201+ precise: false
202+ maxTurns: 10
203+ yolo: false
204+ system: |
205+ You are a technical writer. Write clear, well-structured documents in
206+ Markdown: short paragraphs, headings, examples.
207+ When the user asks you to save a document you just wrote, call
208+ save_answer with the file name; to change an existing file, read it
209+ first, then edit it.
210+
211+ - name: assistant
212+ description: "answers questions, no tools"
213+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: true}
214+ thinking: false
215+ precise: false
216+ maxTurns: 5
217+ yolo: true
218+ system: |
219+ You are a helpful assistant. Answer clearly and briefly, in the user's
220+ language. Say when you do not know; never invent facts, names or
221+ figures.
222+
223+ - name: researcher
224+ description: "reads web pages and summarises them"
225+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: true, fetchContentTool: true, saveAnswerTool: true}
226+ thinking: false
227+ precise: false
228+ maxTurns: 15
229+ yolo: false
230+ system: |
231+ You are a research assistant. To answer, read the web pages the user
232+ names with fetch_content, then summarise what they say, citing each
233+ page's address. Never state what a page says without having read it in
234+ this answer.
235+
236+ - name: docs
237+ description: "answers from a documentation folder (docs/), with links to the passages"
238+ tools: {bashTool: false, editTools: true, planTool: false, askUserTool: false}
239+ docs: true
240+ thinking: false
241+ precise: true
242+ maxTurns: 8
243+ yolo: true
244+ system: |
245+ You answer questions about the project from its documentation, in the
246+ folder docs/. For every question the documentation may cover, call
247+ search_docs first, with the words the documentation is likely to use
248+ (often English, names of commands and types). Answer from the passages
249+ it returns, and end with the link of the passage you used (path:line).
250+ If nothing is found, try other words once, then say the documentation
251+ does not cover it.
252+
253+ - name: json
254+ description: "answers in JSON shaped by Go types (structuredOutput), no tools"
255+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: false}
256+ json: true
257+ thinking: false
258+ precise: true
259+ maxTurns: 3
260+ yolo: true
261+ system: |
262+ You extract facts from the user's text and answer in the JSON shape you
263+ are given. Fill every field from the text; use null when the text does
264+ not say.
265+
266+ - name: coordinator
267+ description: "hands each question to other mm agents (mmConfigs) and relays their answers"
268+ tools: {bashTool: false, editTools: false, planTool: false, askUserTool: false}
269+ coordinator: true
270+ thinking: false
271+ precise: true
272+ maxTurns: 4
273+ yolo: true
274+ system: |
275+ You are a coordinator. You do not answer the questions yourself: for
276+ each question, call ask_agent with the agent whose description fits,
277+ then give its answer to the user, in the user's language.
278+ For a greeting or a question about you, answer in one short sentence,
279+ without any tool.
280+
281+# Machines: from guides/agent-settings-for-small-models.md, "Summary:
282+# typical profiles". With bash on, watchdogTimeout is aligned on bashTimeout
283+# (section 3 of the guide).
284+machines:
285+ - name: small
286+ description: "a laptop, models up to about 8B"
287+ firstTokenTimeout: 3m
288+ watchdogTimeout: 20s
289+ bashTimeout: 30s
290+ maxOutput: 8000
291+ source: "settings guide, row \"Small model (≤ 8B), short tasks\""
292+ - name: medium
293+ description: "a workstation, models up to about 30B"
294+ firstTokenTimeout: 5m
295+ watchdogTimeout: 2m
296+ bashTimeout: 2m
297+ maxOutput: 16000
298+ source: "settings guide, row \"Small/medium model, general coding agent\""
299+ - name: large
300+ description: "a big model, or a modest machine running on the CPU"
301+ firstTokenTimeout: 10m
302+ watchdogTimeout: 5m
303+ bashTimeout: 5m
304+ maxOutput: 24000
305+ source: "settings guide, row \"Larger model or modest machine (CPU)\""
modified internal/agentinit/presets.yaml +1 -1
@@ -80,7 +80,7 @@ families:
8080 maxTokensKey: max_completion_tokens
8181 general: {temperature: 1.0}
8282 precise: 1.0
83- note: "a hosted OpenAI model: its reasoning models (gpt-5, the o-series) accept only the default temperature and take max_completion_tokens instead of max_tokens — remove a sampling key the API refuses"
83+ note: "a hosted OpenAI model: its reasoning models (gpt-5, the o-series) accept only the default temperature (1.0) and take max_completion_tokens instead of max_tokens. With provider openai, mm sends this sampling block as written and adds none of its defaults — remove a key the API still refuses"
8484 source: "OpenAI API reference; not measured in this repository"
8585 origin: {general: card, precise: card}
8686
@@ -80,7 +80,7 @@ families:
80 maxTokensKey: max_completion_tokens80 maxTokensKey: max_completion_tokens
81 general: {temperature: 1.0}81 general: {temperature: 1.0}
82 precise: 1.082 precise: 1.0
83- note: "a hosted OpenAI model: its reasoning models (gpt-5, the o-series) accept only the default temperature and take max_completion_tokens instead of max_tokens — remove a sampling key the API refuses"83+ note: "a hosted OpenAI model: its reasoning models (gpt-5, the o-series) accept only the default temperature (1.0) and take max_completion_tokens instead of max_tokens. With provider openai, mm sends this sampling block as written and adds none of its defaults — remove a key the API still refuses"
84 source: "OpenAI API reference; not measured in this repository"84 source: "OpenAI API reference; not measured in this repository"
85 origin: {general: card, precise: card}85 origin: {general: card, precise: card}
86 86
modified internal/config/config.go +9 -0
@@ -10,6 +10,7 @@ import (
1010 "errors"
1111 "fmt"
1212 "io/fs"
13+ "maps"
1314 "os"
1415 "path/filepath"
1516 "strconv"
@@ -721,13 +722,21 @@ func Load(cliPath string) (string, error) {
721722 }
722723
723724 // Unmarshalling INTO Cfg: keys absent from the file keep their default.
725+ // The sampling map is decoded into in place: a copy keeps the default map
726+ // itself untouched (a second Load, a test, starts from the real defaults).
727+ Cfg.Sampling = maps.Clone(Cfg.Sampling)
724728 if err := yaml.Unmarshal(data, &Cfg); err != nil {
725729 return "", fmt.Errorf("%s: %w", path, err)
726730 }
731+ own, err := ownSampling(data)
732+ if err != nil {
733+ return "", fmt.Errorf("%s: %w", path, err)
734+ }
727735 applyDeprecated()
728736 if err := applyEnv(); err != nil {
729737 return "", err
730738 }
739+ applyProviderSampling(own)
731740 resolveAgentPaths(path)
732741 resolveHooks(path)
733742 resolveToolPaths(path)
@@ -10,6 +10,7 @@ import (
10 "errors"10 "errors"
11 "fmt"11 "fmt"
12 "io/fs"12 "io/fs"
13+ "maps"
13 "os"14 "os"
14 "path/filepath"15 "path/filepath"
15 "strconv"16 "strconv"
@@ -721,13 +722,21 @@ func Load(cliPath string) (string, error) {
721 }722 }
722 723
723 // Unmarshalling INTO Cfg: keys absent from the file keep their default.724 // Unmarshalling INTO Cfg: keys absent from the file keep their default.
725+ // The sampling map is decoded into in place: a copy keeps the default map
726+ // itself untouched (a second Load, a test, starts from the real defaults).
727+ Cfg.Sampling = maps.Clone(Cfg.Sampling)
724 if err := yaml.Unmarshal(data, &Cfg); err != nil {728 if err := yaml.Unmarshal(data, &Cfg); err != nil {
725 return "", fmt.Errorf("%s: %w", path, err)729 return "", fmt.Errorf("%s: %w", path, err)
726 }730 }
731+ own, err := ownSampling(data)
732+ if err != nil {
733+ return "", fmt.Errorf("%s: %w", path, err)
734+ }
727 applyDeprecated()735 applyDeprecated()
728 if err := applyEnv(); err != nil {736 if err := applyEnv(); err != nil {
729 return "", err737 return "", err
730 }738 }
739+ applyProviderSampling(own)
731 resolveAgentPaths(path)740 resolveAgentPaths(path)
732 resolveHooks(path)741 resolveHooks(path)
733 resolveToolPaths(path)742 resolveToolPaths(path)
added internal/config/sampling.go +45 -0
new file mode 100644
@@ -0,0 +1,45 @@
1+package config
2+
3+import "gopkg.in/yaml.v3"
4+
5+// The `sampling:` block is decoded INTO Cfg.Sampling, which already holds
6+// mm's defaults (temperature 0.0, top_p 0.9, max_tokens 4096): yaml.v3 adds
7+// the file's keys to that map, so every default the file does not name is
8+// still sent. For the servers mm was tuned on that is the intended behaviour —
9+// the demos' measured settings rely on it.
10+//
11+// For `provider: openai` it is not (ticket 176). The hosted OpenAI models
12+// refuse some of those defaults outright: the gpt-5 family rejects
13+// `max_tokens` ("Use 'max_completion_tokens' instead"), so a file that writes
14+// `max_completion_tokens` still failed with a 400 because the default
15+// `max_tokens` went along. For that provider the request carries exactly the
16+// file's block, nothing added; no block at all sends no sampling key, and the
17+// server's own defaults apply.
18+
19+// ownSampling is the `sampling:` block exactly as the file writes it: nil
20+// when the file has none.
21+//
22+// own, err := ownSampling([]byte("sampling:\n temperature: 1.0\n"))
23+// // own == map[string]any{"temperature": 1.0}
24+func ownSampling(data []byte) (map[string]any, error) {
25+ var file struct {
26+ Sampling map[string]any `yaml:"sampling"`
27+ }
28+ if err := yaml.Unmarshal(data, &file); err != nil {
29+ return nil, err
30+ }
31+ return file.Sampling, nil
32+}
33+
34+// applyProviderSampling replaces the merged sampling by the file's own block
35+// when the provider takes no default (openai). It runs after applyEnv, so
36+// AGENT_PROVIDER=openai counts too.
37+func applyProviderSampling(own map[string]any) {
38+ if Cfg.Provider != "openai" {
39+ return
40+ }
41+ if own == nil {
42+ own = map[string]any{}
43+ }
44+ Cfg.Sampling = own
45+}
new file mode 100644
@@ -0,0 +1,45 @@
1+package config
2+
3+import "gopkg.in/yaml.v3"
4+
5+// The `sampling:` block is decoded INTO Cfg.Sampling, which already holds
6+// mm's defaults (temperature 0.0, top_p 0.9, max_tokens 4096): yaml.v3 adds
7+// the file's keys to that map, so every default the file does not name is
8+// still sent. For the servers mm was tuned on that is the intended behaviour —
9+// the demos' measured settings rely on it.
10+//
11+// For `provider: openai` it is not (ticket 176). The hosted OpenAI models
12+// refuse some of those defaults outright: the gpt-5 family rejects
13+// `max_tokens` ("Use 'max_completion_tokens' instead"), so a file that writes
14+// `max_completion_tokens` still failed with a 400 because the default
15+// `max_tokens` went along. For that provider the request carries exactly the
16+// file's block, nothing added; no block at all sends no sampling key, and the
17+// server's own defaults apply.
18+
19+// ownSampling is the `sampling:` block exactly as the file writes it: nil
20+// when the file has none.
21+//
22+// own, err := ownSampling([]byte("sampling:\n temperature: 1.0\n"))
23+// // own == map[string]any{"temperature": 1.0}
24+func ownSampling(data []byte) (map[string]any, error) {
25+ var file struct {
26+ Sampling map[string]any `yaml:"sampling"`
27+ }
28+ if err := yaml.Unmarshal(data, &file); err != nil {
29+ return nil, err
30+ }
31+ return file.Sampling, nil
32+}
33+
34+// applyProviderSampling replaces the merged sampling by the file's own block
35+// when the provider takes no default (openai). It runs after applyEnv, so
36+// AGENT_PROVIDER=openai counts too.
37+func applyProviderSampling(own map[string]any) {
38+ if Cfg.Provider != "openai" {
39+ return
40+ }
41+ if own == nil {
42+ own = map[string]any{}
43+ }
44+ Cfg.Sampling = own
45+}
added internal/config/sampling_test.go +68 -0
new file mode 100644
@@ -0,0 +1,68 @@
1+package config
2+
3+import (
4+ "maps"
5+ "testing"
6+)
7+
8+// Ticket 176: with provider openai, a file writing max_completion_tokens
9+// sent the default max_tokens too, and the gpt-5 family answered 400. That
10+// provider now gets exactly the file's block.
11+func TestOpenAISamplingIsExactlyTheFilesBlock(t *testing.T) {
12+ err := loadYAML(t, "provider: openai\nmodel: gpt-5.4-mini\nsampling:\n temperature: 1.0\n max_completion_tokens: 4096\n")
13+ if err != nil {
14+ t.Fatal(err)
15+ }
16+ want := map[string]any{"temperature": 1.0, "max_completion_tokens": 4096}
17+ if !maps.Equal(Cfg.Sampling, want) {
18+ t.Errorf("sampling = %v, want only the file's keys %v", Cfg.Sampling, want)
19+ }
20+}
21+
22+func TestOpenAIWithoutASamplingBlockSendsNoSamplingKey(t *testing.T) {
23+ if err := loadYAML(t, "provider: openai\nmodel: gpt-5.4-mini\n"); err != nil {
24+ t.Fatal(err)
25+ }
26+ if len(Cfg.Sampling) != 0 {
27+ t.Errorf("sampling = %v, want none: the server's defaults apply", Cfg.Sampling)
28+ }
29+}
30+
31+// AGENT_PROVIDER switches the provider after the file is read: the rule
32+// follows the provider in force, not the one written.
33+func TestAgentProviderOpenAIAlsoDropsTheDefaults(t *testing.T) {
34+ t.Setenv("AGENT_PROVIDER", "openai")
35+ if err := loadYAML(t, "provider: llmman\nmodel: m\nsampling:\n temperature: 0.7\n"); err != nil {
36+ t.Fatal(err)
37+ }
38+ if want := (map[string]any{"temperature": 0.7}); !maps.Equal(Cfg.Sampling, want) {
39+ t.Errorf("sampling = %v, want %v", Cfg.Sampling, want)
40+ }
41+}
42+
43+// The other providers keep the merge the demos were measured with: a key the
44+// file does not name keeps mm's default.
45+func TestOtherProvidersStillMergeTheDefaults(t *testing.T) {
46+ if err := loadYAML(t, "provider: llmman\nmodel: m\nsampling:\n temperature: 0.7\n"); err != nil {
47+ t.Fatal(err)
48+ }
49+ want := map[string]any{"temperature": 0.7, "top_p": 0.9, "max_tokens": 4096}
50+ if !maps.Equal(Cfg.Sampling, want) {
51+ t.Errorf("sampling = %v, want the file's temperature over the defaults %v", Cfg.Sampling, want)
52+ }
53+}
54+
55+// Loading a file must not change the defaults themselves: once a test's
56+// config is put back, the default map still says temperature 0.0, top_p 0.9,
57+// max_tokens 4096 (Load used to decode INTO that map, so a key leaked).
58+func TestLoadLeavesTheDefaultSamplingUntouched(t *testing.T) {
59+ before := maps.Clone(Cfg.Sampling)
60+ t.Run("a file with its own sampling", func(t *testing.T) {
61+ if err := loadYAML(t, "provider: llmman\nmodel: m\nsampling:\n max_completion_tokens: 10\n top_p: 0.5\n"); err != nil {
62+ t.Fatal(err)
63+ }
64+ })
65+ if !maps.Equal(Cfg.Sampling, before) {
66+ t.Errorf("default sampling after a load = %v, want it untouched %v", Cfg.Sampling, before)
67+ }
68+}
new file mode 100644
@@ -0,0 +1,68 @@
1+package config
2+
3+import (
4+ "maps"
5+ "testing"
6+)
7+
8+// Ticket 176: with provider openai, a file writing max_completion_tokens
9+// sent the default max_tokens too, and the gpt-5 family answered 400. That
10+// provider now gets exactly the file's block.
11+func TestOpenAISamplingIsExactlyTheFilesBlock(t *testing.T) {
12+ err := loadYAML(t, "provider: openai\nmodel: gpt-5.4-mini\nsampling:\n temperature: 1.0\n max_completion_tokens: 4096\n")
13+ if err != nil {
14+ t.Fatal(err)
15+ }
16+ want := map[string]any{"temperature": 1.0, "max_completion_tokens": 4096}
17+ if !maps.Equal(Cfg.Sampling, want) {
18+ t.Errorf("sampling = %v, want only the file's keys %v", Cfg.Sampling, want)
19+ }
20+}
21+
22+func TestOpenAIWithoutASamplingBlockSendsNoSamplingKey(t *testing.T) {
23+ if err := loadYAML(t, "provider: openai\nmodel: gpt-5.4-mini\n"); err != nil {
24+ t.Fatal(err)
25+ }
26+ if len(Cfg.Sampling) != 0 {
27+ t.Errorf("sampling = %v, want none: the server's defaults apply", Cfg.Sampling)
28+ }
29+}
30+
31+// AGENT_PROVIDER switches the provider after the file is read: the rule
32+// follows the provider in force, not the one written.
33+func TestAgentProviderOpenAIAlsoDropsTheDefaults(t *testing.T) {
34+ t.Setenv("AGENT_PROVIDER", "openai")
35+ if err := loadYAML(t, "provider: llmman\nmodel: m\nsampling:\n temperature: 0.7\n"); err != nil {
36+ t.Fatal(err)
37+ }
38+ if want := (map[string]any{"temperature": 0.7}); !maps.Equal(Cfg.Sampling, want) {
39+ t.Errorf("sampling = %v, want %v", Cfg.Sampling, want)
40+ }
41+}
42+
43+// The other providers keep the merge the demos were measured with: a key the
44+// file does not name keeps mm's default.
45+func TestOtherProvidersStillMergeTheDefaults(t *testing.T) {
46+ if err := loadYAML(t, "provider: llmman\nmodel: m\nsampling:\n temperature: 0.7\n"); err != nil {
47+ t.Fatal(err)
48+ }
49+ want := map[string]any{"temperature": 0.7, "top_p": 0.9, "max_tokens": 4096}
50+ if !maps.Equal(Cfg.Sampling, want) {
51+ t.Errorf("sampling = %v, want the file's temperature over the defaults %v", Cfg.Sampling, want)
52+ }
53+}
54+
55+// Loading a file must not change the defaults themselves: once a test's
56+// config is put back, the default map still says temperature 0.0, top_p 0.9,
57+// max_tokens 4096 (Load used to decode INTO that map, so a key leaked).
58+func TestLoadLeavesTheDefaultSamplingUntouched(t *testing.T) {
59+ before := maps.Clone(Cfg.Sampling)
60+ t.Run("a file with its own sampling", func(t *testing.T) {
61+ if err := loadYAML(t, "provider: llmman\nmodel: m\nsampling:\n max_completion_tokens: 10\n top_p: 0.5\n"); err != nil {
62+ t.Fatal(err)
63+ }
64+ })
65+ if !maps.Equal(Cfg.Sampling, before) {
66+ t.Errorf("default sampling after a load = %v, want it untouched %v", Cfg.Sampling, before)
67+ }
68+}
modified internal/engine/engine.go +4 -3
@@ -586,14 +586,15 @@ func (e *Engine) generate(ctx context.Context, messages []*ai.Message, tools []a
586586 // other providers that can still happen; emptySummary then says so.
587587 func (e *Engine) Summarize(ctx context.Context, messages []*ai.Message, maxTokens int) (string, error) {
588588 // The agent's sampling, minus what does not apply here: `parallel_tool_calls`
589- // without tools is a 400 on the OpenAI API, and `max_tokens` is the summary's
590- // budget, not the answer's.
589+ // without tools is a 400 on the OpenAI API, and the answer limit is the
590+ // summary's budget, not the answer's — under the name the config uses
591+ // (max_completion_tokens for the OpenAI models that refuse max_tokens).
591592 sampling := maps.Clone(config.Cfg.Sampling)
592593 if sampling == nil {
593594 sampling = map[string]any{}
594595 }
595596 delete(sampling, "parallel_tool_calls")
596- sampling["max_tokens"] = maxTokens
597+ sampling[limitKey(sampling)] = maxTokens
597598
598599 genCtx, pl, cancel := watchdog(ctx, 2*config.Cfg.WatchdogTimeout,
599600 firstTokenTimeout(2*config.Cfg.WatchdogTimeout, config.Cfg.FirstTokenTimeout))
@@ -586,14 +586,15 @@ func (e *Engine) generate(ctx context.Context, messages []*ai.Message, tools []a
586 // other providers that can still happen; emptySummary then says so.586 // other providers that can still happen; emptySummary then says so.
587 func (e *Engine) Summarize(ctx context.Context, messages []*ai.Message, maxTokens int) (string, error) {587 func (e *Engine) Summarize(ctx context.Context, messages []*ai.Message, maxTokens int) (string, error) {
588 // The agent's sampling, minus what does not apply here: `parallel_tool_calls`588 // The agent's sampling, minus what does not apply here: `parallel_tool_calls`
589- // without tools is a 400 on the OpenAI API, and `max_tokens` is the summary's589+ // without tools is a 400 on the OpenAI API, and the answer limit is the
590- // budget, not the answer's.590+ // summary's budget, not the answer's — under the name the config uses
591+ // (max_completion_tokens for the OpenAI models that refuse max_tokens).
591 sampling := maps.Clone(config.Cfg.Sampling)592 sampling := maps.Clone(config.Cfg.Sampling)
592 if sampling == nil {593 if sampling == nil {
593 sampling = map[string]any{}594 sampling = map[string]any{}
594 }595 }
595 delete(sampling, "parallel_tool_calls")596 delete(sampling, "parallel_tool_calls")
596- sampling["max_tokens"] = maxTokens597+ sampling[limitKey(sampling)] = maxTokens
597 598
598 genCtx, pl, cancel := watchdog(ctx, 2*config.Cfg.WatchdogTimeout,599 genCtx, pl, cancel := watchdog(ctx, 2*config.Cfg.WatchdogTimeout,
599 firstTokenTimeout(2*config.Cfg.WatchdogTimeout, config.Cfg.FirstTokenTimeout))600 firstTokenTimeout(2*config.Cfg.WatchdogTimeout, config.Cfg.FirstTokenTimeout))
modified internal/engine/toolargs.go +16 -4
@@ -48,14 +48,26 @@ func argsError(err error) (truncated, malformed bool) {
4848 return false, true
4949 }
5050
51-// maxTokens is the configured max_tokens, "" when the sampling sets none.
51+// maxTokens is the configured answer limit, "" when the sampling sets none.
5252 func maxTokens() string {
53- if v, ok := config.Cfg.Sampling["max_tokens"]; ok && v != nil {
53+ if v, ok := config.Cfg.Sampling[limitKey(config.Cfg.Sampling)]; ok && v != nil {
5454 return fmt.Sprint(v)
5555 }
5656 return ""
5757 }
5858
59+// limitKey is the name the sampling gives the answer limit: the OpenAI
60+// API's max_completion_tokens when the config uses it (its gpt-5 family
61+// refuses max_tokens, ticket 176), max_tokens otherwise.
62+//
63+// limitKey(map[string]any{"max_completion_tokens": 4096}) // "max_completion_tokens"
64+func limitKey(sampling map[string]any) string {
65+ if _, ok := sampling["max_completion_tokens"]; ok {
66+ return "max_completion_tokens"
67+ }
68+ return "max_tokens"
69+}
70+
5971 // pieceChars is a size one piece can safely have: half the answer limit, at
6072 // roughly three characters per token. 0 when the limit is unknown.
6173 func pieceChars() int {
@@ -101,9 +113,9 @@ func badToolArgs(err error) (string, bool) {
101113 case truncated:
102114 limit := "the answer limit"
103115 if mt := maxTokens(); mt != "" {
104- limit = "max_tokens (" + mt + ")"
116+ limit = limitKey(config.Cfg.Sampling) + " (" + mt + ")"
105117 }
106- return fmt.Sprintf("the model hit %s while writing a tool call, so its arguments were cut off — raise sampling.max_tokens in the config, or ask for a smaller change (a long file written in several parts)", limit), true
118+ return fmt.Sprintf("the model hit %s while writing a tool call, so its arguments were cut off — raise sampling.%s in the config, or ask for a smaller change (a long file written in several parts)", limit, limitKey(config.Cfg.Sampling)), true
107119 case malformed:
108120 return "the model wrote a tool call whose arguments are not valid JSON — rephrase the request, or try another model", true
109121 }
@@ -48,14 +48,26 @@ func argsError(err error) (truncated, malformed bool) {
48 return false, true48 return false, true
49 }49 }
50 50
51-// maxTokens is the configured max_tokens, "" when the sampling sets none.51+// maxTokens is the configured answer limit, "" when the sampling sets none.
52 func maxTokens() string {52 func maxTokens() string {
53- if v, ok := config.Cfg.Sampling["max_tokens"]; ok && v != nil {53+ if v, ok := config.Cfg.Sampling[limitKey(config.Cfg.Sampling)]; ok && v != nil {
54 return fmt.Sprint(v)54 return fmt.Sprint(v)
55 }55 }
56 return ""56 return ""
57 }57 }
58 58
59+// limitKey is the name the sampling gives the answer limit: the OpenAI
60+// API's max_completion_tokens when the config uses it (its gpt-5 family
61+// refuses max_tokens, ticket 176), max_tokens otherwise.
62+//
63+// limitKey(map[string]any{"max_completion_tokens": 4096}) // "max_completion_tokens"
64+func limitKey(sampling map[string]any) string {
65+ if _, ok := sampling["max_completion_tokens"]; ok {
66+ return "max_completion_tokens"
67+ }
68+ return "max_tokens"
69+}
70+
59 // pieceChars is a size one piece can safely have: half the answer limit, at71 // pieceChars is a size one piece can safely have: half the answer limit, at
60 // roughly three characters per token. 0 when the limit is unknown.72 // roughly three characters per token. 0 when the limit is unknown.
61 func pieceChars() int {73 func pieceChars() int {
@@ -101,9 +113,9 @@ func badToolArgs(err error) (string, bool) {
101 case truncated:113 case truncated:
102 limit := "the answer limit"114 limit := "the answer limit"
103 if mt := maxTokens(); mt != "" {115 if mt := maxTokens(); mt != "" {
104- limit = "max_tokens (" + mt + ")"116+ limit = limitKey(config.Cfg.Sampling) + " (" + mt + ")"
105 }117 }
106- return fmt.Sprintf("the model hit %s while writing a tool call, so its arguments were cut off — raise sampling.max_tokens in the config, or ask for a smaller change (a long file written in several parts)", limit), true118+ return fmt.Sprintf("the model hit %s while writing a tool call, so its arguments were cut off — raise sampling.%s in the config, or ask for a smaller change (a long file written in several parts)", limit, limitKey(config.Cfg.Sampling)), true
107 case malformed:119 case malformed:
108 return "the model wrote a tool call whose arguments are not valid JSON — rephrase the request, or try another model", true120 return "the model wrote a tool call whose arguments are not valid JSON — rephrase the request, or try another model", true
109 }121 }
modified internal/engine/toolargs_test.go +30 -0
@@ -135,3 +135,33 @@ func TestArgsError(t *testing.T) {
135135 }
136136 }
137137 }
138+
139+// Ticket 176: an OpenAI config names its limit max_completion_tokens; the
140+// limit is read, and named, under that key.
141+func TestLimitKeyFollowsTheConfig(t *testing.T) {
142+ cases := []struct {
143+ sampling map[string]any
144+ want string
145+ }{
146+ {map[string]any{"max_tokens": 4096}, "max_tokens"},
147+ {map[string]any{"max_completion_tokens": 4096}, "max_completion_tokens"},
148+ {map[string]any{}, "max_tokens"},
149+ {nil, "max_tokens"},
150+ }
151+ for _, c := range cases {
152+ if got := limitKey(c.sampling); got != c.want {
153+ t.Errorf("limitKey(%v) = %q, want %q", c.sampling, got, c.want)
154+ }
155+ }
156+}
157+
158+func TestTruncatedToolCallNamesMaxCompletionTokens(t *testing.T) {
159+ old := config.Cfg.Sampling
160+ config.Cfg.Sampling = map[string]any{"max_completion_tokens": 2048}
161+ defer func() { config.Cfg.Sampling = old }()
162+
163+ got, ok := badToolArgs(errTruncatedArgs)
164+ if !ok || !strings.Contains(got, "max_completion_tokens (2048)") || !strings.Contains(got, "raise sampling.max_completion_tokens") {
165+ t.Errorf("badToolArgs = %q, want the limit named max_completion_tokens", got)
166+ }
167+}
@@ -135,3 +135,33 @@ func TestArgsError(t *testing.T) {
135 }135 }
136 }136 }
137 }137 }
138+
139+// Ticket 176: an OpenAI config names its limit max_completion_tokens; the
140+// limit is read, and named, under that key.
141+func TestLimitKeyFollowsTheConfig(t *testing.T) {
142+ cases := []struct {
143+ sampling map[string]any
144+ want string
145+ }{
146+ {map[string]any{"max_tokens": 4096}, "max_tokens"},
147+ {map[string]any{"max_completion_tokens": 4096}, "max_completion_tokens"},
148+ {map[string]any{}, "max_tokens"},
149+ {nil, "max_tokens"},
150+ }
151+ for _, c := range cases {
152+ if got := limitKey(c.sampling); got != c.want {
153+ t.Errorf("limitKey(%v) = %q, want %q", c.sampling, got, c.want)
154+ }
155+ }
156+}
157+
158+func TestTruncatedToolCallNamesMaxCompletionTokens(t *testing.T) {
159+ old := config.Cfg.Sampling
160+ config.Cfg.Sampling = map[string]any{"max_completion_tokens": 2048}
161+ defer func() { config.Cfg.Sampling = old }()
162+
163+ got, ok := badToolArgs(errTruncatedArgs)
164+ if !ok || !strings.Contains(got, "max_completion_tokens (2048)") || !strings.Contains(got, "raise sampling.max_completion_tokens") {
165+ t.Errorf("badToolArgs = %q, want the limit named max_completion_tokens", got)
166+ }
167+}
modified release-notes/README.md +2 -1
@@ -4,7 +4,8 @@ One note per tag, in English (`.en.md`) and French (`.fr.md`). Rebuilt on 2026-1
44
55 | Version | Date | Summary | |
66 |---|---|---|---|
7-| [v1.0.1](v1.0.1.en.md) | not tagged yet | Messages typed during an answer wait their turn, the window shows when the model struggles and how long an answer takes, models can be downloaded at start-up and take turns in memory during a hand-off, and deciders need no boilerplate. | [FR](v1.0.1.fr.md) |
7+| [v1.0.2](v1.0.2.en.md) | not tagged yet | Use mini-me as a library from Node.js, test what a model can do with mini-me itself, and send OpenAI models only the sampling settings you wrote. | [FR](v1.0.2.fr.md) |
8+| [v1.0.1](v1.0.1.en.md) | 2026-10-26 | Messages typed during an answer wait their turn, the window shows when the model struggles and how long an answer takes, models can be downloaded at start-up and take turns in memory during a hand-off, and deciders need no boilerplate. | [FR](v1.0.1.fr.md) |
89 | [v1.0.0](v1.0.0.en.md) | 2026-10-22 | Small decision models working for the agent — skill and tool routing, command triage, checks on what it reads — plus `/shell` in the window and `mm init` files that say where each number comes from. | [FR](v1.0.0.fr.md) |
910 | [v0.9.0](v0.9.0.en.md) | 2026-10-21 | Start an agent without writing its file, run your own tools and hooks as sandboxed WebAssembly modules, and talk to any OpenAI-compatible server. | [FR](v0.9.0.fr.md) |
1011 | [v0.8.0](v0.8.0.en.md) | 2026-10-20 | Search what was said before and what is written down: recall your earlier questions in the window, search the stored sessions, and search a documentation folder. | [FR](v0.8.0.fr.md) |
@@ -4,7 +4,8 @@ One note per tag, in English (`.en.md`) and French (`.fr.md`). Rebuilt on 2026-1
4 4
5 | Version | Date | Summary | |5 | Version | Date | Summary | |
6 |---|---|---|---|6 |---|---|---|---|
7-| [v1.0.1](v1.0.1.en.md) | not tagged yet | Messages typed during an answer wait their turn, the window shows when the model struggles and how long an answer takes, models can be downloaded at start-up and take turns in memory during a hand-off, and deciders need no boilerplate. | [FR](v1.0.1.fr.md) |7+| [v1.0.2](v1.0.2.en.md) | not tagged yet | Use mini-me as a library from Node.js, test what a model can do with mini-me itself, and send OpenAI models only the sampling settings you wrote. | [FR](v1.0.2.fr.md) |
8+| [v1.0.1](v1.0.1.en.md) | 2026-10-26 | Messages typed during an answer wait their turn, the window shows when the model struggles and how long an answer takes, models can be downloaded at start-up and take turns in memory during a hand-off, and deciders need no boilerplate. | [FR](v1.0.1.fr.md) |
8 | [v1.0.0](v1.0.0.en.md) | 2026-10-22 | Small decision models working for the agent — skill and tool routing, command triage, checks on what it reads — plus `/shell` in the window and `mm init` files that say where each number comes from. | [FR](v1.0.0.fr.md) |9 | [v1.0.0](v1.0.0.en.md) | 2026-10-22 | Small decision models working for the agent — skill and tool routing, command triage, checks on what it reads — plus `/shell` in the window and `mm init` files that say where each number comes from. | [FR](v1.0.0.fr.md) |
9 | [v0.9.0](v0.9.0.en.md) | 2026-10-21 | Start an agent without writing its file, run your own tools and hooks as sandboxed WebAssembly modules, and talk to any OpenAI-compatible server. | [FR](v0.9.0.fr.md) |10 | [v0.9.0](v0.9.0.en.md) | 2026-10-21 | Start an agent without writing its file, run your own tools and hooks as sandboxed WebAssembly modules, and talk to any OpenAI-compatible server. | [FR](v0.9.0.fr.md) |
10 | [v0.8.0](v0.8.0.en.md) | 2026-10-20 | Search what was said before and what is written down: recall your earlier questions in the window, search the stored sessions, and search a documentation folder. | [FR](v0.8.0.fr.md) |11 | [v0.8.0](v0.8.0.en.md) | 2026-10-20 | Search what was said before and what is written down: recall your earlier questions in the window, search the stored sessions, and search a documentation folder. | [FR](v0.8.0.fr.md) |
added release-notes/v1.0.2.en.md +69 -0
new file mode 100644
@@ -0,0 +1,69 @@
1+# mini-me v1.0.2
2+
3+*[Version française](v1.0.2.fr.md)*
4+
5+Use mini-me as a library from Node.js, test what a model can do with mini-me itself, and send OpenAI models only the sampling settings you wrote.
6+
7+## New
8+
9+### mini-me from Node.js, over ACP (#167)
10+
11+- **[`demo.acp.sdk/nodejs/minime.js`](../demo.acp.sdk/nodejs/README.en.md)** drives `mm -acp` — the protocol an editor such as Zed speaks — from a Node.js program, with no dependency (Node ≥ 20):
12+ - `Agent.start({ config: "agent.yaml" })` starts mm on any agent file;
13+ - `complete(prompt)` returns the whole answer;
14+ - `stream(prompt)` yields the answer as it comes, with the tool calls and their results;
15+ - an `AbortSignal` cancels the turn (`session/cancel`), and leaving the loop early does too;
16+ - `onPermission(call)` answers the confirmation an agent with `yolo: false` asks for. Without it, every such call is refused.
17+- One `Agent` is one mm process and one conversation. Nothing changes in mm: tools, hooks, MCP servers and other agents all work.
18+- **Measured on llmman with Qwen3.5 4B**:
19+ - a one-sentence answer in 2.9 s;
20+ - a turn cancelled after 3.0 s with its text so far;
21+ - a refused `pwd`, for which the model invented an answer.
22+- 11 tests run without a model or mm, against a fake mm (`npm test`).
23+- The same idea for Go, Python, Rust, Swift and Nushell is described in tickets 168–172. **Not started.**
24+
25+### Test a model with mini-me itself (#174, #175)
26+
27+[`testing/`](../testing/README.en.md) holds two test sets. In each, a coordinator agent follows a skill and gives small tasks to a `candidate` agent whose model is under test. A command checks each result, and the coordinator writes a Markdown report in `reports/`.
28+
29+- **[`testing/model-check/`](../testing/model-check/README.en.md)** tests the model as a working agent with mm's built-in tools. Eight tasks:
30+ - tool calls;
31+ - exact instructions;
32+ - Go code that must run and pass `go test`;
33+ - tasks in several steps.
34+- **[`testing/function-calling/`](../testing/function-calling/README.en.md)** tests function calling alone. The candidate's only tools are six tools declared in its agent file (`tools:`), and every call is logged and checked. Eight requests:
35+ - string, enum, number and boolean arguments;
36+ - chained calls;
37+ - two calls at once;
38+ - no call when no tool fits;
39+ - no invented arguments.
40+- **The bench script drives the loop**, not the coordinator's memory. The coordinator repeats `./bench.sh next`, then sends the printed prompt, and each `next` checks the test just run first. Each piece of the design comes from a failed run: a 12B coordinator skipped every check once, rewrote a path with underscores, and recorded a result under the placeholder `test`.
41+- **`../common/run.sh`** runs a set and writes the report from the recorded results if the coordinator stops early. Tests never checked show as NOT RUN.
42+- **First results:**
43+ - function-calling, Gemma 4 12B checking Luciole 1B: **5 of 8**, 13 min 04 s. Luciole varies from one run to the next, so one run is a sample.
44+ - model-check: no run has gone to the end yet. The latest, Luciole 8B checked by Gemma 4 E4B, stopped after a timeout on T2.
45+
46+## Fixed
47+
48+- **`provider: openai` sent sampling keys the file did not write (#176).** A file from `mm init` for `gpt-5.4-mini` failed with `400 Unsupported parameter: 'max_tokens' … Use 'max_completion_tokens' instead`. The file was right. mm read its `sampling:` block on top of its own defaults (`temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`), so `max_tokens` and `top_p` went along unasked.
49+ - For `provider: openai`, mm now sends exactly the block written in the file.
50+ - The context compression's summary request and the message about a cut-off tool call now use `max_completion_tokens` when the file names the limit that way.
51+ - Checked against a local server that records the requests. **Not checked against the OpenAI API itself.**
52+
53+## Demos
54+
55+- [`demos/coding-helpers-unload-option/`](../demos/coding-helpers-unload-option/README.en.md) has a diagram of how `unloadModels` works: two models taking turns in memory during a hand-off.
56+
57+## Documentation
58+
59+- **Configuration reference** ([EN](../docs/en/reference/configuration.md), FR): the `sampling` row says how the file's block combines with mm's defaults, the `provider: openai` exception, and `max_completion_tokens`.
60+- **[How to run with an OpenAI-compatible server](../docs/en/how-to/run-with-an-openai-compatible-server.md)** (EN, FR): what to write in `sampling` for the OpenAI API.
61+- READMEs in English and French for `demo.acp.sdk/` and for each test set of `testing/`.
62+
63+## Upgrading
64+
65+- **Only `provider: openai` changes.** Its request no longer gets mm's default `temperature: 0.0`, `top_p: 0.9` and `max_tokens: 4096`: only the keys written under `sampling:` are sent, and with no block, none.
66+ - On the OpenAI API, this is the fix.
67+ - On another server reached through `provider: openai` (an OpenAI-compatible server), the server's own defaults now apply to any key the file leaves out. Write `temperature`, `top_p` or `max_tokens` in the file to keep the previous values.
68+- Every other provider works exactly as before: a default the file does not name is still sent.
69+- The `mm init` note for OpenAI models changed. Files written before still load as they are.
new file mode 100644
@@ -0,0 +1,69 @@
1+# mini-me v1.0.2
2+
3+*[Version française](v1.0.2.fr.md)*
4+
5+Use mini-me as a library from Node.js, test what a model can do with mini-me itself, and send OpenAI models only the sampling settings you wrote.
6+
7+## New
8+
9+### mini-me from Node.js, over ACP (#167)
10+
11+- **[`demo.acp.sdk/nodejs/minime.js`](../demo.acp.sdk/nodejs/README.en.md)** drives `mm -acp` — the protocol an editor such as Zed speaks — from a Node.js program, with no dependency (Node ≥ 20):
12+ - `Agent.start({ config: "agent.yaml" })` starts mm on any agent file;
13+ - `complete(prompt)` returns the whole answer;
14+ - `stream(prompt)` yields the answer as it comes, with the tool calls and their results;
15+ - an `AbortSignal` cancels the turn (`session/cancel`), and leaving the loop early does too;
16+ - `onPermission(call)` answers the confirmation an agent with `yolo: false` asks for. Without it, every such call is refused.
17+- One `Agent` is one mm process and one conversation. Nothing changes in mm: tools, hooks, MCP servers and other agents all work.
18+- **Measured on llmman with Qwen3.5 4B**:
19+ - a one-sentence answer in 2.9 s;
20+ - a turn cancelled after 3.0 s with its text so far;
21+ - a refused `pwd`, for which the model invented an answer.
22+- 11 tests run without a model or mm, against a fake mm (`npm test`).
23+- The same idea for Go, Python, Rust, Swift and Nushell is described in tickets 168–172. **Not started.**
24+
25+### Test a model with mini-me itself (#174, #175)
26+
27+[`testing/`](../testing/README.en.md) holds two test sets. In each, a coordinator agent follows a skill and gives small tasks to a `candidate` agent whose model is under test. A command checks each result, and the coordinator writes a Markdown report in `reports/`.
28+
29+- **[`testing/model-check/`](../testing/model-check/README.en.md)** tests the model as a working agent with mm's built-in tools. Eight tasks:
30+ - tool calls;
31+ - exact instructions;
32+ - Go code that must run and pass `go test`;
33+ - tasks in several steps.
34+- **[`testing/function-calling/`](../testing/function-calling/README.en.md)** tests function calling alone. The candidate's only tools are six tools declared in its agent file (`tools:`), and every call is logged and checked. Eight requests:
35+ - string, enum, number and boolean arguments;
36+ - chained calls;
37+ - two calls at once;
38+ - no call when no tool fits;
39+ - no invented arguments.
40+- **The bench script drives the loop**, not the coordinator's memory. The coordinator repeats `./bench.sh next`, then sends the printed prompt, and each `next` checks the test just run first. Each piece of the design comes from a failed run: a 12B coordinator skipped every check once, rewrote a path with underscores, and recorded a result under the placeholder `test`.
41+- **`../common/run.sh`** runs a set and writes the report from the recorded results if the coordinator stops early. Tests never checked show as NOT RUN.
42+- **First results:**
43+ - function-calling, Gemma 4 12B checking Luciole 1B: **5 of 8**, 13 min 04 s. Luciole varies from one run to the next, so one run is a sample.
44+ - model-check: no run has gone to the end yet. The latest, Luciole 8B checked by Gemma 4 E4B, stopped after a timeout on T2.
45+
46+## Fixed
47+
48+- **`provider: openai` sent sampling keys the file did not write (#176).** A file from `mm init` for `gpt-5.4-mini` failed with `400 Unsupported parameter: 'max_tokens' … Use 'max_completion_tokens' instead`. The file was right. mm read its `sampling:` block on top of its own defaults (`temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`), so `max_tokens` and `top_p` went along unasked.
49+ - For `provider: openai`, mm now sends exactly the block written in the file.
50+ - The context compression's summary request and the message about a cut-off tool call now use `max_completion_tokens` when the file names the limit that way.
51+ - Checked against a local server that records the requests. **Not checked against the OpenAI API itself.**
52+
53+## Demos
54+
55+- [`demos/coding-helpers-unload-option/`](../demos/coding-helpers-unload-option/README.en.md) has a diagram of how `unloadModels` works: two models taking turns in memory during a hand-off.
56+
57+## Documentation
58+
59+- **Configuration reference** ([EN](../docs/en/reference/configuration.md), FR): the `sampling` row says how the file's block combines with mm's defaults, the `provider: openai` exception, and `max_completion_tokens`.
60+- **[How to run with an OpenAI-compatible server](../docs/en/how-to/run-with-an-openai-compatible-server.md)** (EN, FR): what to write in `sampling` for the OpenAI API.
61+- READMEs in English and French for `demo.acp.sdk/` and for each test set of `testing/`.
62+
63+## Upgrading
64+
65+- **Only `provider: openai` changes.** Its request no longer gets mm's default `temperature: 0.0`, `top_p: 0.9` and `max_tokens: 4096`: only the keys written under `sampling:` are sent, and with no block, none.
66+ - On the OpenAI API, this is the fix.
67+ - On another server reached through `provider: openai` (an OpenAI-compatible server), the server's own defaults now apply to any key the file leaves out. Write `temperature`, `top_p` or `max_tokens` in the file to keep the previous values.
68+- Every other provider works exactly as before: a default the file does not name is still sent.
69+- The `mm init` note for OpenAI models changed. Files written before still load as they are.
added release-notes/v1.0.2.fr.md +69 -0
new file mode 100644
@@ -0,0 +1,69 @@
1+# mini-me v1.0.2
2+
3+*[English version](v1.0.2.en.md)*
4+
5+Utiliser mini-me comme bibliothèque depuis Node.js, tester ce dont un modèle est capable avec mini-me lui-même, et n'envoyer aux modèles OpenAI que les réglages de génération que vous avez écrits.
6+
7+## Nouveautés
8+
9+### mini-me depuis Node.js, par ACP (#167)
10+
11+- **[`demo.acp.sdk/nodejs/minime.js`](../demo.acp.sdk/nodejs/README.fr.md)** pilote `mm -acp` depuis un programme Node.js, sans dépendance (Node ≥ 20). C'est le protocole que parle un éditeur comme Zed.
12+ - `Agent.start({ config: "agent.yaml" })` lance mm sur n'importe quel fichier d'agent ;
13+ - `complete(prompt)` renvoie la réponse entière ;
14+ - `stream(prompt)` la donne au fil de l'eau, avec les appels d'outils et leurs résultats ;
15+ - un `AbortSignal` annule le tour (`session/cancel`), et sortir de la boucle avant la fin aussi ;
16+ - `onPermission(call)` répond à la confirmation que demande un agent en `yolo: false`. Sans lui, tous ces appels sont refusés.
17+- Un `Agent` correspond à un processus mm et à une conversation. Rien ne change dans mm : outils, hooks, serveurs MCP et autres agents fonctionnent tous.
18+- **Mesuré sur llmman avec Qwen3.5 4B** :
19+ - une réponse en une phrase en 2,9 s ;
20+ - un tour annulé au bout de 3,0 s, avec le texte déjà reçu ;
21+ - un `pwd` refusé, pour lequel le modèle a inventé une réponse.
22+- 11 tests tournent sans modèle ni mm, contre un faux mm (`npm test`).
23+- La même idée pour Go, Python, Rust, Swift et Nushell est décrite dans les tickets 168 à 172. **Pas commencée.**
24+
25+### Tester un modèle avec mini-me lui-même (#174, #175)
26+
27+[`testing/`](../testing/README.fr.md) contient deux jeux de tests. Dans chacun, un agent coordinateur suit une skill et donne de petites tâches à un agent `candidate` dont on teste le modèle. Une commande vérifie chaque résultat, et le coordinateur écrit un rapport Markdown dans `reports/`.
28+
29+- **[`testing/model-check/`](../testing/model-check/README.fr.md)** teste le modèle comme agent de travail, avec les outils intégrés de mm. Huit tâches :
30+ - des appels d'outils ;
31+ - des consignes exactes ;
32+ - du code Go qui doit tourner et passer `go test` ;
33+ - des tâches en plusieurs étapes.
34+- **[`testing/function-calling/`](../testing/function-calling/README.fr.md)** teste l'appel d'outils seul. Les seuls outils du candidat sont six outils déclarés dans son fichier d'agent (`tools:`), et chaque appel est enregistré et vérifié. Huit requêtes :
35+ - des arguments chaîne, enum, nombre et booléen ;
36+ - des appels enchaînés ;
37+ - deux appels à la fois ;
38+ - aucun appel quand aucun outil ne convient ;
39+ - aucun argument inventé.
40+- **C'est le script du banc qui mène la boucle**, pas la mémoire du coordinateur. Le coordinateur répète `./bench.sh next`, puis envoie le prompt affiché, et chaque `next` vérifie d'abord le test qui vient de tourner. Chaque élément de cette conception vient d'un passage raté : un coordinateur de 12B a une fois sauté toutes les vérifications, réécrit un chemin avec des tirets bas, et enregistré un résultat sous le nom `test`.
41+- **`../common/run.sh`** lance un jeu et écrit le rapport à partir des résultats enregistrés si le coordinateur s'arrête avant la fin. Les tests jamais vérifiés apparaissent en NOT RUN.
42+- **Premiers résultats :**
43+ - function-calling, Gemma 4 12B qui vérifie Luciole 1B : **5 sur 8**, en 13 min 04 s. Luciole varie d'un passage à l'autre : un passage est un échantillon.
44+ - model-check : aucun passage n'est encore allé au bout. Le dernier, Luciole 8B vérifié par Gemma 4 E4B, s'est arrêté sur un dépassement de délai à T2.
45+
46+## Corrections
47+
48+- **`provider: openai` envoyait des réglages que le fichier n'écrivait pas (#176).** Un fichier écrit par `mm init` pour `gpt-5.4-mini` échouait avec `400 Unsupported parameter: 'max_tokens' … Use 'max_completion_tokens' instead`. Le fichier était juste. mm lisait son bloc `sampling:` par-dessus ses propres valeurs par défaut (`temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`), si bien que `max_tokens` et `top_p` partaient sans avoir été demandés.
49+ - Pour `provider: openai`, mm envoie maintenant exactement le bloc écrit dans le fichier.
50+ - La requête de résumé de la compression du contexte et le message d'appel d'outil coupé utilisent `max_completion_tokens` quand le fichier nomme ainsi la limite.
51+ - Vérifié contre un serveur local qui enregistre les requêtes. **Pas vérifié contre l'API OpenAI elle-même.**
52+
53+## Démos
54+
55+- [`demos/coding-helpers-unload-option/`](../demos/coding-helpers-unload-option/README.fr.md) a un diagramme du fonctionnement de `unloadModels` : deux modèles qui se relaient en mémoire pendant un passage de relais.
56+
57+## Documentation
58+
59+- **Référence de la configuration** ([FR](../docs/fr/reference/configuration.md), EN) : la ligne `sampling` explique comment le bloc du fichier se combine avec les valeurs par défaut de mm, l'exception de `provider: openai`, et `max_completion_tokens`.
60+- **[Utiliser un serveur compatible OpenAI](../docs/fr/how-to/run-with-an-openai-compatible-server.md)** (FR, EN) : quoi écrire dans `sampling` pour l'API OpenAI.
61+- Des README en anglais et en français pour `demo.acp.sdk/` et pour chaque jeu de tests de `testing/`.
62+
63+## Mise à jour
64+
65+- **Seul `provider: openai` change.** Sa requête ne reçoit plus les valeurs par défaut de mm (`temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`) : seules les clés écrites sous `sampling:` partent, et aucune s'il n'y a pas de bloc.
66+ - Sur l'API OpenAI, c'est la correction.
67+ - Sur un autre serveur atteint par `provider: openai` (un serveur compatible OpenAI), ce sont maintenant les valeurs par défaut du serveur qui s'appliquent pour les clés absentes du fichier. Écrivez `temperature`, `top_p` ou `max_tokens` dans le fichier pour garder les valeurs d'avant.
68+- Les autres fournisseurs fonctionnent exactement comme avant : une valeur par défaut que le fichier ne nomme pas est toujours envoyée.
69+- La note de `mm init` pour les modèles OpenAI a changé. Les fichiers écrits avant se chargent tels quels.
new file mode 100644
@@ -0,0 +1,69 @@
1+# mini-me v1.0.2
2+
3+*[English version](v1.0.2.en.md)*
4+
5+Utiliser mini-me comme bibliothèque depuis Node.js, tester ce dont un modèle est capable avec mini-me lui-même, et n'envoyer aux modèles OpenAI que les réglages de génération que vous avez écrits.
6+
7+## Nouveautés
8+
9+### mini-me depuis Node.js, par ACP (#167)
10+
11+- **[`demo.acp.sdk/nodejs/minime.js`](../demo.acp.sdk/nodejs/README.fr.md)** pilote `mm -acp` depuis un programme Node.js, sans dépendance (Node ≥ 20). C'est le protocole que parle un éditeur comme Zed.
12+ - `Agent.start({ config: "agent.yaml" })` lance mm sur n'importe quel fichier d'agent ;
13+ - `complete(prompt)` renvoie la réponse entière ;
14+ - `stream(prompt)` la donne au fil de l'eau, avec les appels d'outils et leurs résultats ;
15+ - un `AbortSignal` annule le tour (`session/cancel`), et sortir de la boucle avant la fin aussi ;
16+ - `onPermission(call)` répond à la confirmation que demande un agent en `yolo: false`. Sans lui, tous ces appels sont refusés.
17+- Un `Agent` correspond à un processus mm et à une conversation. Rien ne change dans mm : outils, hooks, serveurs MCP et autres agents fonctionnent tous.
18+- **Mesuré sur llmman avec Qwen3.5 4B** :
19+ - une réponse en une phrase en 2,9 s ;
20+ - un tour annulé au bout de 3,0 s, avec le texte déjà reçu ;
21+ - un `pwd` refusé, pour lequel le modèle a inventé une réponse.
22+- 11 tests tournent sans modèle ni mm, contre un faux mm (`npm test`).
23+- La même idée pour Go, Python, Rust, Swift et Nushell est décrite dans les tickets 168 à 172. **Pas commencée.**
24+
25+### Tester un modèle avec mini-me lui-même (#174, #175)
26+
27+[`testing/`](../testing/README.fr.md) contient deux jeux de tests. Dans chacun, un agent coordinateur suit une skill et donne de petites tâches à un agent `candidate` dont on teste le modèle. Une commande vérifie chaque résultat, et le coordinateur écrit un rapport Markdown dans `reports/`.
28+
29+- **[`testing/model-check/`](../testing/model-check/README.fr.md)** teste le modèle comme agent de travail, avec les outils intégrés de mm. Huit tâches :
30+ - des appels d'outils ;
31+ - des consignes exactes ;
32+ - du code Go qui doit tourner et passer `go test` ;
33+ - des tâches en plusieurs étapes.
34+- **[`testing/function-calling/`](../testing/function-calling/README.fr.md)** teste l'appel d'outils seul. Les seuls outils du candidat sont six outils déclarés dans son fichier d'agent (`tools:`), et chaque appel est enregistré et vérifié. Huit requêtes :
35+ - des arguments chaîne, enum, nombre et booléen ;
36+ - des appels enchaînés ;
37+ - deux appels à la fois ;
38+ - aucun appel quand aucun outil ne convient ;
39+ - aucun argument inventé.
40+- **C'est le script du banc qui mène la boucle**, pas la mémoire du coordinateur. Le coordinateur répète `./bench.sh next`, puis envoie le prompt affiché, et chaque `next` vérifie d'abord le test qui vient de tourner. Chaque élément de cette conception vient d'un passage raté : un coordinateur de 12B a une fois sauté toutes les vérifications, réécrit un chemin avec des tirets bas, et enregistré un résultat sous le nom `test`.
41+- **`../common/run.sh`** lance un jeu et écrit le rapport à partir des résultats enregistrés si le coordinateur s'arrête avant la fin. Les tests jamais vérifiés apparaissent en NOT RUN.
42+- **Premiers résultats :**
43+ - function-calling, Gemma 4 12B qui vérifie Luciole 1B : **5 sur 8**, en 13 min 04 s. Luciole varie d'un passage à l'autre : un passage est un échantillon.
44+ - model-check : aucun passage n'est encore allé au bout. Le dernier, Luciole 8B vérifié par Gemma 4 E4B, s'est arrêté sur un dépassement de délai à T2.
45+
46+## Corrections
47+
48+- **`provider: openai` envoyait des réglages que le fichier n'écrivait pas (#176).** Un fichier écrit par `mm init` pour `gpt-5.4-mini` échouait avec `400 Unsupported parameter: 'max_tokens' … Use 'max_completion_tokens' instead`. Le fichier était juste. mm lisait son bloc `sampling:` par-dessus ses propres valeurs par défaut (`temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`), si bien que `max_tokens` et `top_p` partaient sans avoir été demandés.
49+ - Pour `provider: openai`, mm envoie maintenant exactement le bloc écrit dans le fichier.
50+ - La requête de résumé de la compression du contexte et le message d'appel d'outil coupé utilisent `max_completion_tokens` quand le fichier nomme ainsi la limite.
51+ - Vérifié contre un serveur local qui enregistre les requêtes. **Pas vérifié contre l'API OpenAI elle-même.**
52+
53+## Démos
54+
55+- [`demos/coding-helpers-unload-option/`](../demos/coding-helpers-unload-option/README.fr.md) a un diagramme du fonctionnement de `unloadModels` : deux modèles qui se relaient en mémoire pendant un passage de relais.
56+
57+## Documentation
58+
59+- **Référence de la configuration** ([FR](../docs/fr/reference/configuration.md), EN) : la ligne `sampling` explique comment le bloc du fichier se combine avec les valeurs par défaut de mm, l'exception de `provider: openai`, et `max_completion_tokens`.
60+- **[Utiliser un serveur compatible OpenAI](../docs/fr/how-to/run-with-an-openai-compatible-server.md)** (FR, EN) : quoi écrire dans `sampling` pour l'API OpenAI.
61+- Des README en anglais et en français pour `demo.acp.sdk/` et pour chaque jeu de tests de `testing/`.
62+
63+## Mise à jour
64+
65+- **Seul `provider: openai` change.** Sa requête ne reçoit plus les valeurs par défaut de mm (`temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096`) : seules les clés écrites sous `sampling:` partent, et aucune s'il n'y a pas de bloc.
66+ - Sur l'API OpenAI, c'est la correction.
67+ - Sur un autre serveur atteint par `provider: openai` (un serveur compatible OpenAI), ce sont maintenant les valeurs par défaut du serveur qui s'appliquent pour les clés absentes du fichier. Écrivez `temperature`, `top_p` ou `max_tokens` dans le fichier pour garder les valeurs d'avant.
68+- Les autres fournisseurs fonctionnent exactement comme avant : une valeur par défaut que le fichier ne nomme pas est toujours envoyée.
69+- La note de `mm init` pour les modèles OpenAI a changé. Les fichiers écrits avant se chargent tels quels.
modified testing/common/bench-lib.sh +2 -2
@@ -25,8 +25,8 @@ RUN="$ROOT/reports/.run"
2525 CURRENT="$ROOT/reports/.current"
2626 TAB="$(printf '\t')"
2727
28-model_of() { # agent file
29- sed -n 's/^model: *//p' "$1" | head -1
28+model_of() { # agent file — the value of `model:`, without quotes or trailing spaces
29+ sed -n 's/^model:[[:space:]]*//p' "$1" | head -1 | sed 's/[[:space:]]*$//; s/^"\(.*\)"$/\1/'
3030 }
3131
3232 field() { # test column (1-based)
@@ -25,8 +25,8 @@ RUN="$ROOT/reports/.run"
25 CURRENT="$ROOT/reports/.current"25 CURRENT="$ROOT/reports/.current"
26 TAB="$(printf '\t')"26 TAB="$(printf '\t')"
27 27
28-model_of() { # agent file28+model_of() { # agent file — the value of `model:`, without quotes or trailing spaces
29- sed -n 's/^model: *//p' "$1" | head -129+ sed -n 's/^model:[[:space:]]*//p' "$1" | head -1 | sed 's/[[:space:]]*$//; s/^"\(.*\)"$/\1/'
30 }30 }
31 31
32 field() { # test column (1-based)32 field() { # test column (1-based)
modified testing/function-calling/.gitignore +1 -0
@@ -4,3 +4,4 @@ work/
44 reports/.results.tsv
55 reports/.notes.md
66 reports/.run
7+reports/.current
@@ -4,3 +4,4 @@ work/
4 reports/.results.tsv4 reports/.results.tsv
5 reports/.notes.md5 reports/.notes.md
6 reports/.run6 reports/.run
7+reports/.current
modified testing/model-check/.gitignore +1 -0
@@ -4,3 +4,4 @@ work/
44 reports/.results.tsv
55 reports/.notes.md
66 reports/.run
7+reports/.current
@@ -4,3 +4,4 @@ work/
4 reports/.results.tsv4 reports/.results.tsv
5 reports/.notes.md5 reports/.notes.md
6 reports/.run6 reports/.run
7+reports/.current