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

Merge feature/genkit-v1.13.1 into main

rickub committed 2026-10-06T10:21:29Z Browse files
5f0c1e0 parents: 58ba549 13af864
modified .tickets/issues/0067-enable-disable-reasoning-mode.yaml +13 -0
@@ -11,6 +11,7 @@ labels:
1111 - kind::feature
1212 related:
1313 - 62
14+ - 76
1415 body: |
1516 Question (user, 2026-10-06): can mm turn a model's reasoning mode on or
1617 off?
@@ -193,3 +194,15 @@ comments:
193194 `reasoning_effort` (Ollama) — per-server spelling left to the user.
194195 A `thinking: false` key is still worth it for one spelling across
195196 providers (it would reuse `withoutReasoning`) and for `/think`.
197+ - id: 2
198+ author:
199+ name: claude
200+ email: noreply@anthropic.com
201+ createdAt: 2026-10-06T16:00:00.000Z
202+ body: |
203+ Ticket 76 is done on branch `feature/genkit-v1.13.1`: once merged,
204+ `sampling.chat_template_kwargs: {enable_thinking: false}` (dmr,
205+ llamacpp, llmman) and `sampling.reasoning_effort: none` (ollama) turn
206+ the reasoning off for every turn — documented in reference/configuration
207+ and explanation/thinking-models (EN+FR). What this ticket still adds:
208+ one provider-independent `thinking: false` key and `/think`.
@@ -11,6 +11,7 @@ labels:
11 - kind::feature11 - kind::feature
12 related:12 related:
13 - 6213 - 62
14+ - 76
14 body: |15 body: |
15 Question (user, 2026-10-06): can mm turn a model's reasoning mode on or16 Question (user, 2026-10-06): can mm turn a model's reasoning mode on or
16 off?17 off?
@@ -193,3 +194,15 @@ comments:
193 `reasoning_effort` (Ollama) — per-server spelling left to the user.194 `reasoning_effort` (Ollama) — per-server spelling left to the user.
194 A `thinking: false` key is still worth it for one spelling across195 A `thinking: false` key is still worth it for one spelling across
195 providers (it would reuse `withoutReasoning`) and for `/think`.196 providers (it would reuse `withoutReasoning`) and for `/think`.
197+ - id: 2
198+ author:
199+ name: claude
200+ email: noreply@anthropic.com
201+ createdAt: 2026-10-06T16:00:00.000Z
202+ body: |
203+ Ticket 76 is done on branch `feature/genkit-v1.13.1`: once merged,
204+ `sampling.chat_template_kwargs: {enable_thinking: false}` (dmr,
205+ llamacpp, llmman) and `sampling.reasoning_effort: none` (ollama) turn
206+ the reasoning off for every turn — documented in reference/configuration
207+ and explanation/thinking-models (EN+FR). What this ticket still adds:
208+ one provider-independent `thinking: false` key and `/think`.
added .tickets/issues/0076-upgrade-genkit-go-to-v1-13-1.yaml +207 -0
new file mode 100644
@@ -0,0 +1,207 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 76
3+title: Upgrade Genkit Go to v1.13.1
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-06T14:30:00.000Z
9+updatedAt: 2026-10-06T16:00:00.000Z
10+labels:
11+ - kind::chore
12+ - priority::medium
13+epic: Providers (LLM Engine)
14+related:
15+ - 67
16+body: |
17+ Bump `github.com/firebase/genkit/go` from v1.10.0 (current go.mod) to
18+ v1.13.1 (latest, 2026-09-03).
19+
20+ ## Why
21+
22+ Since v1.12.0, compat_oai's `ModelGenerator.WithConfig` keeps the
23+ `sampling` keys `openai.ChatCompletionNewParams` does not declare and
24+ sends them as JSON extras (`SetExtraFields`, plugins/compat_oai/
25+ generate.go ~l.231). In v1.10.0 they are silently dropped (JSON round
26+ trip through `base.MapToStruct`). That makes provider-specific keys
27+ usable from the YAML, first of all
28+ `chat_template_kwargs: {enable_thinking: false}` for the llama.cpp
29+ family (dmr, llamacpp, llmman) — see ticket 67.
30+
31+ ## The trap (found in a scratch copy on 2026-10-06)
32+
33+ Bumping alone BREAKS requests that carry an unknown key:
34+
35+ - In v1.13.1 `OpenAICompatible.DefineModel` is deprecated and returns an
36+ UNREGISTERED model. mm discards its result
37+ (internal/engine/openai_compat.go:189), so the call is a no-op.
38+ - The model actually used is then resolved on the fly by the plugin
39+ (`ResolveAction` -> `newSDKModel`), with the strict reflected SDK config
40+ schema: `config: Additional property chat_template_kwargs is not
41+ allowed`, and the whole request fails (v1.10.0 only dropped the key).
42+ - `ModelOptions.ConfigSchema` does not help: it is set on the model
43+ nobody registered.
44+
45+ Fix proven in the scratch copy:
46+
47+ genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{
48+ Label: b.Model,
49+ Supports: &compat_oai.Multimodal,
50+ }).(*ai.ModelAction))
51+
52+ `DefineModel`'s model takes its config untyped: no validation, unknown
53+ keys ride to the wire. Alternative to evaluate: the non-deprecated
54+ `client.NewModel(id, opts)` + `genkit.RegisterAction`, which validates
55+ against the strict schema unless an explicit permissive `ConfigSchema`
56+ is given (it would reject unknown keys otherwise).
57+
58+ ## Measured in the scratch copy
59+
60+ - `go get github.com/firebase/genkit/go@v1.13.1` + `go mod tidy`: go
61+ stays `1.26.0`; also bumps google/dotprompt, invopop/jsonschema
62+ v0.14.0, goccy/go-yaml v1.19.2, buger/jsonparser v1.1.2, adds
63+ go.yaml.in/yaml/v4; openai-go stays v1.8.2.
64+ - `go build`, `go vet`, `go test ./...` green (the skills shipped-YAML
65+ test failed only because the copy had no YAML files).
66+ - Recording server: the request carries
67+ `"chat_template_kwargs": {"enable_thinking": false}` AND mm's own
68+ middleware fields (`return_progress`, `stream_options`).
69+ - Live, DMR qwen3.5-4b, `-p "What is 17*23?"`, sampling
70+ `chat_template_kwargs.enable_thinking: false`: v1.10.0 = 4 s, a 💭
71+ reasoning block, 391; v1.13.1 + RegisterAction = under 1 s, no
72+ reasoning, 391.
73+
74+ ## Side effects to handle
75+
76+ - Genkit now logs `INFO Genkit initialized env=prod plugins=[dmr] …` on
77+ stderr at start-up: silence it (slog level/handler), like the ACP SDK's
78+ "connection closed" log in internal/ask.
79+ - Not re-checked after the bump, each relying on Genkit internals:
80+ - internal/engine/reasoning.go — the tap that reads
81+ `reasoning_content` off the wire because v1.10.0 dropped it; v1.13.1
82+ reads `reasoning_content` / `reasoning` itself (generate.go ~l.548,
83+ ~l.824): possible double display, or the tap becomes redundant.
84+ - internal/engine/toolargs.go — tool-argument decoding workarounds.
85+ - internal/engine/usage.go — the comment saying sampling cannot carry
86+ the no-reasoning field becomes false (summary path can stay as is).
87+ - tool order (ticket 53, tests/tool-order), the watchdog, the
88+ non-streaming retry, `max_tokens` cut-off handling.
89+ - ollama and llmman providers, ACP and window modes, MCP tools.
90+ - Docs: providers / thinking-models explanations, configuration
91+ reference (`sampling` passes extra keys), INSTALL/README if they quote
92+ the Genkit version; ebook chapter on the engine if it shows
93+ `DefineModel`.
94+tasks:
95+ - id: 1
96+ title: "Branch from main after feature/yolo is merged; go get github.com/firebase/genkit/go@v1.13.1 + go mod tidy"
97+ state: closed
98+ priority: none
99+ author:
100+ name: k33g
101+ email: ph.charriere@gmail.com
102+ createdAt: 2026-10-06T14:30:00.000Z
103+ updatedAt: 2026-10-06T16:00:00.000Z
104+ - id: 2
105+ title: "Failing test first: a sampling key unknown to the OpenAI SDK (chat_template_kwargs) reaches the request body (httptest server)"
106+ state: closed
107+ priority: none
108+ author:
109+ name: k33g
110+ email: ph.charriere@gmail.com
111+ createdAt: 2026-10-06T14:30:00.000Z
112+ updatedAt: 2026-10-06T16:00:00.000Z
113+ - id: 3
114+ title: "Register the model explicitly (RegisterAction + DefineModel, or NewModel with a permissive ConfigSchema) — decide which"
115+ state: closed
116+ priority: none
117+ author:
118+ name: k33g
119+ email: ph.charriere@gmail.com
120+ createdAt: 2026-10-06T14:30:00.000Z
121+ updatedAt: 2026-10-06T16:00:00.000Z
122+ - id: 4
123+ title: "Re-check reasoning.go (Genkit now reads reasoning_content itself), toolargs.go, usage.go against v1.13.1; remove what became redundant"
124+ state: closed
125+ priority: none
126+ author:
127+ name: k33g
128+ email: ph.charriere@gmail.com
129+ createdAt: 2026-10-06T14:30:00.000Z
130+ updatedAt: 2026-10-06T16:00:00.000Z
131+ - id: 5
132+ title: "Silence Genkit's 'INFO Genkit initialized' start-up log"
133+ state: closed
134+ priority: none
135+ author:
136+ name: k33g
137+ email: ph.charriere@gmail.com
138+ createdAt: 2026-10-06T14:30:00.000Z
139+ updatedAt: 2026-10-06T16:00:00.000Z
140+ - id: 6
141+ title: "Live checks: dmr, llamacpp, llmman, ollama; line, window, ACP; tools, MCP, /compact, tool-order proxy (tests/tool-order)"
142+ state: open
143+ priority: none
144+ author:
145+ name: k33g
146+ email: ph.charriere@gmail.com
147+ createdAt: 2026-10-06T14:30:00.000Z
148+ updatedAt: 2026-10-06T16:00:00.000Z
149+ - id: 7
150+ title: "Docs EN+FR: sampling extra keys (configuration reference), providers / thinking-models explanations; .memory summary (Genkit version)"
151+ state: closed
152+ priority: none
153+ author:
154+ name: k33g
155+ email: ph.charriere@gmail.com
156+ createdAt: 2026-10-06T14:30:00.000Z
157+ updatedAt: 2026-10-06T16:00:00.000Z
158+comments:
159+ - id: 1
160+ author:
161+ name: claude
162+ email: noreply@anthropic.com
163+ createdAt: 2026-10-06T16:00:00.000Z
164+ body: |
165+ **Done on branch `feature/genkit-v1.13.1` (from main 58ba549), uncommitted.**
166+
167+ - go.mod: genkit v1.13.1 (+ the indirect bumps listed in the body;
168+ mailru/easyjson and wk8/go-ordered-map dropped, pb33f/ordered-map
169+ and go.yaml.in/yaml/v4 added).
170+ - Task 3 decision: `genkit.RegisterAction(g, client.DefineModel(...))`
171+ (`//nolint:staticcheck`, explained). NewModel rejected: it converts
172+ the map config to `openai.ChatCompletionNewParams` BEFORE the model
173+ function, so the extras are lost (measured round trip).
174+ - TWO regressions the bump brought, found by tests/live, both fixed:
175+ 1. Genkit now hands reasoning-only chunks to the streaming callback;
176+ mm's `emit("")` closed the 💭 block on each → one block per token
177+ in the line mode (live, qwen3.5-4b). Fix: `onlyReasoning(chunk)`
178+ skips them (the tap still displays). Test needs a FLUSHED stream
179+ (`TestReasoningStreamsAsOneBlock`): written at once, the tap reads
180+ everything before the callback and the bug hides.
181+ 2. Genkit now appends the reasoning as a reasoning PART of the final
182+ message → it would enter mm's history/session file and go back to
183+ the server as `reasoning_content`. Fix: `dropReasoning(resp)` in
184+ the WrapModel hook (covers tool-loop calls too).
185+ `TestReasoningStaysOutOfTheHistory`.
186+ - Genkit's `INFO Genkit initialized` silenced: `quietGenkit()` =
187+ `logger.SetLevel(slog.LevelWarn)` before Init (GENKIT_LOG_LEVEL wins).
188+ - toolargs.go: Genkit still says "could not parse tool args" — unchanged.
189+ usage.go: comment corrected. reasoning.go tap kept (display +
190+ watchdog heartbeat, one road).
191+ - Tests: `TestSamplingKeysUnknownToTheSDKReachTheServer` (failed first
192+ with the schema error), the two reasoning tests, `TestOpenKeepsGenkitQuiet`.
193+ `go test ./...` green, vet clean.
194+ - Live: DMR mellum2 + bash; DMR qwen3.5-4b thinking shown as ONE block,
195+ `chat_template_kwargs` off via sampling (no reasoning, 391);
196+ two-turn REPL with sessions: session file holds text parts only;
197+ llmman Qwen3.5-4B thinking + bash + second thought block; ACP
198+ (scripted client): stdout pure JSON-RPC, 149 thought chunks forming
199+ one coherent text, answer 391, no INFO line on stderr; tests/tool-order
200+ session.py two ACP turns end_turn; /compact with summaryMaxTokens 300
201+ succeeds.
202+ - NOT checked live: ollama and llamacpp servers (not running on the
203+ host), the window mode, the tool-order proxy measurement, MCP tools.
204+ - Docs EN+FR: reference/configuration (`sampling` extra keys),
205+ explanation/thinking-models (Genkit 1.13 delivery, how to turn
206+ reasoning off through sampling).
207+
new file mode 100644
@@ -0,0 +1,207 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 76
3+title: Upgrade Genkit Go to v1.13.1
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-06T14:30:00.000Z
9+updatedAt: 2026-10-06T16:00:00.000Z
10+labels:
11+ - kind::chore
12+ - priority::medium
13+epic: Providers (LLM Engine)
14+related:
15+ - 67
16+body: |
17+ Bump `github.com/firebase/genkit/go` from v1.10.0 (current go.mod) to
18+ v1.13.1 (latest, 2026-09-03).
19+
20+ ## Why
21+
22+ Since v1.12.0, compat_oai's `ModelGenerator.WithConfig` keeps the
23+ `sampling` keys `openai.ChatCompletionNewParams` does not declare and
24+ sends them as JSON extras (`SetExtraFields`, plugins/compat_oai/
25+ generate.go ~l.231). In v1.10.0 they are silently dropped (JSON round
26+ trip through `base.MapToStruct`). That makes provider-specific keys
27+ usable from the YAML, first of all
28+ `chat_template_kwargs: {enable_thinking: false}` for the llama.cpp
29+ family (dmr, llamacpp, llmman) — see ticket 67.
30+
31+ ## The trap (found in a scratch copy on 2026-10-06)
32+
33+ Bumping alone BREAKS requests that carry an unknown key:
34+
35+ - In v1.13.1 `OpenAICompatible.DefineModel` is deprecated and returns an
36+ UNREGISTERED model. mm discards its result
37+ (internal/engine/openai_compat.go:189), so the call is a no-op.
38+ - The model actually used is then resolved on the fly by the plugin
39+ (`ResolveAction` -> `newSDKModel`), with the strict reflected SDK config
40+ schema: `config: Additional property chat_template_kwargs is not
41+ allowed`, and the whole request fails (v1.10.0 only dropped the key).
42+ - `ModelOptions.ConfigSchema` does not help: it is set on the model
43+ nobody registered.
44+
45+ Fix proven in the scratch copy:
46+
47+ genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{
48+ Label: b.Model,
49+ Supports: &compat_oai.Multimodal,
50+ }).(*ai.ModelAction))
51+
52+ `DefineModel`'s model takes its config untyped: no validation, unknown
53+ keys ride to the wire. Alternative to evaluate: the non-deprecated
54+ `client.NewModel(id, opts)` + `genkit.RegisterAction`, which validates
55+ against the strict schema unless an explicit permissive `ConfigSchema`
56+ is given (it would reject unknown keys otherwise).
57+
58+ ## Measured in the scratch copy
59+
60+ - `go get github.com/firebase/genkit/go@v1.13.1` + `go mod tidy`: go
61+ stays `1.26.0`; also bumps google/dotprompt, invopop/jsonschema
62+ v0.14.0, goccy/go-yaml v1.19.2, buger/jsonparser v1.1.2, adds
63+ go.yaml.in/yaml/v4; openai-go stays v1.8.2.
64+ - `go build`, `go vet`, `go test ./...` green (the skills shipped-YAML
65+ test failed only because the copy had no YAML files).
66+ - Recording server: the request carries
67+ `"chat_template_kwargs": {"enable_thinking": false}` AND mm's own
68+ middleware fields (`return_progress`, `stream_options`).
69+ - Live, DMR qwen3.5-4b, `-p "What is 17*23?"`, sampling
70+ `chat_template_kwargs.enable_thinking: false`: v1.10.0 = 4 s, a 💭
71+ reasoning block, 391; v1.13.1 + RegisterAction = under 1 s, no
72+ reasoning, 391.
73+
74+ ## Side effects to handle
75+
76+ - Genkit now logs `INFO Genkit initialized env=prod plugins=[dmr] …` on
77+ stderr at start-up: silence it (slog level/handler), like the ACP SDK's
78+ "connection closed" log in internal/ask.
79+ - Not re-checked after the bump, each relying on Genkit internals:
80+ - internal/engine/reasoning.go — the tap that reads
81+ `reasoning_content` off the wire because v1.10.0 dropped it; v1.13.1
82+ reads `reasoning_content` / `reasoning` itself (generate.go ~l.548,
83+ ~l.824): possible double display, or the tap becomes redundant.
84+ - internal/engine/toolargs.go — tool-argument decoding workarounds.
85+ - internal/engine/usage.go — the comment saying sampling cannot carry
86+ the no-reasoning field becomes false (summary path can stay as is).
87+ - tool order (ticket 53, tests/tool-order), the watchdog, the
88+ non-streaming retry, `max_tokens` cut-off handling.
89+ - ollama and llmman providers, ACP and window modes, MCP tools.
90+ - Docs: providers / thinking-models explanations, configuration
91+ reference (`sampling` passes extra keys), INSTALL/README if they quote
92+ the Genkit version; ebook chapter on the engine if it shows
93+ `DefineModel`.
94+tasks:
95+ - id: 1
96+ title: "Branch from main after feature/yolo is merged; go get github.com/firebase/genkit/go@v1.13.1 + go mod tidy"
97+ state: closed
98+ priority: none
99+ author:
100+ name: k33g
101+ email: ph.charriere@gmail.com
102+ createdAt: 2026-10-06T14:30:00.000Z
103+ updatedAt: 2026-10-06T16:00:00.000Z
104+ - id: 2
105+ title: "Failing test first: a sampling key unknown to the OpenAI SDK (chat_template_kwargs) reaches the request body (httptest server)"
106+ state: closed
107+ priority: none
108+ author:
109+ name: k33g
110+ email: ph.charriere@gmail.com
111+ createdAt: 2026-10-06T14:30:00.000Z
112+ updatedAt: 2026-10-06T16:00:00.000Z
113+ - id: 3
114+ title: "Register the model explicitly (RegisterAction + DefineModel, or NewModel with a permissive ConfigSchema) — decide which"
115+ state: closed
116+ priority: none
117+ author:
118+ name: k33g
119+ email: ph.charriere@gmail.com
120+ createdAt: 2026-10-06T14:30:00.000Z
121+ updatedAt: 2026-10-06T16:00:00.000Z
122+ - id: 4
123+ title: "Re-check reasoning.go (Genkit now reads reasoning_content itself), toolargs.go, usage.go against v1.13.1; remove what became redundant"
124+ state: closed
125+ priority: none
126+ author:
127+ name: k33g
128+ email: ph.charriere@gmail.com
129+ createdAt: 2026-10-06T14:30:00.000Z
130+ updatedAt: 2026-10-06T16:00:00.000Z
131+ - id: 5
132+ title: "Silence Genkit's 'INFO Genkit initialized' start-up log"
133+ state: closed
134+ priority: none
135+ author:
136+ name: k33g
137+ email: ph.charriere@gmail.com
138+ createdAt: 2026-10-06T14:30:00.000Z
139+ updatedAt: 2026-10-06T16:00:00.000Z
140+ - id: 6
141+ title: "Live checks: dmr, llamacpp, llmman, ollama; line, window, ACP; tools, MCP, /compact, tool-order proxy (tests/tool-order)"
142+ state: open
143+ priority: none
144+ author:
145+ name: k33g
146+ email: ph.charriere@gmail.com
147+ createdAt: 2026-10-06T14:30:00.000Z
148+ updatedAt: 2026-10-06T16:00:00.000Z
149+ - id: 7
150+ title: "Docs EN+FR: sampling extra keys (configuration reference), providers / thinking-models explanations; .memory summary (Genkit version)"
151+ state: closed
152+ priority: none
153+ author:
154+ name: k33g
155+ email: ph.charriere@gmail.com
156+ createdAt: 2026-10-06T14:30:00.000Z
157+ updatedAt: 2026-10-06T16:00:00.000Z
158+comments:
159+ - id: 1
160+ author:
161+ name: claude
162+ email: noreply@anthropic.com
163+ createdAt: 2026-10-06T16:00:00.000Z
164+ body: |
165+ **Done on branch `feature/genkit-v1.13.1` (from main 58ba549), uncommitted.**
166+
167+ - go.mod: genkit v1.13.1 (+ the indirect bumps listed in the body;
168+ mailru/easyjson and wk8/go-ordered-map dropped, pb33f/ordered-map
169+ and go.yaml.in/yaml/v4 added).
170+ - Task 3 decision: `genkit.RegisterAction(g, client.DefineModel(...))`
171+ (`//nolint:staticcheck`, explained). NewModel rejected: it converts
172+ the map config to `openai.ChatCompletionNewParams` BEFORE the model
173+ function, so the extras are lost (measured round trip).
174+ - TWO regressions the bump brought, found by tests/live, both fixed:
175+ 1. Genkit now hands reasoning-only chunks to the streaming callback;
176+ mm's `emit("")` closed the 💭 block on each → one block per token
177+ in the line mode (live, qwen3.5-4b). Fix: `onlyReasoning(chunk)`
178+ skips them (the tap still displays). Test needs a FLUSHED stream
179+ (`TestReasoningStreamsAsOneBlock`): written at once, the tap reads
180+ everything before the callback and the bug hides.
181+ 2. Genkit now appends the reasoning as a reasoning PART of the final
182+ message → it would enter mm's history/session file and go back to
183+ the server as `reasoning_content`. Fix: `dropReasoning(resp)` in
184+ the WrapModel hook (covers tool-loop calls too).
185+ `TestReasoningStaysOutOfTheHistory`.
186+ - Genkit's `INFO Genkit initialized` silenced: `quietGenkit()` =
187+ `logger.SetLevel(slog.LevelWarn)` before Init (GENKIT_LOG_LEVEL wins).
188+ - toolargs.go: Genkit still says "could not parse tool args" — unchanged.
189+ usage.go: comment corrected. reasoning.go tap kept (display +
190+ watchdog heartbeat, one road).
191+ - Tests: `TestSamplingKeysUnknownToTheSDKReachTheServer` (failed first
192+ with the schema error), the two reasoning tests, `TestOpenKeepsGenkitQuiet`.
193+ `go test ./...` green, vet clean.
194+ - Live: DMR mellum2 + bash; DMR qwen3.5-4b thinking shown as ONE block,
195+ `chat_template_kwargs` off via sampling (no reasoning, 391);
196+ two-turn REPL with sessions: session file holds text parts only;
197+ llmman Qwen3.5-4B thinking + bash + second thought block; ACP
198+ (scripted client): stdout pure JSON-RPC, 149 thought chunks forming
199+ one coherent text, answer 391, no INFO line on stderr; tests/tool-order
200+ session.py two ACP turns end_turn; /compact with summaryMaxTokens 300
201+ succeeds.
202+ - NOT checked live: ollama and llamacpp servers (not running on the
203+ host), the window mode, the tool-order proxy measurement, MCP tools.
204+ - Docs EN+FR: reference/configuration (`sampling` extra keys),
205+ explanation/thinking-models (Genkit 1.13 delivery, how to turn
206+ reasoning off through sampling).
207+
added .tickets/issues/0077-ebook-chapter-on-mcp.yaml +131 -0
new file mode 100644
@@ -0,0 +1,131 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 77
3+title: "Ebook: a chapter on MCP"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-06T18:00:00.000Z
9+updatedAt: 2026-10-06T18:00:00.000Z
10+labels:
11+ - documentation
12+ - priority::low
13+body: |
14+ The ebook (« Créer un agent de code pas à pas », `ebook/`, French
15+ AsciiDoc, built with the ebook-builder skill) never covers the MCP client,
16+ and its conclusion (ebook/chapters/99-conclusion.adoc, "Ce qu'il faudrait
17+ faire ensuite") still lists MCP as future work — stale since the client
18+ landed. Found while updating the book for yolo + Genkit 1.13
19+ (2026-10-06).
20+
21+ ## What exists to describe (verified 2026-10-06)
22+
23+ - `internal/mcp` (~1,600 lines with internal/tools/mcp.go):
24+ - `client.go` — `Client` interface, `ServerClient`, `Connect`,
25+ `ConnectAll` (discovery, warnings), `CloseAll`, the `Tool` model.
26+ - `http_client.go` / `http_wire.go` — Streamable HTTP: `initialize` →
27+ `notifications/initialized` → `Mcp-Session-Id` on every POST, session
28+ lost → re-initialise once; JSON or SSE answers; bearer / API-key
29+ auth headers from environment variables (decision recorded
30+ 2026-09-23, branch feature/mcp-gateway-session).
31+ - `tool_wrapper.go` — each MCP tool becomes a Genkit tool named
32+ `<server>_<tool>` (`SanitizeToolName`), collisions with built-in
33+ tools refused (`IsBuiltinTool`, `CheckCollisions`), required params
34+ checked, output truncated to maxOutput, loop detector fed, the two
35+ paths (evented / terminal) like every built-in tool.
36+ - Permissions: `mcp.permissions` (default `ask`) and per-server
37+ `permissions`; since the yolo work `mustAsk` = `ask` OR `yolo: false`
38+ (consent package — ties in with chapter 11).
39+ - `main.go` — MCP assembly at start-up, banner warnings, and
40+ `MCP_GATEWAY_URL` (the Docker Sandboxes gateway, sbx injects it).
41+ - **stdio transport is NOT implemented**: `Connect` returns "stdio
42+ transport for MCP is not yet implemented - use HTTP transport", although
43+ several comments (config.go `MCP`, tool_wrapper.go) say "stdio or HTTP".
44+ The chapter must describe HTTP only (or this ticket waits for stdio);
45+ the misleading comments are worth fixing on the way.
46+ - The ACP façade receives `mcpServers` in `session/new` and ignores them
47+ explicitly (confirmed 2026-10-06: internal/acp/acp.go:247 logs
48+ `mcpServers=N (ignored)`): MCP servers come from mm's YAML, never from
49+ the editor.
50+ - User docs already exist and are the factual base (EN+FR): tutorials/
51+ mcp-getting-started, how-to/mcp-use-{context7,github,wikipedia},
52+ reference/mcp-{configuration,authentication,available-tools},
53+ explanation/mcp-{architecture,decisions}; demo/ has MCP-GITHUB*.md,
54+ MCP_SERVERS_GUIDE.md, MCP_SBX_FIX.md. NB: docs/en/reference/
55+ mcp-configuration.md is a stale copy of configuration.md with no MCP
56+ section (the FR one is the real MCP reference) — fix or note.
57+
58+ ## Proposal
59+
60+ New chapter `ebook/chapters/14-mcp.adoc`, after 13 (window mode) and
61+ before the conclusion — MCP builds on the tool machinery (ch 04), the
62+ event sink (ch 11) and the permission rules (ch 11 `consent`). Same
63+ structure as the other chapters: « À quoi on répond », the code layer
64+ by layer, « Pourquoi », « Ce qu'on retient ». Suggested beats:
65+ 1. Why an agent with five tools would want more without recompiling —
66+ and the conclusion's own question: does it need them?
67+ 2. The protocol in one exchange (initialize, tools/list, tools/call) and
68+ why mm hand-rolls the HTTP client.
69+ 3. A session that survives: Mcp-Session-Id, re-initialise on loss (the
70+ gateway incident of 2026-09-23 as the story).
71+ 4. Wrapping: one MCP tool = one Genkit tool, names, collisions, the
72+ contracts it keeps (truncation, loop detector, text failures).
73+ 5. Trust: `permissions: ask` by default, how `yolo` combines with it.
74+ 6. Docker Sandboxes: MCP_GATEWAY_URL.
75+ 7. What is not there (stdio) and why.
76+tasks:
77+ - id: 1
78+ title: "Verify the facts against the code at writing time (stdio status, ACP mcpServers, permissions/yolo, gateway)"
79+ state: open
80+ priority: none
81+ author:
82+ name: k33g
83+ email: ph.charriere@gmail.com
84+ createdAt: 2026-10-06T18:00:00.000Z
85+ updatedAt: 2026-10-06T18:00:00.000Z
86+ - id: 2
87+ title: "Write ebook/chapters/14-mcp.adoc (FR, one physical line per paragraph, Latin-1 in SVG text) and include it in book.adoc"
88+ state: open
89+ priority: none
90+ author:
91+ name: k33g
92+ email: ph.charriere@gmail.com
93+ createdAt: 2026-10-06T18:00:00.000Z
94+ updatedAt: 2026-10-06T18:00:00.000Z
95+ - id: 3
96+ title: "Diagram (SVG + drawio pair in ebook/images/): mm ↔ MCP server over HTTP, the wrapped tool in the loop"
97+ state: open
98+ priority: none
99+ author:
100+ name: k33g
101+ email: ph.charriere@gmail.com
102+ createdAt: 2026-10-06T18:00:00.000Z
103+ updatedAt: 2026-10-06T18:00:00.000Z
104+ - id: 4
105+ title: "Update the conclusion (drop 'MCP' from future work) and the introduction's chapter list if it has one"
106+ state: open
107+ priority: none
108+ author:
109+ name: k33g
110+ email: ph.charriere@gmail.com
111+ createdAt: 2026-10-06T18:00:00.000Z
112+ updatedAt: 2026-10-06T18:00:00.000Z
113+ - id: 5
114+ title: "Fix the misleading 'stdio or HTTP' comments in the code (config.go, tool_wrapper.go) — or implement stdio first"
115+ state: open
116+ priority: none
117+ author:
118+ name: k33g
119+ email: ph.charriere@gmail.com
120+ createdAt: 2026-10-06T18:00:00.000Z
121+ updatedAt: 2026-10-06T18:00:00.000Z
122+ - id: 6
123+ title: "Rebuild the book (bash .claude/skills/ebook-builder/scripts/build_ebook.sh ebook): 0 warnings"
124+ state: open
125+ priority: none
126+ author:
127+ name: k33g
128+ email: ph.charriere@gmail.com
129+ createdAt: 2026-10-06T18:00:00.000Z
130+ updatedAt: 2026-10-06T18:00:00.000Z
131+comments: []
new file mode 100644
@@ -0,0 +1,131 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 77
3+title: "Ebook: a chapter on MCP"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-06T18:00:00.000Z
9+updatedAt: 2026-10-06T18:00:00.000Z
10+labels:
11+ - documentation
12+ - priority::low
13+body: |
14+ The ebook (« Créer un agent de code pas à pas », `ebook/`, French
15+ AsciiDoc, built with the ebook-builder skill) never covers the MCP client,
16+ and its conclusion (ebook/chapters/99-conclusion.adoc, "Ce qu'il faudrait
17+ faire ensuite") still lists MCP as future work — stale since the client
18+ landed. Found while updating the book for yolo + Genkit 1.13
19+ (2026-10-06).
20+
21+ ## What exists to describe (verified 2026-10-06)
22+
23+ - `internal/mcp` (~1,600 lines with internal/tools/mcp.go):
24+ - `client.go` — `Client` interface, `ServerClient`, `Connect`,
25+ `ConnectAll` (discovery, warnings), `CloseAll`, the `Tool` model.
26+ - `http_client.go` / `http_wire.go` — Streamable HTTP: `initialize` →
27+ `notifications/initialized` → `Mcp-Session-Id` on every POST, session
28+ lost → re-initialise once; JSON or SSE answers; bearer / API-key
29+ auth headers from environment variables (decision recorded
30+ 2026-09-23, branch feature/mcp-gateway-session).
31+ - `tool_wrapper.go` — each MCP tool becomes a Genkit tool named
32+ `<server>_<tool>` (`SanitizeToolName`), collisions with built-in
33+ tools refused (`IsBuiltinTool`, `CheckCollisions`), required params
34+ checked, output truncated to maxOutput, loop detector fed, the two
35+ paths (evented / terminal) like every built-in tool.
36+ - Permissions: `mcp.permissions` (default `ask`) and per-server
37+ `permissions`; since the yolo work `mustAsk` = `ask` OR `yolo: false`
38+ (consent package — ties in with chapter 11).
39+ - `main.go` — MCP assembly at start-up, banner warnings, and
40+ `MCP_GATEWAY_URL` (the Docker Sandboxes gateway, sbx injects it).
41+ - **stdio transport is NOT implemented**: `Connect` returns "stdio
42+ transport for MCP is not yet implemented - use HTTP transport", although
43+ several comments (config.go `MCP`, tool_wrapper.go) say "stdio or HTTP".
44+ The chapter must describe HTTP only (or this ticket waits for stdio);
45+ the misleading comments are worth fixing on the way.
46+ - The ACP façade receives `mcpServers` in `session/new` and ignores them
47+ explicitly (confirmed 2026-10-06: internal/acp/acp.go:247 logs
48+ `mcpServers=N (ignored)`): MCP servers come from mm's YAML, never from
49+ the editor.
50+ - User docs already exist and are the factual base (EN+FR): tutorials/
51+ mcp-getting-started, how-to/mcp-use-{context7,github,wikipedia},
52+ reference/mcp-{configuration,authentication,available-tools},
53+ explanation/mcp-{architecture,decisions}; demo/ has MCP-GITHUB*.md,
54+ MCP_SERVERS_GUIDE.md, MCP_SBX_FIX.md. NB: docs/en/reference/
55+ mcp-configuration.md is a stale copy of configuration.md with no MCP
56+ section (the FR one is the real MCP reference) — fix or note.
57+
58+ ## Proposal
59+
60+ New chapter `ebook/chapters/14-mcp.adoc`, after 13 (window mode) and
61+ before the conclusion — MCP builds on the tool machinery (ch 04), the
62+ event sink (ch 11) and the permission rules (ch 11 `consent`). Same
63+ structure as the other chapters: « À quoi on répond », the code layer
64+ by layer, « Pourquoi », « Ce qu'on retient ». Suggested beats:
65+ 1. Why an agent with five tools would want more without recompiling —
66+ and the conclusion's own question: does it need them?
67+ 2. The protocol in one exchange (initialize, tools/list, tools/call) and
68+ why mm hand-rolls the HTTP client.
69+ 3. A session that survives: Mcp-Session-Id, re-initialise on loss (the
70+ gateway incident of 2026-09-23 as the story).
71+ 4. Wrapping: one MCP tool = one Genkit tool, names, collisions, the
72+ contracts it keeps (truncation, loop detector, text failures).
73+ 5. Trust: `permissions: ask` by default, how `yolo` combines with it.
74+ 6. Docker Sandboxes: MCP_GATEWAY_URL.
75+ 7. What is not there (stdio) and why.
76+tasks:
77+ - id: 1
78+ title: "Verify the facts against the code at writing time (stdio status, ACP mcpServers, permissions/yolo, gateway)"
79+ state: open
80+ priority: none
81+ author:
82+ name: k33g
83+ email: ph.charriere@gmail.com
84+ createdAt: 2026-10-06T18:00:00.000Z
85+ updatedAt: 2026-10-06T18:00:00.000Z
86+ - id: 2
87+ title: "Write ebook/chapters/14-mcp.adoc (FR, one physical line per paragraph, Latin-1 in SVG text) and include it in book.adoc"
88+ state: open
89+ priority: none
90+ author:
91+ name: k33g
92+ email: ph.charriere@gmail.com
93+ createdAt: 2026-10-06T18:00:00.000Z
94+ updatedAt: 2026-10-06T18:00:00.000Z
95+ - id: 3
96+ title: "Diagram (SVG + drawio pair in ebook/images/): mm ↔ MCP server over HTTP, the wrapped tool in the loop"
97+ state: open
98+ priority: none
99+ author:
100+ name: k33g
101+ email: ph.charriere@gmail.com
102+ createdAt: 2026-10-06T18:00:00.000Z
103+ updatedAt: 2026-10-06T18:00:00.000Z
104+ - id: 4
105+ title: "Update the conclusion (drop 'MCP' from future work) and the introduction's chapter list if it has one"
106+ state: open
107+ priority: none
108+ author:
109+ name: k33g
110+ email: ph.charriere@gmail.com
111+ createdAt: 2026-10-06T18:00:00.000Z
112+ updatedAt: 2026-10-06T18:00:00.000Z
113+ - id: 5
114+ title: "Fix the misleading 'stdio or HTTP' comments in the code (config.go, tool_wrapper.go) — or implement stdio first"
115+ state: open
116+ priority: none
117+ author:
118+ name: k33g
119+ email: ph.charriere@gmail.com
120+ createdAt: 2026-10-06T18:00:00.000Z
121+ updatedAt: 2026-10-06T18:00:00.000Z
122+ - id: 6
123+ title: "Rebuild the book (bash .claude/skills/ebook-builder/scripts/build_ebook.sh ebook): 0 warnings"
124+ state: open
125+ priority: none
126+ author:
127+ name: k33g
128+ email: ph.charriere@gmail.com
129+ createdAt: 2026-10-06T18:00:00.000Z
130+ updatedAt: 2026-10-06T18:00:00.000Z
131+comments: []
modified demo-llmman/agent.llmman.yaml +4 -0
@@ -42,6 +42,8 @@ mmConfigs:
4242 # an agent follows its asker's yolo, not this one. Override for one run: AGENT_YOLO=false.
4343 yolo: false
4444
45+
46+
4547 # The `bash` tool.
4648 # `false` removes it: the model can still read and edit files (editTools) and load skills,
4749 # but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
@@ -189,6 +191,8 @@ sampling:
189191 parallel_tool_calls: false
190192 top_p: 0.9
191193 max_tokens: 4096
194+ chat_template_kwargs:
195+ enable_thinking: true
192196
193197 # Silence allowed between two tokens once the answer has started.
194198 watchdogTimeout: 3m
@@ -42,6 +42,8 @@ mmConfigs:
42 # an agent follows its asker's yolo, not this one. Override for one run: AGENT_YOLO=false.42 # an agent follows its asker's yolo, not this one. Override for one run: AGENT_YOLO=false.
43 yolo: false43 yolo: false
44 44
45+
46+
45 # The `bash` tool. 47 # The `bash` tool.
46 # `false` removes it: the model can still read and edit files (editTools) and load skills, 48 # `false` removes it: the model can still read and edit files (editTools) and load skills,
47 # but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".49 # but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
@@ -189,6 +191,8 @@ sampling:
189 parallel_tool_calls: false191 parallel_tool_calls: false
190 top_p: 0.9192 top_p: 0.9
191 max_tokens: 4096193 max_tokens: 4096
194+ chat_template_kwargs:
195+ enable_thinking: true
192 196
193 # Silence allowed between two tokens once the answer has started.197 # Silence allowed between two tokens once the answer has started.
194 watchdogTimeout: 3m198 watchdogTimeout: 3m
modified docs/en/README.md +1 -0
@@ -16,6 +16,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
1616 - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>`
1717 - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, the demo skills
1818 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)
19+- [Turn a model's reasoning off](how-to/turn-reasoning-off.md) — `chat_template_kwargs` or `reasoning_effort` in `sampling`, per server
1920 - [Confirm tool calls before they run](how-to/confirm-tool-calls.md) — `yolo: false`, the four answers, `AGENT_YOLO`, who decides with `mmConfigs`
2021 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the template and the two kits under `docker-sbx/`, window mode and editor
2122 - [Run mini-me with the v3 sandbox kits](how-to/run-with-sandbox-kits-v3.md) — the same two kits for the kit specification v3, under `docker-sbx-v3/`
@@ -16,6 +16,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
16 - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>`16 - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>`
17 - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, the demo skills17 - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, the demo skills
18 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)18 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)
19+- [Turn a model's reasoning off](how-to/turn-reasoning-off.md) — `chat_template_kwargs` or `reasoning_effort` in `sampling`, per server
19 - [Confirm tool calls before they run](how-to/confirm-tool-calls.md) — `yolo: false`, the four answers, `AGENT_YOLO`, who decides with `mmConfigs`20 - [Confirm tool calls before they run](how-to/confirm-tool-calls.md) — `yolo: false`, the four answers, `AGENT_YOLO`, who decides with `mmConfigs`
20 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the template and the two kits under `docker-sbx/`, window mode and editor21 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the template and the two kits under `docker-sbx/`, window mode and editor
21 - [Run mini-me with the v3 sandbox kits](how-to/run-with-sandbox-kits-v3.md) — the same two kits for the kit specification v3, under `docker-sbx-v3/`22 - [Run mini-me with the v3 sandbox kits](how-to/run-with-sandbox-kits-v3.md) — the same two kits for the kit specification v3, under `docker-sbx-v3/`
modified docs/en/explanation/providers.md +1 -1
@@ -18,7 +18,7 @@
1818
1919 **llmman is a third registry entry, with its own probe.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) is a daemon in front of one `llama-server` per loaded model, and speaks several APIs; `mm` uses only its OpenAI one. Everything llama.cpp-specific passes through it — prompt progress, reasoning, `enable_thinking: false` for summaries — so it gets the same treatment as `llamacpp`. Its `/props`, though, is a stub (`n_ctx` 4096, no chat template, measured on llmman 0.1.498), so the probe reads the served window from `/api/ps` instead, for the configured model, once it is loaded. The trained context from `/api/show` is not used: `LLMMAN_CONTEXT_LENGTH` can serve less, and a window too large means compression that never triggers before the server refuses the prompt. A model missing from `/v1/models` is only a warning: llmman pulls it on the first request.
2020
21-**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`.
21+**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". `mm` uses it for summaries only; for every turn, the user writes the same server-specific key in `sampling`, which forwards keys the OpenAI API does not know since Genkit 1.12 (see [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md)). Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`.
2222
2323 **Errors are rewritten in the server's own words.** A stack of wrapped messages is noise; "tool calls need llama-server started with --jinja" is the whole diagnosis. The provider recognises connection refusals, the missing `--jinja` flag, 401/403, 404 with a pull or alias hint, 429 and 5xx. Unknown errors come back unchanged: a raw message beats a wrong hint.
2424
@@ -18,7 +18,7 @@
18 18
19 **llmman is a third registry entry, with its own probe.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) is a daemon in front of one `llama-server` per loaded model, and speaks several APIs; `mm` uses only its OpenAI one. Everything llama.cpp-specific passes through it — prompt progress, reasoning, `enable_thinking: false` for summaries — so it gets the same treatment as `llamacpp`. Its `/props`, though, is a stub (`n_ctx` 4096, no chat template, measured on llmman 0.1.498), so the probe reads the served window from `/api/ps` instead, for the configured model, once it is loaded. The trained context from `/api/show` is not used: `LLMMAN_CONTEXT_LENGTH` can serve less, and a window too large means compression that never triggers before the server refuses the prompt. A model missing from `/v1/models` is only a warning: llmman pulls it on the first request.19 **llmman is a third registry entry, with its own probe.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) is a daemon in front of one `llama-server` per loaded model, and speaks several APIs; `mm` uses only its OpenAI one. Everything llama.cpp-specific passes through it — prompt progress, reasoning, `enable_thinking: false` for summaries — so it gets the same treatment as `llamacpp`. Its `/props`, though, is a stub (`n_ctx` 4096, no chat template, measured on llmman 0.1.498), so the probe reads the served window from `/api/ps` instead, for the configured model, once it is loaded. The trained context from `/api/show` is not used: `LLMMAN_CONTEXT_LENGTH` can serve less, and a window too large means compression that never triggers before the server refuses the prompt. A model missing from `/v1/models` is only a warning: llmman pulls it on the first request.
20 20
21-**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`.21+**Ollama is a fourth entry, and shows why each server keeps its own.** Ollama's `/v1` looks like the others, but measured on Ollama 0.35.0 it differs in three ways. Reasoning streams as `delta.reasoning`, which the tap already reads. No prompt progress comes back, so `return_progress` is not sent. And `chat_template_kwargs` is ignored: the summary of a compaction reasoned until its token limit and came back empty; `reasoning_effort: "none"` is what Ollama obeys, and with it the same compaction succeeded. That is why how to turn reasoning off is a per-provider field, not something implied by "is llama.cpp". `mm` uses it for summaries only; for every turn, the user writes the same server-specific key in `sampling`, which forwards keys the OpenAI API does not know since Genkit 1.12 (see [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md)). Like llmman, Ollama reports the served window on `/api/ps` once the model is loaded (the two share that reading); unlike llmman, it does not pull a missing model, so the probe's warning says `ollama pull`.
22 22
23 **Errors are rewritten in the server's own words.** A stack of wrapped messages is noise; "tool calls need llama-server started with --jinja" is the whole diagnosis. The provider recognises connection refusals, the missing `--jinja` flag, 401/403, 404 with a pull or alias hint, 429 and 5xx. Unknown errors come back unchanged: a raw message beats a wrong hint.23 **Errors are rewritten in the server's own words.** A stack of wrapped messages is noise; "tool calls need llama-server started with --jinja" is the whole diagnosis. The provider recognises connection refusals, the missing `--jinja` flag, 401/403, 404 with a pull or alias hint, 429 and 5xx. Unknown errors come back unchanged: a raw message beats a wrong hint.
24 24
modified docs/en/explanation/thinking-models.md +3 -1
@@ -30,6 +30,8 @@ So `mm` reads the reasoning one layer below Genkit: a middleware on the HTTP cli
3030
3131 This is why `showThinking: false` turns off the *display* and nothing else. The reasoning is always read, because reading it is what keeps the turn alive.
3232
33+Since Genkit 1.13 the plugin does forward reasoning: as reasoning-only chunks to the streaming handler, and as a reasoning part of the final message. `mm` keeps its own reading anyway — one road for the display and the heartbeat whatever Genkit version or server — and neutralises the two new deliveries. A chunk that carries nothing but reasoning is skipped by `mm`'s handler: handled as an empty answer chunk, each one closed the thought block, and the line mode printed one `💭` block per token. The reasoning part of the final message is removed before Genkit or `mm` keeps the message: left in, it would go back to the server as the assistant message's `reasoning_content` on the next model call.
34+
3335 ## Where it goes
3436
3537 | Front end | What you see |
@@ -46,6 +48,6 @@ Three properties hold everywhere:
4648
4749 ## What this is not
4850
49-It is not a "reasoning effort" setting: `mm` asks for nothing and configures nothing on the model side. A model thinks or it does not, and `mm` shows what arrives.
51+It is not a "reasoning effort" setting: `mm` has no key of its own for it, and shows what arrives. The server can still be asked not to reason, through `sampling`, whose keys unknown to the OpenAI API are sent as they are (since Genkit 1.12): `chat_template_kwargs: {enable_thinking: false}` for a llama.cpp server (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` for Ollama. Measured with Qwen3.5 4B on Docker Model Runner: the same answer in under a second, without the four seconds of reasoning. `mm` does this by itself, per server, for the summary of a compaction only. See [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md).
5052
5153 It is not a parser. Some models instead wrap their reasoning in `<think>` tags inside `content`. Those arrive as ordinary answer text and `mm` shows them as such — the two shapes would need different treatment, and only the field is handled today.
@@ -30,6 +30,8 @@ So `mm` reads the reasoning one layer below Genkit: a middleware on the HTTP cli
30 30
31 This is why `showThinking: false` turns off the *display* and nothing else. The reasoning is always read, because reading it is what keeps the turn alive.31 This is why `showThinking: false` turns off the *display* and nothing else. The reasoning is always read, because reading it is what keeps the turn alive.
32 32
33+Since Genkit 1.13 the plugin does forward reasoning: as reasoning-only chunks to the streaming handler, and as a reasoning part of the final message. `mm` keeps its own reading anyway — one road for the display and the heartbeat whatever Genkit version or server — and neutralises the two new deliveries. A chunk that carries nothing but reasoning is skipped by `mm`'s handler: handled as an empty answer chunk, each one closed the thought block, and the line mode printed one `💭` block per token. The reasoning part of the final message is removed before Genkit or `mm` keeps the message: left in, it would go back to the server as the assistant message's `reasoning_content` on the next model call.
34+
33 ## Where it goes35 ## Where it goes
34 36
35 | Front end | What you see |37 | Front end | What you see |
@@ -46,6 +48,6 @@ Three properties hold everywhere:
46 48
47 ## What this is not49 ## What this is not
48 50
49-It is not a "reasoning effort" setting: `mm` asks for nothing and configures nothing on the model side. A model thinks or it does not, and `mm` shows what arrives.51+It is not a "reasoning effort" setting: `mm` has no key of its own for it, and shows what arrives. The server can still be asked not to reason, through `sampling`, whose keys unknown to the OpenAI API are sent as they are (since Genkit 1.12): `chat_template_kwargs: {enable_thinking: false}` for a llama.cpp server (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` for Ollama. Measured with Qwen3.5 4B on Docker Model Runner: the same answer in under a second, without the four seconds of reasoning. `mm` does this by itself, per server, for the summary of a compaction only. See [How to turn a model's reasoning off](../how-to/turn-reasoning-off.md).
50 52
51 It is not a parser. Some models instead wrap their reasoning in `<think>` tags inside `content`. Those arrive as ordinary answer text and `mm` shows them as such — the two shapes would need different treatment, and only the field is handled today.53 It is not a parser. Some models instead wrap their reasoning in `<think>` tags inside `content`. Those arrive as ordinary answer text and `mm` shows them as such — the two shapes would need different treatment, and only the field is handled today.
added docs/en/how-to/turn-reasoning-off.md +54 -0
new file mode 100644
@@ -0,0 +1,54 @@
1+# How to turn a model's reasoning off
2+
3+A thinking model (Qwen3.5, Qwen3.x, gpt-oss…) reasons before it answers. On a simple question that costs seconds and tokens for nothing. This guide shows how to ask the server not to reason, for every turn. It assumes you already run `mm` with a YAML configuration file and a thinking model.
4+
5+## Steps
6+
7+1. Find your provider in the configuration file (`provider:`).
8+
9+2. Add the server's own switch under `sampling`.
10+
11+ For `dmr`, `llamacpp` or `llmman` — all llama.cpp underneath:
12+
13+ ```yaml
14+ sampling:
15+ temperature: 0.0
16+ max_tokens: 4096
17+ chat_template_kwargs:
18+ enable_thinking: false
19+ ```
20+
21+ For `ollama`:
22+
23+ ```yaml
24+ sampling:
25+ temperature: 0.0
26+ max_tokens: 4096
27+ reasoning_effort: none
28+ ```
29+
30+ The keys you write are merged with the built-in ones (`temperature`, `top_p`, `max_tokens`): only the switch needs adding.
31+
32+3. Ask a question and compare. With reasoning on, the line mode prints a dim `💭` block before the `🤖` answer; with it off, the answer comes straight away:
33+
34+ ```
35+ $ mm -p "What is 17*23? Answer with the number only." agent.yaml
36+
37+ 🤖 391⚙ 0 command(s)
38+ 391
39+ ```
40+
41+ Measured with Qwen3.5 4B on Docker Model Runner: under a second, where the same question took four seconds of reasoning.
42+
43+## Variants
44+
45+- **Hide the reasoning but let the model think.** `showThinking: false` removes the `💭` block, the window's `‣ thinking` entry and the editor's thought chunks. The model still reasons — same time, same tokens.
46+- **The model ignores the switch.** `enable_thinking` is a variable of the model's chat template: a template that does not read it (a model without a thinking mode, or with another variable name) is not affected. Check the model card for the variable its template reads.
47+- **Turn it off for one run.** Keep two configuration files, or start the server itself without reasoning (for llama-server, see its `--reasoning-budget` option).
48+- **Compaction summaries.** Nothing to do: `mm` already asks the server not to reason for the summary of `/compact` and of the automatic compression, whatever `sampling` says.
49+
50+## See also
51+
52+- The `sampling` key and what it forwards: [configuration reference](../reference/configuration.md).
53+- Where reasoning comes from and why `mm` reads it itself: [Thinking models](../explanation/thinking-models.md).
54+- Why each server is told in its own words: [Providers](../explanation/providers.md).
new file mode 100644
@@ -0,0 +1,54 @@
1+# How to turn a model's reasoning off
2+
3+A thinking model (Qwen3.5, Qwen3.x, gpt-oss…) reasons before it answers. On a simple question that costs seconds and tokens for nothing. This guide shows how to ask the server not to reason, for every turn. It assumes you already run `mm` with a YAML configuration file and a thinking model.
4+
5+## Steps
6+
7+1. Find your provider in the configuration file (`provider:`).
8+
9+2. Add the server's own switch under `sampling`.
10+
11+ For `dmr`, `llamacpp` or `llmman` — all llama.cpp underneath:
12+
13+ ```yaml
14+ sampling:
15+ temperature: 0.0
16+ max_tokens: 4096
17+ chat_template_kwargs:
18+ enable_thinking: false
19+ ```
20+
21+ For `ollama`:
22+
23+ ```yaml
24+ sampling:
25+ temperature: 0.0
26+ max_tokens: 4096
27+ reasoning_effort: none
28+ ```
29+
30+ The keys you write are merged with the built-in ones (`temperature`, `top_p`, `max_tokens`): only the switch needs adding.
31+
32+3. Ask a question and compare. With reasoning on, the line mode prints a dim `💭` block before the `🤖` answer; with it off, the answer comes straight away:
33+
34+ ```
35+ $ mm -p "What is 17*23? Answer with the number only." agent.yaml
36+
37+ 🤖 391⚙ 0 command(s)
38+ 391
39+ ```
40+
41+ Measured with Qwen3.5 4B on Docker Model Runner: under a second, where the same question took four seconds of reasoning.
42+
43+## Variants
44+
45+- **Hide the reasoning but let the model think.** `showThinking: false` removes the `💭` block, the window's `‣ thinking` entry and the editor's thought chunks. The model still reasons — same time, same tokens.
46+- **The model ignores the switch.** `enable_thinking` is a variable of the model's chat template: a template that does not read it (a model without a thinking mode, or with another variable name) is not affected. Check the model card for the variable its template reads.
47+- **Turn it off for one run.** Keep two configuration files, or start the server itself without reasoning (for llama-server, see its `--reasoning-budget` option).
48+- **Compaction summaries.** Nothing to do: `mm` already asks the server not to reason for the summary of `/compact` and of the automatic compression, whatever `sampling` says.
49+
50+## See also
51+
52+- The `sampling` key and what it forwards: [configuration reference](../reference/configuration.md).
53+- Where reasoning comes from and why `mm` reads it itself: [Thinking models](../explanation/thinking-models.md).
54+- Why each server is told in its own words: [Providers](../explanation/providers.md).
modified docs/en/reference/configuration.md +1 -1
@@ -22,7 +22,7 @@
2222 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
2323 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
2424 | `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. |
25-| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. 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`). |
25+| `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`). |
2626 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
2727 | `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). |
2828 | `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. |
@@ -22,7 +22,7 @@
22 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |22 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
23 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |23 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
24 | `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. |24 | `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. |
25-| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. 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`). |25+| `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`). |
26 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |26 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
27 | `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). |27 | `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). |
28 | `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. |28 | `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/en/reference/mcp-configuration.md +1 -1
@@ -21,7 +21,7 @@
2121 | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |
2222 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
2323 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
24-| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. |
24+| `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 (for example `chat_template_kwargs`). The shipped `agent.yaml` adds `parallel_tool_calls: false`. |
2525 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
2626 | `context` | map | see below | Automatic history compression. |
2727 | `sessions` | map | see below | Persistent, per-project sessions. |
@@ -21,7 +21,7 @@
21 | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |21 | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |
22 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |22 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
23 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |23 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
24-| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. |24+| `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 (for example `chat_template_kwargs`). The shipped `agent.yaml` adds `parallel_tool_calls: false`. |
25 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |25 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
26 | `context` | map | see below | Automatic history compression. |26 | `context` | map | see below | Automatic history compression. |
27 | `sessions` | map | see below | Persistent, per-project sessions. |27 | `sessions` | map | see below | Persistent, per-project sessions. |
modified docs/fr/README.md +1 -0
@@ -16,6 +16,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
1616 - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>`
1717 - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, les skills de démo
1818 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)
19+- [Couper le raisonnement d'un modèle](how-to/turn-reasoning-off.md) — `chat_template_kwargs` ou `reasoning_effort` dans `sampling`, selon le serveur
1920 - [Confirmer les appels d'outils avant leur exécution](how-to/confirm-tool-calls.md) — `yolo: false`, les quatre réponses, `AGENT_YOLO`, qui décide avec `mmConfigs`
2021 - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le template et les deux kits sous `docker-sbx/`, mode fenêtre et éditeur
2122 - [Faire tourner mini-me avec les kits sandbox v3](how-to/run-with-sandbox-kits-v3.md) — les deux mêmes kits pour la spécification de kit v3, sous `docker-sbx-v3/`
@@ -16,6 +16,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
16 - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>`16 - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>`
17 - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, les skills de démo17 - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`, les skills de démo
18 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)18 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)
19+- [Couper le raisonnement d'un modèle](how-to/turn-reasoning-off.md) — `chat_template_kwargs` ou `reasoning_effort` dans `sampling`, selon le serveur
19 - [Confirmer les appels d'outils avant leur exécution](how-to/confirm-tool-calls.md) — `yolo: false`, les quatre réponses, `AGENT_YOLO`, qui décide avec `mmConfigs`20 - [Confirmer les appels d'outils avant leur exécution](how-to/confirm-tool-calls.md) — `yolo: false`, les quatre réponses, `AGENT_YOLO`, qui décide avec `mmConfigs`
20 - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le template et les deux kits sous `docker-sbx/`, mode fenêtre et éditeur21 - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le template et les deux kits sous `docker-sbx/`, mode fenêtre et éditeur
21 - [Faire tourner mini-me avec les kits sandbox v3](how-to/run-with-sandbox-kits-v3.md) — les deux mêmes kits pour la spécification de kit v3, sous `docker-sbx-v3/`22 - [Faire tourner mini-me avec les kits sandbox v3](how-to/run-with-sandbox-kits-v3.md) — les deux mêmes kits pour la spécification de kit v3, sous `docker-sbx-v3/`
modified docs/fr/explanation/providers.md +1 -1
@@ -18,7 +18,7 @@
1818
1919 **llmman est une troisième entrée du registre, avec sa propre sonde.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) est un démon placé devant un `llama-server` par modèle chargé, et parle plusieurs API ; `mm` n'utilise que celle d'OpenAI. Tout ce qui est propre à llama.cpp la traverse — avancement de la lecture du prompt, raisonnement, `enable_thinking: false` pour les résumés — d'où le même traitement que `llamacpp`. Son `/props`, en revanche, est un bouchon (`n_ctx` 4096, pas de chat template, mesuré sur llmman 0.1.498) : la sonde lit donc la fenêtre servie sur `/api/ps`, pour le modèle configuré, une fois qu'il est chargé. Le contexte d'entraînement de `/api/show` n'est pas utilisé : `LLMMAN_CONTEXT_LENGTH` peut servir moins, et une fenêtre trop grande donne une compression qui ne se déclenche jamais avant que le serveur refuse le prompt. Un modèle absent de `/v1/models` n'est qu'un avertissement : llmman le télécharge à la première requête.
2020
21-**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`.
21+**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». `mm` ne s'en sert que pour les résumés ; pour chaque tour, l'utilisateur écrit la même clé propre au serveur dans `sampling`, qui transmet depuis Genkit 1.12 les clés que l'API OpenAI ne connaît pas (voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md)). Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`.
2222
2323 **Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste.
2424
@@ -18,7 +18,7 @@
18 18
19 **llmman est une troisième entrée du registre, avec sa propre sonde.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) est un démon placé devant un `llama-server` par modèle chargé, et parle plusieurs API ; `mm` n'utilise que celle d'OpenAI. Tout ce qui est propre à llama.cpp la traverse — avancement de la lecture du prompt, raisonnement, `enable_thinking: false` pour les résumés — d'où le même traitement que `llamacpp`. Son `/props`, en revanche, est un bouchon (`n_ctx` 4096, pas de chat template, mesuré sur llmman 0.1.498) : la sonde lit donc la fenêtre servie sur `/api/ps`, pour le modèle configuré, une fois qu'il est chargé. Le contexte d'entraînement de `/api/show` n'est pas utilisé : `LLMMAN_CONTEXT_LENGTH` peut servir moins, et une fenêtre trop grande donne une compression qui ne se déclenche jamais avant que le serveur refuse le prompt. Un modèle absent de `/v1/models` n'est qu'un avertissement : llmman le télécharge à la première requête.19 **llmman est une troisième entrée du registre, avec sa propre sonde.** [llmman](https://github.com/llmmanorg/llmman) (`llmman serve`, port 17434) est un démon placé devant un `llama-server` par modèle chargé, et parle plusieurs API ; `mm` n'utilise que celle d'OpenAI. Tout ce qui est propre à llama.cpp la traverse — avancement de la lecture du prompt, raisonnement, `enable_thinking: false` pour les résumés — d'où le même traitement que `llamacpp`. Son `/props`, en revanche, est un bouchon (`n_ctx` 4096, pas de chat template, mesuré sur llmman 0.1.498) : la sonde lit donc la fenêtre servie sur `/api/ps`, pour le modèle configuré, une fois qu'il est chargé. Le contexte d'entraînement de `/api/show` n'est pas utilisé : `LLMMAN_CONTEXT_LENGTH` peut servir moins, et une fenêtre trop grande donne une compression qui ne se déclenche jamais avant que le serveur refuse le prompt. Un modèle absent de `/v1/models` n'est qu'un avertissement : llmman le télécharge à la première requête.
20 20
21-**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`.21+**Ollama est une quatrième entrée, et montre pourquoi chaque serveur garde la sienne.** Le `/v1` d'Ollama ressemble aux autres, mais, mesuré sur Ollama 0.35.0, il diffère sur trois points. Le raisonnement arrive dans `delta.reasoning`, que le tap lit déjà. Aucun avancement de lecture du prompt ne revient, donc `return_progress` n'est pas envoyé. Et `chat_template_kwargs` est ignoré : le résumé d'une compaction a raisonné jusqu'à sa limite de tokens et est revenu vide ; c'est `reasoning_effort: "none"` qu'Ollama respecte, et avec lui la même compaction a réussi. Voilà pourquoi la façon de couper le raisonnement est un champ par fournisseur, et non une conséquence de « est llama.cpp ». `mm` ne s'en sert que pour les résumés ; pour chaque tour, l'utilisateur écrit la même clé propre au serveur dans `sampling`, qui transmet depuis Genkit 1.12 les clés que l'API OpenAI ne connaît pas (voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md)). Comme llmman, Ollama rapporte la fenêtre servie sur `/api/ps` une fois le modèle chargé (les deux partagent cette lecture) ; contrairement à llmman, il ne télécharge pas un modèle absent, d'où l'avertissement de la sonde qui dit `ollama pull`.
22 22
23 **Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste.23 **Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste.
24 24
modified docs/fr/explanation/thinking-models.md +3 -1
@@ -30,6 +30,8 @@ Et **le battement du chien de garde**, qui est la partie sérieuse. `mm` annule
3030
3131 D'où le fait que `showThinking: false` coupe l'*affichage* et rien d'autre. Le raisonnement est toujours lu, parce que le lire est ce qui garde le tour en vie.
3232
33+Depuis Genkit 1.13, le greffon transmet bien le raisonnement : sous forme de chunks qui ne contiennent que du raisonnement, passés au gestionnaire de streaming, et sous forme d'une partie de raisonnement dans le message final. `mm` garde malgré tout sa propre lecture — une seule voie pour l'affichage et le battement, quels que soient la version de Genkit et le serveur — et neutralise ces deux nouvelles livraisons. Un chunk qui ne contient que du raisonnement est ignoré par le gestionnaire de `mm` : traité comme un morceau de réponse vide, chacun fermait le bloc de pensée, et le mode ligne affichait un bloc `💭` par token. La partie de raisonnement du message final est retirée avant que Genkit ou `mm` ne conserve le message : laissée en place, elle repartirait vers le serveur comme `reasoning_content` du message de l'assistant au prochain appel du modèle.
34+
3335 ## Où il va
3436
3537 | Façade | Ce que vous voyez |
@@ -46,6 +48,6 @@ Trois propriétés valent partout :
4648
4749 ## Ce que ce n'est pas
4850
49-Ce n'est pas un réglage d'« effort de raisonnement » : `mm` ne demande rien et ne configure rien côté modèle. Un modèle réfléchit ou non, et `mm` affiche ce qui arrive.
51+Ce n'est pas un réglage d'« effort de raisonnement » : `mm` n'a pas de clé à lui pour cela, et affiche ce qui arrive. On peut tout de même demander au serveur de ne pas raisonner, via `sampling`, dont les clés inconnues de l'API OpenAI sont transmises telles quelles (depuis Genkit 1.12) : `chat_template_kwargs: {enable_thinking: false}` pour un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` pour Ollama. Mesuré avec Qwen3.5 4B sur Docker Model Runner : la même réponse en moins d'une seconde, sans les quatre secondes de raisonnement. `mm` le fait de lui-même, serveur par serveur, pour le seul résumé d'une compaction. Voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md).
5052
5153 Ce n'est pas un analyseur. Certains modèles encadrent plutôt leur raisonnement de balises `<think>` à l'intérieur de `content`. Celles-là arrivent comme du texte de réponse ordinaire et `mm` les affiche comme tel — les deux formes demanderaient des traitements différents, et seul le champ est géré aujourd'hui.
@@ -30,6 +30,8 @@ Et **le battement du chien de garde**, qui est la partie sérieuse. `mm` annule
30 30
31 D'où le fait que `showThinking: false` coupe l'*affichage* et rien d'autre. Le raisonnement est toujours lu, parce que le lire est ce qui garde le tour en vie.31 D'où le fait que `showThinking: false` coupe l'*affichage* et rien d'autre. Le raisonnement est toujours lu, parce que le lire est ce qui garde le tour en vie.
32 32
33+Depuis Genkit 1.13, le greffon transmet bien le raisonnement : sous forme de chunks qui ne contiennent que du raisonnement, passés au gestionnaire de streaming, et sous forme d'une partie de raisonnement dans le message final. `mm` garde malgré tout sa propre lecture — une seule voie pour l'affichage et le battement, quels que soient la version de Genkit et le serveur — et neutralise ces deux nouvelles livraisons. Un chunk qui ne contient que du raisonnement est ignoré par le gestionnaire de `mm` : traité comme un morceau de réponse vide, chacun fermait le bloc de pensée, et le mode ligne affichait un bloc `💭` par token. La partie de raisonnement du message final est retirée avant que Genkit ou `mm` ne conserve le message : laissée en place, elle repartirait vers le serveur comme `reasoning_content` du message de l'assistant au prochain appel du modèle.
34+
33 ## Où il va35 ## Où il va
34 36
35 | Façade | Ce que vous voyez |37 | Façade | Ce que vous voyez |
@@ -46,6 +48,6 @@ Trois propriétés valent partout :
46 48
47 ## Ce que ce n'est pas49 ## Ce que ce n'est pas
48 50
49-Ce n'est pas un réglage d'« effort de raisonnement » : `mm` ne demande rien et ne configure rien côté modèle. Un modèle réfléchit ou non, et `mm` affiche ce qui arrive.51+Ce n'est pas un réglage d'« effort de raisonnement » : `mm` n'a pas de clé à lui pour cela, et affiche ce qui arrive. On peut tout de même demander au serveur de ne pas raisonner, via `sampling`, dont les clés inconnues de l'API OpenAI sont transmises telles quelles (depuis Genkit 1.12) : `chat_template_kwargs: {enable_thinking: false}` pour un serveur llama.cpp (`dmr`, `llamacpp`, `llmman`), `reasoning_effort: none` pour Ollama. Mesuré avec Qwen3.5 4B sur Docker Model Runner : la même réponse en moins d'une seconde, sans les quatre secondes de raisonnement. `mm` le fait de lui-même, serveur par serveur, pour le seul résumé d'une compaction. Voir [Couper le raisonnement d'un modèle](../how-to/turn-reasoning-off.md).
50 52
51 Ce n'est pas un analyseur. Certains modèles encadrent plutôt leur raisonnement de balises `<think>` à l'intérieur de `content`. Celles-là arrivent comme du texte de réponse ordinaire et `mm` les affiche comme tel — les deux formes demanderaient des traitements différents, et seul le champ est géré aujourd'hui.53 Ce n'est pas un analyseur. Certains modèles encadrent plutôt leur raisonnement de balises `<think>` à l'intérieur de `content`. Celles-là arrivent comme du texte de réponse ordinaire et `mm` les affiche comme tel — les deux formes demanderaient des traitements différents, et seul le champ est géré aujourd'hui.
added docs/fr/how-to/turn-reasoning-off.md +54 -0
new file mode 100644
@@ -0,0 +1,54 @@
1+# Couper le raisonnement d'un modèle
2+
3+Un modèle à raisonnement (Qwen3.5, Qwen3.x, gpt-oss…) réfléchit avant de répondre. Sur une question simple, cela coûte des secondes et des tokens pour rien. Ce guide montre comment demander au serveur de ne pas raisonner, à chaque tour. Il suppose que vous lancez déjà `mm` avec un fichier de configuration YAML et un modèle à raisonnement.
4+
5+## Étapes
6+
7+1. Repérez votre fournisseur dans le fichier de configuration (`provider:`).
8+
9+2. Ajoutez l'interrupteur propre au serveur sous `sampling`.
10+
11+ Pour `dmr`, `llamacpp` ou `llmman` — tous trois llama.cpp en dessous :
12+
13+ ```yaml
14+ sampling:
15+ temperature: 0.0
16+ max_tokens: 4096
17+ chat_template_kwargs:
18+ enable_thinking: false
19+ ```
20+
21+ Pour `ollama` :
22+
23+ ```yaml
24+ sampling:
25+ temperature: 0.0
26+ max_tokens: 4096
27+ reasoning_effort: none
28+ ```
29+
30+ Les clés que vous écrivez s'ajoutent à celles par défaut (`temperature`, `top_p`, `max_tokens`) : seul l'interrupteur est à ajouter.
31+
32+3. Posez une question et comparez. Raisonnement actif, le mode ligne affiche un bloc `💭` grisé avant la réponse `🤖` ; coupé, la réponse arrive directement :
33+
34+ ```
35+ $ mm -p "What is 17*23? Answer with the number only." agent.yaml
36+
37+ 🤖 391⚙ 0 command(s)
38+ 391
39+ ```
40+
41+ Mesuré avec Qwen3.5 4B sur Docker Model Runner : moins d'une seconde, là où la même question prenait quatre secondes de raisonnement.
42+
43+## Variantes
44+
45+- **Masquer le raisonnement mais laisser le modèle réfléchir.** `showThinking: false` retire le bloc `💭`, l'entrée `‣ thinking` de la fenêtre et les morceaux de pensée envoyés à l'éditeur. Le modèle raisonne quand même — même durée, mêmes tokens.
46+- **Le modèle ignore l'interrupteur.** `enable_thinking` est une variable du template de chat du modèle : un template qui ne la lit pas (un modèle sans mode de raisonnement, ou qui utilise un autre nom de variable) n'est pas concerné. Vérifiez sur la fiche du modèle la variable que lit son template.
47+- **Le couper pour une seule exécution.** Gardez deux fichiers de configuration, ou démarrez le serveur lui-même sans raisonnement (pour llama-server, voir son option `--reasoning-budget`).
48+- **Résumés de compaction.** Rien à faire : `mm` demande déjà au serveur de ne pas raisonner pour le résumé de `/compact` et de la compression automatique, quoi que dise `sampling`.
49+
50+## Voir aussi
51+
52+- La clé `sampling` et ce qu'elle transmet : [référence de la configuration](../reference/configuration.md).
53+- D'où vient le raisonnement et pourquoi `mm` le lit lui-même : [Modèles à raisonnement](../explanation/thinking-models.md).
54+- Pourquoi chaque serveur est prévenu dans ses propres termes : [Fournisseurs](../explanation/providers.md).
new file mode 100644
@@ -0,0 +1,54 @@
1+# Couper le raisonnement d'un modèle
2+
3+Un modèle à raisonnement (Qwen3.5, Qwen3.x, gpt-oss…) réfléchit avant de répondre. Sur une question simple, cela coûte des secondes et des tokens pour rien. Ce guide montre comment demander au serveur de ne pas raisonner, à chaque tour. Il suppose que vous lancez déjà `mm` avec un fichier de configuration YAML et un modèle à raisonnement.
4+
5+## Étapes
6+
7+1. Repérez votre fournisseur dans le fichier de configuration (`provider:`).
8+
9+2. Ajoutez l'interrupteur propre au serveur sous `sampling`.
10+
11+ Pour `dmr`, `llamacpp` ou `llmman` — tous trois llama.cpp en dessous :
12+
13+ ```yaml
14+ sampling:
15+ temperature: 0.0
16+ max_tokens: 4096
17+ chat_template_kwargs:
18+ enable_thinking: false
19+ ```
20+
21+ Pour `ollama` :
22+
23+ ```yaml
24+ sampling:
25+ temperature: 0.0
26+ max_tokens: 4096
27+ reasoning_effort: none
28+ ```
29+
30+ Les clés que vous écrivez s'ajoutent à celles par défaut (`temperature`, `top_p`, `max_tokens`) : seul l'interrupteur est à ajouter.
31+
32+3. Posez une question et comparez. Raisonnement actif, le mode ligne affiche un bloc `💭` grisé avant la réponse `🤖` ; coupé, la réponse arrive directement :
33+
34+ ```
35+ $ mm -p "What is 17*23? Answer with the number only." agent.yaml
36+
37+ 🤖 391⚙ 0 command(s)
38+ 391
39+ ```
40+
41+ Mesuré avec Qwen3.5 4B sur Docker Model Runner : moins d'une seconde, là où la même question prenait quatre secondes de raisonnement.
42+
43+## Variantes
44+
45+- **Masquer le raisonnement mais laisser le modèle réfléchir.** `showThinking: false` retire le bloc `💭`, l'entrée `‣ thinking` de la fenêtre et les morceaux de pensée envoyés à l'éditeur. Le modèle raisonne quand même — même durée, mêmes tokens.
46+- **Le modèle ignore l'interrupteur.** `enable_thinking` est une variable du template de chat du modèle : un template qui ne la lit pas (un modèle sans mode de raisonnement, ou qui utilise un autre nom de variable) n'est pas concerné. Vérifiez sur la fiche du modèle la variable que lit son template.
47+- **Le couper pour une seule exécution.** Gardez deux fichiers de configuration, ou démarrez le serveur lui-même sans raisonnement (pour llama-server, voir son option `--reasoning-budget`).
48+- **Résumés de compaction.** Rien à faire : `mm` demande déjà au serveur de ne pas raisonner pour le résumé de `/compact` et de la compression automatique, quoi que dise `sampling`.
49+
50+## Voir aussi
51+
52+- La clé `sampling` et ce qu'elle transmet : [référence de la configuration](../reference/configuration.md).
53+- D'où vient le raisonnement et pourquoi `mm` le lit lui-même : [Modèles à raisonnement](../explanation/thinking-models.md).
54+- Pourquoi chaque serveur est prévenu dans ses propres termes : [Fournisseurs](../explanation/providers.md).
modified docs/fr/reference/configuration.md +1 -1
@@ -22,7 +22,7 @@
2222 | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. |
2323 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |
2424 | `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. |
25-| `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. 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`). |
25+| `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`). |
2626 | `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é. |
2727 | `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). |
2828 | `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. |
@@ -22,7 +22,7 @@
22 | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. |22 | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. |
23 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |23 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |
24 | `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. |24 | `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. |
25-| `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. 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`). |25+| `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`). |
26 | `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é. |26 | `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é. |
27 | `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). |27 | `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). |
28 | `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. |28 | `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. |
modified go.mod +7 -7
@@ -3,7 +3,7 @@ module mm
33 go 1.26.0
44
55 require (
6- github.com/firebase/genkit/go v1.10.0
6+ github.com/firebase/genkit/go v1.13.1
77 github.com/gdamore/tcell/v2 v2.13.10
88 github.com/openai/openai-go v1.8.2
99 github.com/sashabaranov/go-openai v1.42.1
@@ -19,28 +19,28 @@ require (
1919 github.com/gdamore/encoding v1.0.1 // indirect
2020 github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect
2121 github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
22+ github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
2223 github.com/rivo/uniseg v0.4.7 // indirect
24+ go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
2325 golang.org/x/term v0.46.0 // indirect
2426 )
2527
2628 require (
2729 github.com/bahlo/generic-list-go v0.2.0 // indirect
28- github.com/buger/jsonparser v1.1.1 // indirect
30+ github.com/buger/jsonparser v1.1.2 // indirect
2931 github.com/coder/acp-go-sdk v0.13.5
3032 github.com/coder/websocket v1.8.14 // indirect
3133 github.com/go-logr/logr v1.4.3 // indirect
3234 github.com/go-logr/stdr v1.2.2 // indirect
33- github.com/goccy/go-yaml v1.17.1 // indirect
34- github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 // indirect
35+ github.com/goccy/go-yaml v1.19.2 // indirect
36+ github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 // indirect
3537 github.com/google/uuid v1.6.0 // indirect
36- github.com/invopop/jsonschema v0.13.0 // indirect
37- github.com/mailru/easyjson v0.9.0 // indirect
38+ github.com/invopop/jsonschema v0.14.0 // indirect
3839 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect
3940 github.com/tidwall/gjson v1.18.0 // indirect
4041 github.com/tidwall/match v1.1.1 // indirect
4142 github.com/tidwall/pretty v1.2.1 // indirect
4243 github.com/tidwall/sjson v1.2.5 // indirect
43- github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
4444 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
4545 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
4646 github.com/xeipuuv/gojsonschema v1.2.0 // indirect
@@ -3,7 +3,7 @@ module mm
3 go 1.26.03 go 1.26.0
4 4
5 require (5 require (
6- github.com/firebase/genkit/go v1.10.06+ github.com/firebase/genkit/go v1.13.1
7 github.com/gdamore/tcell/v2 v2.13.107 github.com/gdamore/tcell/v2 v2.13.10
8 github.com/openai/openai-go v1.8.28 github.com/openai/openai-go v1.8.2
9 github.com/sashabaranov/go-openai v1.42.19 github.com/sashabaranov/go-openai v1.42.1
@@ -19,28 +19,28 @@ require (
19 github.com/gdamore/encoding v1.0.1 // indirect19 github.com/gdamore/encoding v1.0.1 // indirect
20 github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect20 github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect
21 github.com/lucasb-eyer/go-colorful v1.4.0 // indirect21 github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
22+ github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
22 github.com/rivo/uniseg v0.4.7 // indirect23 github.com/rivo/uniseg v0.4.7 // indirect
24+ go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
23 golang.org/x/term v0.46.0 // indirect25 golang.org/x/term v0.46.0 // indirect
24 )26 )
25 27
26 require (28 require (
27 github.com/bahlo/generic-list-go v0.2.0 // indirect29 github.com/bahlo/generic-list-go v0.2.0 // indirect
28- github.com/buger/jsonparser v1.1.1 // indirect30+ github.com/buger/jsonparser v1.1.2 // indirect
29 github.com/coder/acp-go-sdk v0.13.531 github.com/coder/acp-go-sdk v0.13.5
30 github.com/coder/websocket v1.8.14 // indirect32 github.com/coder/websocket v1.8.14 // indirect
31 github.com/go-logr/logr v1.4.3 // indirect33 github.com/go-logr/logr v1.4.3 // indirect
32 github.com/go-logr/stdr v1.2.2 // indirect34 github.com/go-logr/stdr v1.2.2 // indirect
33- github.com/goccy/go-yaml v1.17.1 // indirect35+ github.com/goccy/go-yaml v1.19.2 // indirect
34- github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 // indirect36+ github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 // indirect
35 github.com/google/uuid v1.6.0 // indirect37 github.com/google/uuid v1.6.0 // indirect
36- github.com/invopop/jsonschema v0.13.0 // indirect38+ github.com/invopop/jsonschema v0.14.0 // indirect
37- github.com/mailru/easyjson v0.9.0 // indirect
38 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect39 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect
39 github.com/tidwall/gjson v1.18.0 // indirect40 github.com/tidwall/gjson v1.18.0 // indirect
40 github.com/tidwall/match v1.1.1 // indirect41 github.com/tidwall/match v1.1.1 // indirect
41 github.com/tidwall/pretty v1.2.1 // indirect42 github.com/tidwall/pretty v1.2.1 // indirect
42 github.com/tidwall/sjson v1.2.5 // indirect43 github.com/tidwall/sjson v1.2.5 // indirect
43- github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
44 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect44 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
45 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect45 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
46 github.com/xeipuuv/gojsonschema v1.2.0 // indirect46 github.com/xeipuuv/gojsonschema v1.2.0 // indirect
modified go.sum +20 -14
@@ -1,7 +1,7 @@
11 github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
22 github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
3-github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
4-github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
3+github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
4+github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
55 github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk=
66 github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko=
77 github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
@@ -9,8 +9,8 @@ github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6p
99 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1010 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1111 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12-github.com/firebase/genkit/go v1.10.0 h1:kOu3MKfgqRPk9yYHg2HFoCg8VWzcHJtfRyQw7OuYqMs=
13-github.com/firebase/genkit/go v1.10.0/go.mod h1:AzmlJrm+2PjSrLnBHwY0uTbRC/GsazMa0JYpBrVf18E=
12+github.com/firebase/genkit/go v1.13.1 h1:6fgQ0ogxG+SIgtYQD/yf8T0+39lacioB5voFdFYk1tI=
13+github.com/firebase/genkit/go v1.13.1/go.mod h1:nWewix7d2O+oikJ035XPmY4mrO2phXwmk6NekKmaDLU=
1414 github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
1515 github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
1616 github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA=
@@ -22,28 +22,28 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
2222 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
2323 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
2424 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
25-github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
26-github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
27-github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 h1:okN800+zMJOGHLJCgry+OGzhhtH6YrjQh1rluHmOacE=
28-github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254/go.mod h1:k8cjJAQWc//ac/bMnzItyOFbfT01tgRTZGgxELCuxEQ=
25+github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
26+github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
27+github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 h1:SJdnmyOaT+kZNcUR+a1y2+Oa51j2ctCjYbtexaiXN68=
28+github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95/go.mod h1:dnlL7KrFwJ7s8EJdsAp1WdLqOalJq1Sx2jWZnQkhFXs=
2929 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
3030 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
3131 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
3232 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
33-github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
34-github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
33+github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
34+github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
3535 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
3636 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
3737 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3838 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
3939 github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
4040 github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
41-github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
42-github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
4341 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A=
4442 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU=
4543 github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
4644 github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
45+github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
46+github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
4747 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4848 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4949 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -66,8 +66,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
6666 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
6767 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
6868 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
69-github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
70-github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
7169 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
7270 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
7371 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -96,10 +94,14 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
9694 go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
9795 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
9896 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
97+go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
98+go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
9999 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
100100 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
101101 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
102102 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
103+golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
104+golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
103105 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
104106 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
105107 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -109,6 +111,8 @@ golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
109111 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
110112 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
111113 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
114+golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
115+golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
112116 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
113117 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
114118 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -133,6 +137,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
133137 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
134138 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
135139 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
140+golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
141+golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
136142 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
137143 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
138144 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -1,7 +1,7 @@
1 github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=1 github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
2 github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=2 github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
3-github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=3+github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
4-github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=4+github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
5 github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk=5 github.com/coder/acp-go-sdk v0.13.5 h1:LI9jq5xon7xslaYlnoktvTVyDlE37yIk2daT7N9ASYk=
6 github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko=6 github.com/coder/acp-go-sdk v0.13.5/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko=
7 github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=7 github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
@@ -9,8 +9,8 @@ github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6p
9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=10 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=11 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12-github.com/firebase/genkit/go v1.10.0 h1:kOu3MKfgqRPk9yYHg2HFoCg8VWzcHJtfRyQw7OuYqMs=12+github.com/firebase/genkit/go v1.13.1 h1:6fgQ0ogxG+SIgtYQD/yf8T0+39lacioB5voFdFYk1tI=
13-github.com/firebase/genkit/go v1.10.0/go.mod h1:AzmlJrm+2PjSrLnBHwY0uTbRC/GsazMa0JYpBrVf18E=13+github.com/firebase/genkit/go v1.13.1/go.mod h1:nWewix7d2O+oikJ035XPmY4mrO2phXwmk6NekKmaDLU=
14 github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=14 github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
15 github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=15 github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
16 github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA=16 github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA=
@@ -22,28 +22,28 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
22 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=22 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
23 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=23 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
24 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=24 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
25-github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=25+github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
26-github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=26+github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
27-github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254 h1:okN800+zMJOGHLJCgry+OGzhhtH6YrjQh1rluHmOacE=27+github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95 h1:SJdnmyOaT+kZNcUR+a1y2+Oa51j2ctCjYbtexaiXN68=
28-github.com/google/dotprompt/go v0.0.0-20251014011017-8d056e027254/go.mod h1:k8cjJAQWc//ac/bMnzItyOFbfT01tgRTZGgxELCuxEQ=28+github.com/google/dotprompt/go v0.0.0-20260708220100-73beb993ac95/go.mod h1:dnlL7KrFwJ7s8EJdsAp1WdLqOalJq1Sx2jWZnQkhFXs=
29 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=29 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
30 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=30 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
31 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=31 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
32 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=32 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
33-github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=33+github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
34-github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=34+github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
35 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=35 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
36 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=36 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
37 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=37 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
38 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=38 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
39 github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=39 github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
40 github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=40 github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
41-github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
42-github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
43 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A=41 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A=
44 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU=42 github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU=
45 github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=43 github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
46 github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=44 github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
45+github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
46+github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
47 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=47 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
48 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=48 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
49 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=49 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -66,8 +66,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
66 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=66 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
67 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=67 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
68 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=68 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
69-github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
70-github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
71 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=69 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
72 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=70 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
73 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=71 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -96,10 +94,14 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
96 go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=94 go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
97 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=95 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
98 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=96 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
97+go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
98+go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
99 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=99 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
100 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=100 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
101 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=101 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
102 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=102 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
103+golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
104+golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
103 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=105 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
104 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=106 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
105 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=107 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -109,6 +111,8 @@ golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
109 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=111 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
110 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=112 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
111 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=113 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
114+golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
115+golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
112 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=116 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
113 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=117 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
114 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=118 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -133,6 +137,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
133 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=137 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
134 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=138 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
135 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=139 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
140+golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
141+golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
136 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=142 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
137 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=143 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
138 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=144 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
modified internal/engine/engine.go +7 -0
@@ -332,6 +332,7 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
332332 })
333333 }
334334 }
335+ dropReasoning(resp)
335336 return resp, err
336337 },
337338 // The tool is about to write to the screen: we forget the
@@ -450,6 +451,12 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
450451 if guard.composing && !wasComposing {
451452 spinner.Start("Composing a tool call")
452453 }
454+ // Since Genkit 1.13 a chunk may carry nothing but reasoning. The
455+ // tap has already shown it (reasoning.go); handed to emit, its
456+ // empty text would close the thought block after every token.
457+ if onlyReasoning(chunk) {
458+ return nil
459+ }
453460 emit(chunk.Text())
454461 return nil
455462 }),
@@ -332,6 +332,7 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
332 })332 })
333 }333 }
334 }334 }
335+ dropReasoning(resp)
335 return resp, err336 return resp, err
336 },337 },
337 // The tool is about to write to the screen: we forget the338 // The tool is about to write to the screen: we forget the
@@ -450,6 +451,12 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
450 if guard.composing && !wasComposing {451 if guard.composing && !wasComposing {
451 spinner.Start("Composing a tool call")452 spinner.Start("Composing a tool call")
452 }453 }
454+ // Since Genkit 1.13 a chunk may carry nothing but reasoning. The
455+ // tap has already shown it (reasoning.go); handed to emit, its
456+ // empty text would close the thought block after every token.
457+ if onlyReasoning(chunk) {
458+ return nil
459+ }
453 emit(chunk.Text())460 emit(chunk.Text())
454 return nil461 return nil
455 }),462 }),
modified internal/engine/openai_compat.go +22 -2
@@ -4,6 +4,7 @@ import (
44 "context"
55 "encoding/json"
66 "fmt"
7+ "log/slog"
78 "net/http"
89 "os"
910 "strings"
@@ -12,6 +13,7 @@ import (
1213 "mm/internal/config"
1314
1415 "github.com/firebase/genkit/go/ai"
16+ "github.com/firebase/genkit/go/core/logger"
1517 "github.com/firebase/genkit/go/genkit"
1618 "github.com/firebase/genkit/go/plugins/compat_oai"
1719 "github.com/openai/openai-go/option"
@@ -185,14 +187,32 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str
185187 // request asks for the token counts it would otherwise lack (usage.go).
186188 Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)},
187189 }
190+ quietGenkit()
188191 g := genkit.Init(ctx, genkit.WithPlugins(client))
189- client.DefineModel(p.name, b.Model, ai.ModelOptions{
192+ // Registered explicitly: since Genkit 1.13 DefineModel returns an
193+ // UNREGISTERED model, and an unregistered name is resolved by the plugin
194+ // with the OpenAI SDK's strict config schema, which rejects any sampling
195+ // key the SDK does not declare — chat_template_kwargs, the llama.cpp way
196+ // to turn reasoning off, first of all ("Additional property … is not
197+ // allowed"). DefineModel's model takes its config untyped and sends such
198+ // keys as JSON extras; NewModel, its non-deprecated successor, converts
199+ // the config to the SDK struct first and loses them. Ticket 76.
200+ genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{ //nolint:staticcheck // see above: the only constructor that keeps provider-specific sampling keys
190201 Label: b.Model,
191202 Supports: &compat_oai.Multimodal, // enables Tools, Multiturn, SystemRole…
192- })
203+ }).(*ai.ModelAction))
193204 return g, p.name + "/" + b.Model, nil
194205 }
195206
207+// quietGenkit keeps Genkit's informational log lines off the screen: since
208+// 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on
209+// stderr, which lands in the middle of the terminal's output and of the
210+// window's banner. Warnings still show. GENKIT_LOG_LEVEL, when set, wins:
211+// Init applies it after this call.
212+func quietGenkit() {
213+ logger.SetLevel(slog.LevelWarn)
214+}
215+
196216 // Probe: reachability on /models — the one endpoint every OpenAI-compatible
197217 // server has, and the one the fallback logic already relied on — then whatever
198218 // the server-specific probe adds. The config's context window always wins over
@@ -4,6 +4,7 @@ import (
4 "context"4 "context"
5 "encoding/json"5 "encoding/json"
6 "fmt"6 "fmt"
7+ "log/slog"
7 "net/http"8 "net/http"
8 "os"9 "os"
9 "strings"10 "strings"
@@ -12,6 +13,7 @@ import (
12 "mm/internal/config"13 "mm/internal/config"
13 14
14 "github.com/firebase/genkit/go/ai"15 "github.com/firebase/genkit/go/ai"
16+ "github.com/firebase/genkit/go/core/logger"
15 "github.com/firebase/genkit/go/genkit"17 "github.com/firebase/genkit/go/genkit"
16 "github.com/firebase/genkit/go/plugins/compat_oai"18 "github.com/firebase/genkit/go/plugins/compat_oai"
17 "github.com/openai/openai-go/option"19 "github.com/openai/openai-go/option"
@@ -185,14 +187,32 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str
185 // request asks for the token counts it would otherwise lack (usage.go).187 // request asks for the token counts it would otherwise lack (usage.go).
186 Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)},188 Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)},
187 }189 }
190+ quietGenkit()
188 g := genkit.Init(ctx, genkit.WithPlugins(client))191 g := genkit.Init(ctx, genkit.WithPlugins(client))
189- client.DefineModel(p.name, b.Model, ai.ModelOptions{192+ // Registered explicitly: since Genkit 1.13 DefineModel returns an
193+ // UNREGISTERED model, and an unregistered name is resolved by the plugin
194+ // with the OpenAI SDK's strict config schema, which rejects any sampling
195+ // key the SDK does not declare — chat_template_kwargs, the llama.cpp way
196+ // to turn reasoning off, first of all ("Additional property … is not
197+ // allowed"). DefineModel's model takes its config untyped and sends such
198+ // keys as JSON extras; NewModel, its non-deprecated successor, converts
199+ // the config to the SDK struct first and loses them. Ticket 76.
200+ genkit.RegisterAction(g, client.DefineModel(p.name, b.Model, ai.ModelOptions{ //nolint:staticcheck // see above: the only constructor that keeps provider-specific sampling keys
190 Label: b.Model,201 Label: b.Model,
191 Supports: &compat_oai.Multimodal, // enables Tools, Multiturn, SystemRole…202 Supports: &compat_oai.Multimodal, // enables Tools, Multiturn, SystemRole…
192- })203+ }).(*ai.ModelAction))
193 return g, p.name + "/" + b.Model, nil204 return g, p.name + "/" + b.Model, nil
194 }205 }
195 206
207+// quietGenkit keeps Genkit's informational log lines off the screen: since
208+// 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on
209+// stderr, which lands in the middle of the terminal's output and of the
210+// window's banner. Warnings still show. GENKIT_LOG_LEVEL, when set, wins:
211+// Init applies it after this call.
212+func quietGenkit() {
213+ logger.SetLevel(slog.LevelWarn)
214+}
215+
196 // Probe: reachability on /models — the one endpoint every OpenAI-compatible216 // Probe: reachability on /models — the one endpoint every OpenAI-compatible
197 // server has, and the one the fallback logic already relied on — then whatever217 // server has, and the one the fallback logic already relied on — then whatever
198 // the server-specific probe adds. The config's context window always wins over218 // the server-specific probe adds. The config's context window always wins over
modified internal/engine/provider_test.go +13 -0
@@ -9,6 +9,7 @@ package engine
99 import (
1010 "context"
1111 "encoding/json"
12+ "log/slog"
1213 "net/http"
1314 "net/http/httptest"
1415 "os"
@@ -18,6 +19,8 @@ import (
1819
1920 "mm/internal/config"
2021
22+ "github.com/firebase/genkit/go/core/logger"
23+
2124 "github.com/firebase/genkit/go/ai"
2225 )
2326
@@ -534,3 +537,13 @@ func TestProbeOllama(t *testing.T) {
534537 t.Errorf("missing model: %+v, want one warning naming ollama pull, window unknown", info)
535538 }
536539 }
540+
541+// Opening a provider leaves Genkit at warning level: its "Genkit initialized"
542+// INFO line would land in the terminal's output and the window's banner.
543+func TestOpenKeepsGenkitQuiet(t *testing.T) {
544+ t.Setenv("GENKIT_LOG_LEVEL", "")
545+ _ = engineOn(t, "http://127.0.0.1:1/v1")
546+ if got := logger.GetLevel(); got < slog.LevelWarn {
547+ t.Errorf("Genkit log level = %v after Open, want warn or above", got)
548+ }
549+}
@@ -9,6 +9,7 @@ package engine
9 import (9 import (
10 "context"10 "context"
11 "encoding/json"11 "encoding/json"
12+ "log/slog"
12 "net/http"13 "net/http"
13 "net/http/httptest"14 "net/http/httptest"
14 "os"15 "os"
@@ -18,6 +19,8 @@ import (
18 19
19 "mm/internal/config"20 "mm/internal/config"
20 21
22+ "github.com/firebase/genkit/go/core/logger"
23+
21 "github.com/firebase/genkit/go/ai"24 "github.com/firebase/genkit/go/ai"
22 )25 )
23 26
@@ -534,3 +537,13 @@ func TestProbeOllama(t *testing.T) {
534 t.Errorf("missing model: %+v, want one warning naming ollama pull, window unknown", info)537 t.Errorf("missing model: %+v, want one warning naming ollama pull, window unknown", info)
535 }538 }
536 }539 }
540+
541+// Opening a provider leaves Genkit at warning level: its "Genkit initialized"
542+// INFO line would land in the terminal's output and the window's banner.
543+func TestOpenKeepsGenkitQuiet(t *testing.T) {
544+ t.Setenv("GENKIT_LOG_LEVEL", "")
545+ _ = engineOn(t, "http://127.0.0.1:1/v1")
546+ if got := logger.GetLevel(); got < slog.LevelWarn {
547+ t.Errorf("Genkit log level = %v after Open, want warn or above", got)
548+ }
549+}
modified internal/engine/reasoning.go +38 -0
@@ -5,8 +5,10 @@ import (
55 "encoding/json"
66 "io"
77 "net/http"
8+ "slices"
89 "strings"
910
11+ "github.com/firebase/genkit/go/ai"
1012 "github.com/openai/openai-go/option"
1113 )
1214
@@ -34,6 +36,42 @@ import (
3436 // is carried on the REQUEST CONTEXT rather than in a package variable — so
3537 // two turns running at once cannot cross their reasoning.
3638
39+// dropReasoning removes the reasoning parts from a model response, in place.
40+// Since Genkit 1.13 the plugin reads `reasoning_content` itself and puts it
41+// in the response as a reasoning part (ticket 76). Kept, it would land in the
42+// history — Genkit builds the next model call of the turn from it, mm keeps
43+// it for the next question and the session file — and go back to the server
44+// as the assistant message's `reasoning_content`: the model would read its
45+// own thoughts, and pay for them in context. The reasoning is shown through
46+// the tap above; it never belongs to the conversation. A nil response, or one
47+// without a message, is left alone.
48+//
49+// resp, err := next(ctx, params)
50+// dropReasoning(resp)
51+func dropReasoning(resp *ai.ModelResponse) {
52+ if resp == nil || resp.Message == nil {
53+ return
54+ }
55+ resp.Message.Content = slices.DeleteFunc(resp.Message.Content, func(p *ai.Part) bool {
56+ return p.IsReasoning()
57+ })
58+}
59+
60+// onlyReasoning reports whether a streamed chunk carries reasoning and
61+// nothing else — no text, no tool call. Genkit 1.13 started handing such
62+// chunks to the streaming callback; earlier versions dropped them.
63+func onlyReasoning(chunk *ai.ModelResponseChunk) bool {
64+ if chunk == nil || len(chunk.Content) == 0 {
65+ return false
66+ }
67+ for _, p := range chunk.Content {
68+ if !p.IsReasoning() {
69+ return false
70+ }
71+ }
72+ return true
73+}
74+
3775 // reasonKey is the context key carrying the current turn's listener.
3876 type reasonKey struct{}
3977
@@ -5,8 +5,10 @@ import (
5 "encoding/json"5 "encoding/json"
6 "io"6 "io"
7 "net/http"7 "net/http"
8+ "slices"
8 "strings"9 "strings"
9 10
11+ "github.com/firebase/genkit/go/ai"
10 "github.com/openai/openai-go/option"12 "github.com/openai/openai-go/option"
11 )13 )
12 14
@@ -34,6 +36,42 @@ import (
34 // is carried on the REQUEST CONTEXT rather than in a package variable — so36 // is carried on the REQUEST CONTEXT rather than in a package variable — so
35 // two turns running at once cannot cross their reasoning.37 // two turns running at once cannot cross their reasoning.
36 38
39+// dropReasoning removes the reasoning parts from a model response, in place.
40+// Since Genkit 1.13 the plugin reads `reasoning_content` itself and puts it
41+// in the response as a reasoning part (ticket 76). Kept, it would land in the
42+// history — Genkit builds the next model call of the turn from it, mm keeps
43+// it for the next question and the session file — and go back to the server
44+// as the assistant message's `reasoning_content`: the model would read its
45+// own thoughts, and pay for them in context. The reasoning is shown through
46+// the tap above; it never belongs to the conversation. A nil response, or one
47+// without a message, is left alone.
48+//
49+// resp, err := next(ctx, params)
50+// dropReasoning(resp)
51+func dropReasoning(resp *ai.ModelResponse) {
52+ if resp == nil || resp.Message == nil {
53+ return
54+ }
55+ resp.Message.Content = slices.DeleteFunc(resp.Message.Content, func(p *ai.Part) bool {
56+ return p.IsReasoning()
57+ })
58+}
59+
60+// onlyReasoning reports whether a streamed chunk carries reasoning and
61+// nothing else — no text, no tool call. Genkit 1.13 started handing such
62+// chunks to the streaming callback; earlier versions dropped them.
63+func onlyReasoning(chunk *ai.ModelResponseChunk) bool {
64+ if chunk == nil || len(chunk.Content) == 0 {
65+ return false
66+ }
67+ for _, p := range chunk.Content {
68+ if !p.IsReasoning() {
69+ return false
70+ }
71+ }
72+ return true
73+}
74+
37 // reasonKey is the context key carrying the current turn's listener.75 // reasonKey is the context key carrying the current turn's listener.
38 type reasonKey struct{}76 type reasonKey struct{}
39 77
modified internal/engine/reasoning_test.go +77 -0
@@ -2,13 +2,20 @@ package engine
22
33 import (
44 "context"
5+ "encoding/json"
6+ "fmt"
57 "io"
68 "net/http"
79 "net/http/httptest"
810 "strings"
911 "sync"
1012 "testing"
13+ "time"
1114
15+ "mm/internal/config"
16+ "mm/internal/ui"
17+
18+ "github.com/firebase/genkit/go/ai"
1219 "github.com/openai/openai-go/option"
1320 )
1421
@@ -160,3 +167,73 @@ func TestWithReasoningIgnoresANilListener(t *testing.T) {
160167 }
161168
162169 var _ = option.WithMiddleware(reasoningTap) // the middleware still fits the option
170+
171+// The model never reads its own thoughts back: Genkit 1.13 puts the streamed
172+// reasoning into the final message as a reasoning part, and the history that
173+// part would land in is sent back as `reasoning_content` on the next request
174+// (ticket 76). The returned history carries none, and neither does the next
175+// request.
176+func TestReasoningStaysOutOfTheHistory(t *testing.T) {
177+ srv, bodies := recordingLlama(t, func(w http.ResponseWriter) {
178+ fmt.Fprint(w, sseChunkLine(`{"role":"assistant","reasoning_content":"let me think"}`, ""))
179+ fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, ""))
180+ fmt.Fprint(w, sseChunkLine(`{}`, "stop"))
181+ fmt.Fprint(w, "data: [DONE]\n\n")
182+ })
183+ e := engineOn(t, srv.URL+"/v1")
184+
185+ resp, history, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil)
186+ if err != nil {
187+ t.Fatalf("Generate: %v", err)
188+ }
189+ for _, m := range append(history, resp.Message) {
190+ for _, p := range m.Content {
191+ if p.IsReasoning() {
192+ t.Fatalf("a %s message keeps a reasoning part: %q", m.Role, p.Text)
193+ }
194+ }
195+ }
196+
197+ if _, _, err := e.Generate(context.Background(), append(history, ai.NewUserTextMessage("and 17*24?")), nil); err != nil {
198+ t.Fatalf("second Generate: %v", err)
199+ }
200+ got := bodies()
201+ if b, _ := json.Marshal(got[len(got)-1]["messages"]); strings.Contains(string(b), "reasoning_content") {
202+ t.Errorf("the next request sends the reasoning back: %s", b)
203+ }
204+}
205+
206+// One thought block per thought: Genkit 1.13 hands the streaming callback
207+// the chunks that carry nothing but reasoning, and each of them used to close
208+// the 💭 block the tap had just opened — one block per token on screen
209+// (ticket 76). The tap shows the reasoning; those chunks must not end it.
210+func TestReasoningStreamsAsOneBlock(t *testing.T) {
211+ srv, _ := recordingLlama(t, func(w http.ResponseWriter) {
212+ // One chunk at a time, as a server streams them: written together,
213+ // the tap reads them all before Genkit's callback runs and the bug
214+ // does not show.
215+ for _, word := range []string{"The", " user", " asks"} {
216+ fmt.Fprint(w, sseChunkLine(fmt.Sprintf(`{"reasoning_content":%q}`, word), ""))
217+ w.(http.Flusher).Flush()
218+ time.Sleep(20 * time.Millisecond)
219+ }
220+ fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, ""))
221+ fmt.Fprint(w, sseChunkLine(`{}`, "stop"))
222+ fmt.Fprint(w, "data: [DONE]\n\n")
223+ })
224+ e := engineOn(t, srv.URL+"/v1")
225+ var out strings.Builder
226+ oldOut, oldShow := ui.Out, config.Cfg.ShowThinking
227+ ui.Out, config.Cfg.ShowThinking = &out, true
228+ t.Cleanup(func() { ui.Out, config.Cfg.ShowThinking = oldOut, oldShow })
229+
230+ if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil); err != nil {
231+ t.Fatalf("Generate: %v", err)
232+ }
233+ if n := strings.Count(out.String(), "💭"); n != 1 {
234+ t.Errorf("%d thought blocks, want 1:\n%q", n, out.String())
235+ }
236+ if !strings.Contains(out.String(), "The user asks") {
237+ t.Errorf("the reasoning is not shown in one piece:\n%q", out.String())
238+ }
239+}
@@ -2,13 +2,20 @@ package engine
2 2
3 import (3 import (
4 "context"4 "context"
5+ "encoding/json"
6+ "fmt"
5 "io"7 "io"
6 "net/http"8 "net/http"
7 "net/http/httptest"9 "net/http/httptest"
8 "strings"10 "strings"
9 "sync"11 "sync"
10 "testing"12 "testing"
13+ "time"
11 14
15+ "mm/internal/config"
16+ "mm/internal/ui"
17+
18+ "github.com/firebase/genkit/go/ai"
12 "github.com/openai/openai-go/option"19 "github.com/openai/openai-go/option"
13 )20 )
14 21
@@ -160,3 +167,73 @@ func TestWithReasoningIgnoresANilListener(t *testing.T) {
160 }167 }
161 168
162 var _ = option.WithMiddleware(reasoningTap) // the middleware still fits the option169 var _ = option.WithMiddleware(reasoningTap) // the middleware still fits the option
170+
171+// The model never reads its own thoughts back: Genkit 1.13 puts the streamed
172+// reasoning into the final message as a reasoning part, and the history that
173+// part would land in is sent back as `reasoning_content` on the next request
174+// (ticket 76). The returned history carries none, and neither does the next
175+// request.
176+func TestReasoningStaysOutOfTheHistory(t *testing.T) {
177+ srv, bodies := recordingLlama(t, func(w http.ResponseWriter) {
178+ fmt.Fprint(w, sseChunkLine(`{"role":"assistant","reasoning_content":"let me think"}`, ""))
179+ fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, ""))
180+ fmt.Fprint(w, sseChunkLine(`{}`, "stop"))
181+ fmt.Fprint(w, "data: [DONE]\n\n")
182+ })
183+ e := engineOn(t, srv.URL+"/v1")
184+
185+ resp, history, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil)
186+ if err != nil {
187+ t.Fatalf("Generate: %v", err)
188+ }
189+ for _, m := range append(history, resp.Message) {
190+ for _, p := range m.Content {
191+ if p.IsReasoning() {
192+ t.Fatalf("a %s message keeps a reasoning part: %q", m.Role, p.Text)
193+ }
194+ }
195+ }
196+
197+ if _, _, err := e.Generate(context.Background(), append(history, ai.NewUserTextMessage("and 17*24?")), nil); err != nil {
198+ t.Fatalf("second Generate: %v", err)
199+ }
200+ got := bodies()
201+ if b, _ := json.Marshal(got[len(got)-1]["messages"]); strings.Contains(string(b), "reasoning_content") {
202+ t.Errorf("the next request sends the reasoning back: %s", b)
203+ }
204+}
205+
206+// One thought block per thought: Genkit 1.13 hands the streaming callback
207+// the chunks that carry nothing but reasoning, and each of them used to close
208+// the 💭 block the tap had just opened — one block per token on screen
209+// (ticket 76). The tap shows the reasoning; those chunks must not end it.
210+func TestReasoningStreamsAsOneBlock(t *testing.T) {
211+ srv, _ := recordingLlama(t, func(w http.ResponseWriter) {
212+ // One chunk at a time, as a server streams them: written together,
213+ // the tap reads them all before Genkit's callback runs and the bug
214+ // does not show.
215+ for _, word := range []string{"The", " user", " asks"} {
216+ fmt.Fprint(w, sseChunkLine(fmt.Sprintf(`{"reasoning_content":%q}`, word), ""))
217+ w.(http.Flusher).Flush()
218+ time.Sleep(20 * time.Millisecond)
219+ }
220+ fmt.Fprint(w, sseChunkLine(`{"content":"391"}`, ""))
221+ fmt.Fprint(w, sseChunkLine(`{}`, "stop"))
222+ fmt.Fprint(w, "data: [DONE]\n\n")
223+ })
224+ e := engineOn(t, srv.URL+"/v1")
225+ var out strings.Builder
226+ oldOut, oldShow := ui.Out, config.Cfg.ShowThinking
227+ ui.Out, config.Cfg.ShowThinking = &out, true
228+ t.Cleanup(func() { ui.Out, config.Cfg.ShowThinking = oldOut, oldShow })
229+
230+ if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("17*23?")}, nil); err != nil {
231+ t.Fatalf("Generate: %v", err)
232+ }
233+ if n := strings.Count(out.String(), "💭"); n != 1 {
234+ t.Errorf("%d thought blocks, want 1:\n%q", n, out.String())
235+ }
236+ if !strings.Contains(out.String(), "The user asks") {
237+ t.Errorf("the reasoning is not shown in one piece:\n%q", out.String())
238+ }
239+}
modified internal/engine/summary_test.go +31 -0
@@ -11,6 +11,8 @@ import (
1111 "sync"
1212 "testing"
1313
14+ "mm/internal/config"
15+
1416 "github.com/firebase/genkit/go/ai"
1517 )
1618
@@ -167,3 +169,32 @@ func TestStreamOptionsWithoutThinkingOllama(t *testing.T) {
167169 t.Errorf("a request that chose was changed: %s", got)
168170 }
169171 }
172+
173+// A sampling key the OpenAI SDK does not declare reaches the server as is:
174+// `chat_template_kwargs` is how a llama.cpp server is told, per request, not
175+// to reason — the YAML must be able to say it (ticket 76).
176+func TestSamplingKeysUnknownToTheSDKReachTheServer(t *testing.T) {
177+ srv, bodies := recordingLlama(t, goodAnswer)
178+ e := engineOn(t, srv.URL+"/v1")
179+ old := config.Cfg.Sampling
180+ t.Cleanup(func() { config.Cfg.Sampling = old })
181+ config.Cfg.Sampling = map[string]any{
182+ "temperature": 0.0,
183+ "chat_template_kwargs": map[string]any{"enable_thinking": false},
184+ }
185+
186+ if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("hi")}, nil); err != nil {
187+ t.Fatalf("Generate: %v", err)
188+ }
189+ got := bodies()
190+ if len(got) != 1 {
191+ t.Fatalf("%d request(s), want 1", len(got))
192+ }
193+ kw, _ := got[0]["chat_template_kwargs"].(map[string]any)
194+ if v, set := kw["enable_thinking"]; !set || v != false {
195+ t.Errorf("chat_template_kwargs = %v, want {enable_thinking: false}", got[0]["chat_template_kwargs"])
196+ }
197+ if got[0]["stream_options"] == nil {
198+ t.Error("mm's own middleware field stream_options is gone")
199+ }
200+}
@@ -11,6 +11,8 @@ import (
11 "sync"11 "sync"
12 "testing"12 "testing"
13 13
14+ "mm/internal/config"
15+
14 "github.com/firebase/genkit/go/ai"16 "github.com/firebase/genkit/go/ai"
15 )17 )
16 18
@@ -167,3 +169,32 @@ func TestStreamOptionsWithoutThinkingOllama(t *testing.T) {
167 t.Errorf("a request that chose was changed: %s", got)169 t.Errorf("a request that chose was changed: %s", got)
168 }170 }
169 }171 }
172+
173+// A sampling key the OpenAI SDK does not declare reaches the server as is:
174+// `chat_template_kwargs` is how a llama.cpp server is told, per request, not
175+// to reason — the YAML must be able to say it (ticket 76).
176+func TestSamplingKeysUnknownToTheSDKReachTheServer(t *testing.T) {
177+ srv, bodies := recordingLlama(t, goodAnswer)
178+ e := engineOn(t, srv.URL+"/v1")
179+ old := config.Cfg.Sampling
180+ t.Cleanup(func() { config.Cfg.Sampling = old })
181+ config.Cfg.Sampling = map[string]any{
182+ "temperature": 0.0,
183+ "chat_template_kwargs": map[string]any{"enable_thinking": false},
184+ }
185+
186+ if _, _, err := e.Generate(context.Background(), []*ai.Message{ai.NewUserTextMessage("hi")}, nil); err != nil {
187+ t.Fatalf("Generate: %v", err)
188+ }
189+ got := bodies()
190+ if len(got) != 1 {
191+ t.Fatalf("%d request(s), want 1", len(got))
192+ }
193+ kw, _ := got[0]["chat_template_kwargs"].(map[string]any)
194+ if v, set := kw["enable_thinking"]; !set || v != false {
195+ t.Errorf("chat_template_kwargs = %v, want {enable_thinking: false}", got[0]["chat_template_kwargs"])
196+ }
197+ if got[0]["stream_options"] == nil {
198+ t.Error("mm's own middleware field stream_options is gone")
199+ }
200+}
modified internal/engine/usage.go +6 -3
@@ -44,9 +44,12 @@ import (
4444 // {"enable_thinking": false}` the same 300 tokens were the summary. On a real
4545 // session (18k tokens of history, Qwen3.8-27B) the reasoning ate the whole
4646 // summaryMaxTokens and every compaction failed with "empty summary". Notes
47-// need no reasoning. It cannot go in the sampling config either: compat_oai
48-// maps the config onto openai.ChatCompletionNewParams, which has no such
49-// field. How to say it depends on the server (thinkingOff below): llama.cpp
47+// need no reasoning. It is added here, per request, rather than in the
48+// sampling config: the summary needs it whatever the YAML says, and the
49+// field's name depends on the server. (Until Genkit 1.12 the sampling config
50+// could not carry it at all — compat_oai mapped it onto
51+// openai.ChatCompletionNewParams, which has no such field, and dropped it;
52+// since then unknown keys travel as JSON extras, ticket 76.) How to say it depends on the server (thinkingOff below): llama.cpp
5053 // reads the template variable, and a template without it ignores it; Ollama
5154 // ignores chat_template_kwargs entirely (measured 2026-10-01, Ollama 0.35.0,
5255 // qwen3.5:2b: it reasoned through the whole budget) and obeys
@@ -44,9 +44,12 @@ import (
44 // {"enable_thinking": false}` the same 300 tokens were the summary. On a real44 // {"enable_thinking": false}` the same 300 tokens were the summary. On a real
45 // session (18k tokens of history, Qwen3.8-27B) the reasoning ate the whole45 // session (18k tokens of history, Qwen3.8-27B) the reasoning ate the whole
46 // summaryMaxTokens and every compaction failed with "empty summary". Notes46 // summaryMaxTokens and every compaction failed with "empty summary". Notes
47-// need no reasoning. It cannot go in the sampling config either: compat_oai47+// need no reasoning. It is added here, per request, rather than in the
48-// maps the config onto openai.ChatCompletionNewParams, which has no such48+// sampling config: the summary needs it whatever the YAML says, and the
49-// field. How to say it depends on the server (thinkingOff below): llama.cpp49+// field's name depends on the server. (Until Genkit 1.12 the sampling config
50+// could not carry it at all — compat_oai mapped it onto
51+// openai.ChatCompletionNewParams, which has no such field, and dropped it;
52+// since then unknown keys travel as JSON extras, ticket 76.) How to say it depends on the server (thinkingOff below): llama.cpp
50 // reads the template variable, and a template without it ignores it; Ollama53 // reads the template variable, and a template without it ignores it; Ollama
51 // ignores chat_template_kwargs entirely (measured 2026-10-01, Ollama 0.35.0,54 // ignores chat_template_kwargs entirely (measured 2026-10-01, Ollama 0.35.0,
52 // qwen3.5:2b: it reasoned through the whole budget) and obeys55 // qwen3.5:2b: it reasoned through the whole budget) and obeys
modified internal/ui/ui.go +7 -4
@@ -58,7 +58,8 @@ type Diff struct {
5858 }
5959
6060 // Sink receives the events of a turn. nil (the default) means the terminal
61-// front end: the tools print themselves and nobody asks for permission.
61+// front end: the tools print themselves, and ask — when they ask at all —
62+// through the line mode's prompt (consent.Terminal).
6263 type Sink interface {
6364 // Text is a chunk of the model's streamed answer.
6465 Text(text string)
@@ -69,9 +70,11 @@ type Sink interface {
6970 Reasoning(text string)
7071 // ToolStart announces a call before anything runs (ACP: status "pending").
7172 ToolStart(ev ToolEvent)
72- // Allow asks whether the call may run. The terminal front end has no
73- // gatekeeper and would always say yes; a client shows the user a dialog.
74- // ctx is the turn's context: a cancelled turn abandons the question.
73+ // Allow asks the user whether the call may run — a dialog, a prompt in
74+ // the box — remembering session-wide answers. Whether to ask at all is
75+ // decided BEFORE, by the caller: consent.Gate (yolo) for the built-in
76+ // tools, the MCP wrapper for its own. ctx is the turn's context: a
77+ // cancelled turn abandons the question.
7578 Allow(ctx context.Context, ev ToolEvent) bool
7679 // ToolRunning reports that the call was allowed and is executing.
7780 ToolRunning(id string)
@@ -58,7 +58,8 @@ type Diff struct {
58 }58 }
59 59
60 // Sink receives the events of a turn. nil (the default) means the terminal60 // Sink receives the events of a turn. nil (the default) means the terminal
61-// front end: the tools print themselves and nobody asks for permission.61+// front end: the tools print themselves, and ask — when they ask at all —
62+// through the line mode's prompt (consent.Terminal).
62 type Sink interface {63 type Sink interface {
63 // Text is a chunk of the model's streamed answer.64 // Text is a chunk of the model's streamed answer.
64 Text(text string)65 Text(text string)
@@ -69,9 +70,11 @@ type Sink interface {
69 Reasoning(text string)70 Reasoning(text string)
70 // ToolStart announces a call before anything runs (ACP: status "pending").71 // ToolStart announces a call before anything runs (ACP: status "pending").
71 ToolStart(ev ToolEvent)72 ToolStart(ev ToolEvent)
72- // Allow asks whether the call may run. The terminal front end has no73+ // Allow asks the user whether the call may run — a dialog, a prompt in
73- // gatekeeper and would always say yes; a client shows the user a dialog.74+ // the box — remembering session-wide answers. Whether to ask at all is
74- // ctx is the turn's context: a cancelled turn abandons the question.75+ // decided BEFORE, by the caller: consent.Gate (yolo) for the built-in
76+ // tools, the MCP wrapper for its own. ctx is the turn's context: a
77+ // cancelled turn abandons the question.
75 Allow(ctx context.Context, ev ToolEvent) bool78 Allow(ctx context.Context, ev ToolEvent) bool
76 // ToolRunning reports that the call was allowed and is executing.79 // ToolRunning reports that the call was allowed and is executing.
77 ToolRunning(id string)80 ToolRunning(id string)