bots-garden/mini-mepublic Fork
⑂ 931b4ab
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

📦 Mini Me

k33g committed 2026-09-17T08:30:43+02:00 Browse files
931b4ab parent: 5a8a838
added .tickets/issues/0016-check-and-review-the-compress-mechanism.yaml +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 16
3+title: Check and review the compress mechanism
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-17T06:17:29.481Z
9+updatedAt: 2026-09-17T06:17:29.481Z
10+labels: []
11+body: ""
12+tasks: []
13+comments: []
new file mode 100644
@@ -0,0 +1,13 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 16
3+title: Check and review the compress mechanism
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-17T06:17:29.481Z
9+updatedAt: 2026-09-17T06:17:29.481Z
10+labels: []
11+body: ""
12+tasks: []
13+comments: []
modified README.md +92 -1
@@ -1,4 +1,95 @@
11 # Mini Me
22
3-## ACP with Mini Me and Zed
3+> No, no. We don't gnaw on our kitty.
44
5+**Mini Me** (`mm`) is a minimalist coding agent for **local LLMs**, written in Go, that runs in your terminal or inside an editor such as **Zed**.
6+
7+## What is mini-me?
8+
9+Mini Me started as the preparation of a *Tools-in-Action* talk for **Devoxx Belgium 2026**, where I explain how an **agent loop** works with a single tool: `bash`. The model asks for a command, the agent runs it, the output goes back to the model, and so on until the model answers.
10+
11+From there it grew into the coding agent I wanted for local models (Docker Model Runner, llama.cpp): small enough to be read in an afternoon, and careful about what a small model does wrong:
12+
13+- **hallucinations** — the agent shows the real output of every command before the model comments on it, and the prompt forbids stating anything a command did not return;
14+- **silent failures** — a failing command, a refused edit or a rejected permission goes back to the model as text, so it reads why and tries something else;
15+- **endless loops** — the same action with the same result, repeated, is detected and the model is told to change approach; a watchdog catches a connection that went silent.
16+
17+What makes Mini Me a bit special is that it was designed from the start to be used **from an editor through the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)**: the same loop runs in a terminal REPL, in a full-screen terminal window, and inside Zed.
18+
19+## Install
20+
21+You need Go 1.25+ and a model server that can emit tool calls: Docker Model Runner (default) or Llamacpp.
22+
23+```bash
24+git clone https://git.rickub.com/bots-garden/mini-me.git
25+cd mini-me
26+./install.sh # go build, then copies mm to /usr/local/bin
27+mm -h
28+```
29+> `install.sh` works only on macOS and Linux
30+
31+Or download a binary from the [releases page](https://rickub.com/bots-garden/mini-me/releases) and put it on your `PATH`.
32+
33+Then pick a configuration file. Two are shipped: `agent.dmr.yaml` for Docker Model Runner and `agent.llamacpp.yaml` for llama.cpp. Copy one to `agent.yaml` in your project and adjust the `model:` and `system:` keys to taste.
34+
35+## Use
36+
37+The base command is `mm`. Run it **from the directory of the project you want to work on**: that is where commands run and where files are read and written.
38+
39+### 1. Terminal, line by line
40+
41+```bash
42+mm # reads ./agent.yaml
43+mm agent.dmr.yaml # or name the file
44+```
45+
46+Type a question at the `>` prompt. Each command the model runs is shown with its output, and every turn ends with a recap (`⚙ 2 command(s)`). Useful commands: `/help`, `/new`, `/compact`, `/quit`.
47+
48+### 2. Terminal, one window
49+
50+```bash
51+mm -tui
52+```
53+
54+The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn, **F1** lists the keys. With `permissions: ask` in the YAML, the agent asks before running a command or writing a file.
55+
56+### 3. Inside an editor (ACP)
57+
58+```bash
59+mm -acp
60+```
61+
62+Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand: the editor launches it. See the next section.
63+
64+## Use with the Zed editor
65+
66+1. Install `mm` on your `PATH` (see above).
67+2. In Zed, open `settings.json` and register the agent:
68+
69+ ```json
70+ {
71+ "agent_servers": {
72+ "mini-me": {
73+ "type": "custom",
74+ "command": "mm",
75+ "args": ["-acp"],
76+ "env": { "AGENT_CONFIG": "/absolute/path/to/agent.yaml" }
77+ }
78+ }
79+ }
80+ ```
81+
82+ `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required.
83+
84+3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and Zed asks you to allow it before it runs. File changes appear as real diffs.
85+4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu; `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back.
86+5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.
87+
88+### Other ACP editors
89+
90+Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (IntelliJ IDEA, PyCharm, WebStorm…), **Neovim** (CodeCompanion, agentic.nvim, avante.nvim), **Emacs** (agent-shell), **Visual Studio Code** through third-party ACP extensions, Sublime Text and Qt Creator through their ACP plugins. The registration details differ; the agent side does not.
91+
92+## Learn more
93+
94+- [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
95+- The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
@@ -1,4 +1,95 @@
1 # Mini Me1 # Mini Me
2 2
3-## ACP with Mini Me and Zed3+> No, no. We don't gnaw on our kitty.
4 4
5+**Mini Me** (`mm`) is a minimalist coding agent for **local LLMs**, written in Go, that runs in your terminal or inside an editor such as **Zed**.
6+
7+## What is mini-me?
8+
9+Mini Me started as the preparation of a *Tools-in-Action* talk for **Devoxx Belgium 2026**, where I explain how an **agent loop** works with a single tool: `bash`. The model asks for a command, the agent runs it, the output goes back to the model, and so on until the model answers.
10+
11+From there it grew into the coding agent I wanted for local models (Docker Model Runner, llama.cpp): small enough to be read in an afternoon, and careful about what a small model does wrong:
12+
13+- **hallucinations** — the agent shows the real output of every command before the model comments on it, and the prompt forbids stating anything a command did not return;
14+- **silent failures** — a failing command, a refused edit or a rejected permission goes back to the model as text, so it reads why and tries something else;
15+- **endless loops** — the same action with the same result, repeated, is detected and the model is told to change approach; a watchdog catches a connection that went silent.
16+
17+What makes Mini Me a bit special is that it was designed from the start to be used **from an editor through the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)**: the same loop runs in a terminal REPL, in a full-screen terminal window, and inside Zed.
18+
19+## Install
20+
21+You need Go 1.25+ and a model server that can emit tool calls: Docker Model Runner (default) or Llamacpp.
22+
23+```bash
24+git clone https://git.rickub.com/bots-garden/mini-me.git
25+cd mini-me
26+./install.sh # go build, then copies mm to /usr/local/bin
27+mm -h
28+```
29+> `install.sh` works only on macOS and Linux
30+
31+Or download a binary from the [releases page](https://rickub.com/bots-garden/mini-me/releases) and put it on your `PATH`.
32+
33+Then pick a configuration file. Two are shipped: `agent.dmr.yaml` for Docker Model Runner and `agent.llamacpp.yaml` for llama.cpp. Copy one to `agent.yaml` in your project and adjust the `model:` and `system:` keys to taste.
34+
35+## Use
36+
37+The base command is `mm`. Run it **from the directory of the project you want to work on**: that is where commands run and where files are read and written.
38+
39+### 1. Terminal, line by line
40+
41+```bash
42+mm # reads ./agent.yaml
43+mm agent.dmr.yaml # or name the file
44+```
45+
46+Type a question at the `>` prompt. Each command the model runs is shown with its output, and every turn ends with a recap (`⚙ 2 command(s)`). Useful commands: `/help`, `/new`, `/compact`, `/quit`.
47+
48+### 2. Terminal, one window
49+
50+```bash
51+mm -tui
52+```
53+
54+The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn, **F1** lists the keys. With `permissions: ask` in the YAML, the agent asks before running a command or writing a file.
55+
56+### 3. Inside an editor (ACP)
57+
58+```bash
59+mm -acp
60+```
61+
62+Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand: the editor launches it. See the next section.
63+
64+## Use with the Zed editor
65+
66+1. Install `mm` on your `PATH` (see above).
67+2. In Zed, open `settings.json` and register the agent:
68+
69+ ```json
70+ {
71+ "agent_servers": {
72+ "mini-me": {
73+ "type": "custom",
74+ "command": "mm",
75+ "args": ["-acp"],
76+ "env": { "AGENT_CONFIG": "/absolute/path/to/agent.yaml" }
77+ }
78+ }
79+ }
80+ ```
81+
82+ `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required.
83+
84+3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and Zed asks you to allow it before it runs. File changes appear as real diffs.
85+4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu; `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back.
86+5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.
87+
88+### Other ACP editors
89+
90+Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (IntelliJ IDEA, PyCharm, WebStorm…), **Neovim** (CodeCompanion, agentic.nvim, avante.nvim), **Emacs** (agent-shell), **Visual Studio Code** through third-party ACP extensions, Sublime Text and Qt Creator through their ACP plugins. The registration details differ; the agent side does not.
91+
92+## Learn more
93+
94+- [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
95+- The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
modified agent.dmr.yaml +30 -101
@@ -1,85 +1,44 @@
11 # Agent settings.
22 #
3-# Every key is optional: what you leave out keeps its built-in default
4-# (see internal/config/config.go). Nothing here requires recompiling —
5-# `go run .` picks the file up at startup.
6-#
7-# Use another file with: AGENT_CONFIG=./fast.yaml go run .
8-
9-# Which server is behind the URL. Left out, it means `dmr`: every agent.yaml
10-# written before this key existed keeps working unchanged. `llamacpp` speaks
11-# the same OpenAI-compatible protocol, but with its own port, its own way of
12-# refusing tool calls (no --jinja) and its own words in error messages — the
13-# provider is what turns those into one useful line. See agent.llamacpp.yaml.
14-# AGENT_PROVIDER overrides this key for one run.
153 provider: dmr
164
17-# The chat model, in the provider's own naming (DMR: `ai/…`, `hf.co/…`). It
18-# MUST be able to emit tool_calls, otherwise the agent never runs a single
19-# command. AGENT_MODEL overrides it.
205 model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
6+baseUrl: http://localhost:12434/engines/v1
7+fallback: http://host.docker.internal:12434/engines/v1
8+
219
22-# Endpoint. `baseUrl` is tried first; if it does not answer, `fallback` is used
23-# — handy from a container or a sandbox, where the host is reachable as
24-# host.docker.internal. Both are LEFT OUT here on purpose: each provider has its
25-# defaults, and an explicit value is always respected — so with DMR's URL
26-# written in this file, `AGENT_PROVIDER=llamacpp ./bob agent.yaml` still talked
27-# to port 12434. Leaving them out keeps the file provider-neutral. These are the
28-# values the `dmr` provider fills in:
29-# baseUrl: http://localhost:12434/engines/v1
30-# fallback: http://host.docker.internal:12434/engines/v1
31-# AGENT_BASE_URL — or, for dmr, the older DMR_BASE_URL — overrides both without
32-# probing. `fallback: ""` (an explicit empty string) disables the fallback.
33-
34-# The NAME of the environment variable holding the API key — never the key
35-# itself, so this file can be committed and shown on screen. DMR ignores the
36-# key, so nothing is needed here; llama-server checks one only when started
37-# with --api-key (default variable: LLAMA_API_KEY).
38-# apiKeyEnv: LLAMA_API_KEY
39-
40-# How many tokens the server actually serves. The model does not know this:
41-# it is what the operator started the server with (`llama-server -c 32768`,
42-# `docker model configure`). Shown at start-up next to its origin, because a
43-# number without an origin never gets corrected. 0 = ask the server
44-# (llama-server tells its n_ctx on /props), else "unknown".
10+# How many tokens the server actually serves.
11+# 0 = ask the server
4512 contextWindow: 0
4613
47-# Max number of characters a tool returns to the model. Context safeguard:
48-# beyond that, the output is truncated (beginning + end kept).
14+# Max number of characters a tool returns to the model.
15+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
4916 maxOutput: 16000
5017
51-# Max number of model ↔ tools round trips for a single question. Raise it for
52-# tasks that need long chains of commands.
18+# Max number of model <-> tools round trips for a single question.
19+# Raise it for tasks that need long chains of commands.
5320 maxTurns: 40
5421
55-# The built-in file tools: read_file, write_file, edit_file. This is the switch
56-# this part exists for. `true`: the model edits files through tools it can SEE
57-# in its tool list. `false`: the agent is part 09 again — bash and read_skill —
58-# and edits files through the `edit` CLI if it is on the PATH. Same binary,
59-# same prompts, two set-ups: measured side by side, that is the comparison
60-# (in part 07, `read_skill` as a tool was loaded 3/3 times where a catalogue
61-# in the prompt plus `cat` managed 1/11). Paths are relative to the current
62-# directory and are not confined to it.
22+# The `bash` tool.
23+# `false` removes it: the model can still read and edit files (editTools) and load skills,
24+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
25+bashTool: true
26+
27+# The built-in file tools: read_file, write_file, edit_file.
6328 editTools: true
6429
65-# Where the `read_skill` tool looks for its markdown procedures. The path is
66-# relative to the CURRENT DIRECTORY, so run the agent from the directory that
67-# holds skills/ — not from src/. When the directory has no *.md file, the tool
68-# is not declared at all.
30+# Where the `read_skill` tool looks for its markdown procedures.
31+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
6932 skillsDir: skills
7033
71-# Persistent sessions, per project. When enabled, every conversation is saved
72-# as one JSON file under `dir` (relative to the directory the agent is started
73-# from; the editor's project in ACP mode), rewritten after each turn. In the
74-# terminal, `/sessions` lists them and `/resume` continues the last one; in an
75-# editor, "Reload Agent" (ACP session/load) replays one. `/new` always starts
76-# a fresh session and file. Add `.mm/` to the project's .gitignore.
34+# Persistent sessions, per project.
35+# When enabled, every conversation is saved as one JSON file under `dir`
7736 sessions:
7837 enabled: false
7938 dir: .mm/sessions
8039
81-# How many lines of a command's output are echoed to the terminal. "Show me
82-# that file" means show it: running `cat` is not displaying it. 0 disables it.
40+# How many lines of a command's output are echoed to the terminal.
41+# 0 disables it.
8342 previewLines: 20
8443
8544 # The system prompt: what the agent is, and what it is allowed to do.
@@ -155,64 +114,34 @@ system: |
155114 # we want precise and reproducible answers, not creativity.
156115 sampling:
157116 temperature: 0.0
158- # Une seule commande par tour. Sans ça, le modèle peut en demander
159- # plusieurs d'un coup : Genkit les exécute EN PARALLÈLE, dans le même
160- # répertoire, et on ne sait plus quelle sortie appartient à quelle
161- # commande. Pour le détecteur de boucle, c'est pire : deux appels du même
162- # tour s'enregistrent dans un ordre indéterminé, et « la même action deux
163- # fois de suite » perd son sens. Mettre `true` pour retrouver le
164- # comportement par défaut de l'API.
165117 parallel_tool_calls: false
166118 top_p: 0.9
167119 max_tokens: 4096
168120
169121 watchdogTimeout: 30s
170122
171-# Context compression, carried over from part 08 (its CONTEXT_WINDOW.md has the
172-# reasoning). OFF by default: with `enabled: false` the agent behaves exactly
173-# as before. Why it exists: nothing in the agent ever shortens the history —
174-# measured with the fake engine of part 03, the messages sent to the model grew
175-# 2 → 5 → 7 → 9 over four requests, and every `bash` output (up to maxOutput
176-# characters) and every skill read stays until /quit. On a local model the
177-# window is fixed at load time and the prefill runs on the laptop, so a long
178-# session first shows up as a false "[watchdog: connection hang]", then as an
179-# error from the server.
180-#
181-# The window it measures against is the top-level `contextWindow` above — ONE
182-# key for the banner and for the trigger, so the two cannot disagree. When
183-# that key is 0, the value the provider's probe learned from the server is
184-# used (llama-server tells its n_ctx on /props; DMR tells nothing). When both
185-# are unknown, only `maxMessages` can trigger, and the agent says so at
186-# start-up.
123+# Context compression
187124 context:
188125 enabled: false
189126
190- # Compress when the history reaches this share of the window. 75 leaves a
191- # quarter for the next question, the outputs of its turns and the answer —
192- # a single `bash` output can be maxOutput characters, about 4-5k tokens.
127+ # Compress when the history reaches this share of the window.
193128 threshold: 75
194129
195- # Fallback on the message count, for when the window is unknown or the
196- # estimate is off. One command costs 2 messages (call + response), so 80 is
197- # roughly 30 commands of history.
130+ # Fallback on the message count, for when the window is unknown.
131+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
198132 maxMessages: 80
199133
200- # Question turns kept raw at the end. The recent turns are where the model
201- # works; summarising them makes it re-run what it just did. Three covers
202- # "do X", "now fix it", "and test it" — the usual shape of a demo.
134+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
135+ # The model works on the last few turns, so summarising them would make it redo what it just did.
203136 keepLastTurns: 3
204137
205- # max_tokens of the summary request. Seven sections of one line per item
206- # fit in far less; the cap stops a runaway model from filling the window it
207- # was asked to empty.
138+ # max_tokens of the summary request.
208139 summaryMaxTokens: 1200
209140
210- # Replaces the built-in summary prompt (08-context-compression/CONTEXT_WINDOW.md
211- # § 4.1) when set. Empty = built-in.
141+ # Replaces the built-in summary prompt when set.
142+ # Empty = built-in.
212143 prompt: ""
213144
214- # One line after each compression: what was replaced, what was kept,
215- # tokens before → after. Worth showing on a screen, noise in a log.
216145 showStats: true
217146
218147 displayCommands: true
@@ -1,85 +1,44 @@
1 # Agent settings.1 # Agent settings.
2 #2 #
3-# Every key is optional: what you leave out keeps its built-in default
4-# (see internal/config/config.go). Nothing here requires recompiling —
5-# `go run .` picks the file up at startup.
6-#
7-# Use another file with: AGENT_CONFIG=./fast.yaml go run .
8-
9-# Which server is behind the URL. Left out, it means `dmr`: every agent.yaml
10-# written before this key existed keeps working unchanged. `llamacpp` speaks
11-# the same OpenAI-compatible protocol, but with its own port, its own way of
12-# refusing tool calls (no --jinja) and its own words in error messages — the
13-# provider is what turns those into one useful line. See agent.llamacpp.yaml.
14-# AGENT_PROVIDER overrides this key for one run.
15 provider: dmr3 provider: dmr
16 4
17-# The chat model, in the provider's own naming (DMR: `ai/…`, `hf.co/…`). It
18-# MUST be able to emit tool_calls, otherwise the agent never runs a single
19-# command. AGENT_MODEL overrides it.
20 model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M5 model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
6+baseUrl: http://localhost:12434/engines/v1
7+fallback: http://host.docker.internal:12434/engines/v1
8+
21 9
22-# Endpoint. `baseUrl` is tried first; if it does not answer, `fallback` is used10+# How many tokens the server actually serves.
23-# — handy from a container or a sandbox, where the host is reachable as11+# 0 = ask the server
24-# host.docker.internal. Both are LEFT OUT here on purpose: each provider has its
25-# defaults, and an explicit value is always respected — so with DMR's URL
26-# written in this file, `AGENT_PROVIDER=llamacpp ./bob agent.yaml` still talked
27-# to port 12434. Leaving them out keeps the file provider-neutral. These are the
28-# values the `dmr` provider fills in:
29-# baseUrl: http://localhost:12434/engines/v1
30-# fallback: http://host.docker.internal:12434/engines/v1
31-# AGENT_BASE_URL — or, for dmr, the older DMR_BASE_URL — overrides both without
32-# probing. `fallback: ""` (an explicit empty string) disables the fallback.
33-
34-# The NAME of the environment variable holding the API key — never the key
35-# itself, so this file can be committed and shown on screen. DMR ignores the
36-# key, so nothing is needed here; llama-server checks one only when started
37-# with --api-key (default variable: LLAMA_API_KEY).
38-# apiKeyEnv: LLAMA_API_KEY
39-
40-# How many tokens the server actually serves. The model does not know this:
41-# it is what the operator started the server with (`llama-server -c 32768`,
42-# `docker model configure`). Shown at start-up next to its origin, because a
43-# number without an origin never gets corrected. 0 = ask the server
44-# (llama-server tells its n_ctx on /props), else "unknown".
45 contextWindow: 012 contextWindow: 0
46 13
47-# Max number of characters a tool returns to the model. Context safeguard:14+# Max number of characters a tool returns to the model.
48-# beyond that, the output is truncated (beginning + end kept).15+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
49 maxOutput: 1600016 maxOutput: 16000
50 17
51-# Max number of model ↔ tools round trips for a single question. Raise it for18+# Max number of model <-> tools round trips for a single question.
52-# tasks that need long chains of commands.19+# Raise it for tasks that need long chains of commands.
53 maxTurns: 4020 maxTurns: 40
54 21
55-# The built-in file tools: read_file, write_file, edit_file. This is the switch22+# The `bash` tool.
56-# this part exists for. `true`: the model edits files through tools it can SEE23+# `false` removes it: the model can still read and edit files (editTools) and load skills,
57-# in its tool list. `false`: the agent is part 09 again — bash and read_skill —24+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
58-# and edits files through the `edit` CLI if it is on the PATH. Same binary,25+bashTool: true
59-# same prompts, two set-ups: measured side by side, that is the comparison26+
60-# (in part 07, `read_skill` as a tool was loaded 3/3 times where a catalogue27+# The built-in file tools: read_file, write_file, edit_file.
61-# in the prompt plus `cat` managed 1/11). Paths are relative to the current
62-# directory and are not confined to it.
63 editTools: true28 editTools: true
64 29
65-# Where the `read_skill` tool looks for its markdown procedures. The path is30+# Where the `read_skill` tool looks for its markdown procedures.
66-# relative to the CURRENT DIRECTORY, so run the agent from the directory that31+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
67-# holds skills/ — not from src/. When the directory has no *.md file, the tool
68-# is not declared at all.
69 skillsDir: skills32 skillsDir: skills
70 33
71-# Persistent sessions, per project. When enabled, every conversation is saved34+# Persistent sessions, per project.
72-# as one JSON file under `dir` (relative to the directory the agent is started35+# When enabled, every conversation is saved as one JSON file under `dir`
73-# from; the editor's project in ACP mode), rewritten after each turn. In the
74-# terminal, `/sessions` lists them and `/resume` continues the last one; in an
75-# editor, "Reload Agent" (ACP session/load) replays one. `/new` always starts
76-# a fresh session and file. Add `.mm/` to the project's .gitignore.
77 sessions:36 sessions:
78 enabled: false37 enabled: false
79 dir: .mm/sessions38 dir: .mm/sessions
80 39
81-# How many lines of a command's output are echoed to the terminal. "Show me40+# How many lines of a command's output are echoed to the terminal.
82-# that file" means show it: running `cat` is not displaying it. 0 disables it.41+# 0 disables it.
83 previewLines: 2042 previewLines: 20
84 43
85 # The system prompt: what the agent is, and what it is allowed to do.44 # The system prompt: what the agent is, and what it is allowed to do.
@@ -155,64 +114,34 @@ system: |
155 # we want precise and reproducible answers, not creativity.114 # we want precise and reproducible answers, not creativity.
156 sampling:115 sampling:
157 temperature: 0.0116 temperature: 0.0
158- # Une seule commande par tour. Sans ça, le modèle peut en demander
159- # plusieurs d'un coup : Genkit les exécute EN PARALLÈLE, dans le même
160- # répertoire, et on ne sait plus quelle sortie appartient à quelle
161- # commande. Pour le détecteur de boucle, c'est pire : deux appels du même
162- # tour s'enregistrent dans un ordre indéterminé, et « la même action deux
163- # fois de suite » perd son sens. Mettre `true` pour retrouver le
164- # comportement par défaut de l'API.
165 parallel_tool_calls: false117 parallel_tool_calls: false
166 top_p: 0.9118 top_p: 0.9
167 max_tokens: 4096119 max_tokens: 4096
168 120
169 watchdogTimeout: 30s121 watchdogTimeout: 30s
170 122
171-# Context compression, carried over from part 08 (its CONTEXT_WINDOW.md has the123+# Context compression
172-# reasoning). OFF by default: with `enabled: false` the agent behaves exactly
173-# as before. Why it exists: nothing in the agent ever shortens the history —
174-# measured with the fake engine of part 03, the messages sent to the model grew
175-# 2 → 5 → 7 → 9 over four requests, and every `bash` output (up to maxOutput
176-# characters) and every skill read stays until /quit. On a local model the
177-# window is fixed at load time and the prefill runs on the laptop, so a long
178-# session first shows up as a false "[watchdog: connection hang]", then as an
179-# error from the server.
180-#
181-# The window it measures against is the top-level `contextWindow` above — ONE
182-# key for the banner and for the trigger, so the two cannot disagree. When
183-# that key is 0, the value the provider's probe learned from the server is
184-# used (llama-server tells its n_ctx on /props; DMR tells nothing). When both
185-# are unknown, only `maxMessages` can trigger, and the agent says so at
186-# start-up.
187 context:124 context:
188 enabled: false125 enabled: false
189 126
190- # Compress when the history reaches this share of the window. 75 leaves a127+ # Compress when the history reaches this share of the window.
191- # quarter for the next question, the outputs of its turns and the answer —
192- # a single `bash` output can be maxOutput characters, about 4-5k tokens.
193 threshold: 75128 threshold: 75
194 129
195- # Fallback on the message count, for when the window is unknown or the130+ # Fallback on the message count, for when the window is unknown.
196- # estimate is off. One command costs 2 messages (call + response), so 80 is131+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
197- # roughly 30 commands of history.
198 maxMessages: 80132 maxMessages: 80
199 133
200- # Question turns kept raw at the end. The recent turns are where the model134+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
201- # works; summarising them makes it re-run what it just did. Three covers135+ # The model works on the last few turns, so summarising them would make it redo what it just did.
202- # "do X", "now fix it", "and test it" — the usual shape of a demo.
203 keepLastTurns: 3136 keepLastTurns: 3
204 137
205- # max_tokens of the summary request. Seven sections of one line per item138+ # max_tokens of the summary request.
206- # fit in far less; the cap stops a runaway model from filling the window it
207- # was asked to empty.
208 summaryMaxTokens: 1200139 summaryMaxTokens: 1200
209 140
210- # Replaces the built-in summary prompt (08-context-compression/CONTEXT_WINDOW.md141+ # Replaces the built-in summary prompt when set.
211- # § 4.1) when set. Empty = built-in.142+ # Empty = built-in.
212 prompt: ""143 prompt: ""
213 144
214- # One line after each compression: what was replaced, what was kept,
215- # tokens before → after. Worth showing on a screen, noise in a log.
216 showStats: true145 showStats: true
217 146
218 displayCommands: true147 displayCommands: true
modified agent.llamacpp.yaml +44 -73
@@ -1,91 +1,59 @@
1-# Same agent, served by llama.cpp's `llama-server` instead of Docker Model Runner.
1+# Agent settings — same agent, served by llama.cpp's `llama-server` instead of Docker Model Runner.
22 #
3-# llama-server -hf poolside/Laguna-XS-2.1-GGUF:Q4_K_M \
4-# -a poolside/Laguna-XS-2.1-GGUF:Q4_K_M --jinja -c 32768 --port 8080
5-# ./bob agent.llamacpp.yaml # from demo/, so that skills/ is found
3+# llama serve -hf JetBrains/Mellum2-12B-A2.5B-Instruct-GGUF-Q4_K_M:Q4_K_M
64 #
7-# `-hf <user>/<repo>:<quant>` downloads the GGUF from Hugging Face (the quant tag
8-# is optional and defaults to Q4_K_M; here the repo has exactly one such file,
9-# Laguna-XS-2.1-Q4_K_M.gguf, 20.3 GB). `-a` gives the served model the SAME name
10-# as the `model:` key below: llama-server routes requests on the `model` field,
11-# and without an alias it exposes the file's name on /v1/models, not the repo's.
12-# `--jinja` is not optional: without it llama-server refuses the `tools`
13-# parameter, and this agent is nothing but tool calls. The agent recognises that
14-# refusal and says so in one line instead of printing the server's stack.
15-# `-c` is the context the server SERVES (the model accepts up to 262,144); it is
16-# what shows up under `ctx:` at start-up, read from GET /props.
17-# Laguna XS 2.1 is a 33B MoE with 3B active parameters, built for coding; its
18-# card says the chat template does tool calling under --jinja, and that it
19-# needs a recent llama.cpp (the card points at PR #25165 — check it is merged
20-# in your build, or build that branch).
21-
225 provider: llamacpp
236
24-# Must match what GET /v1/models returns — the `-a` alias above. llama-server
25-# routes on this field; a name it does not serve is an error, not a fallback.
26-#model: poolside/Laguna-XS-2.1-GGUF:Q4_K_M
27-#model: unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M
287 model: jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
298
309 # llama-server's default is 127.0.0.1:8080; the OpenAI routes sit under /v1.
31-# Leave it out to get exactly this value.
3210 baseUrl: http://127.0.0.1:8080/v1
3311
34-# No fallback here: llama-server is wherever you started it.
12+# No fallback: llama-server is wherever you started it.
3513 fallback: ""
3614
3715 # Only when llama-server was started with --api-key; unset otherwise.
3816 # apiKeyEnv: LLAMA_API_KEY
3917
40-# The built-in file tools: read_file, write_file, edit_file. This is the switch
41-# this part exists for. `true`: the model edits files through tools it can SEE
42-# in its tool list. `false`: the agent is part 09 again — bash and read_skill —
43-# and edits files through the `edit` CLI if it is on the PATH. Same binary,
44-# same prompts, two set-ups: measured side by side, that is the comparison
45-# (in part 07, `read_skill` as a tool was loaded 3/3 times where a catalogue
46-# in the prompt plus `cat` managed 1/11). Paths are relative to the current
47-# directory and are not confined to it.
18+# The `bash` tool.
19+# `false` removes it: the model can still read and edit files (editTools) and load skills,
20+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
21+bashTool: true
22+
23+# The built-in file tools: read_file, write_file, edit_file.
4824 editTools: true
4925
50-# 0 = read the served size from /props. Set it when the server hides /props
51-# (a reverse proxy) or when you know better.
26+# How many tokens the server actually serves.
27+# 0 = ask the server (llama-server tells it on /props)
5228 contextWindow: 0
5329
30+# Max number of characters a tool returns to the model.
31+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
5432 maxOutput: 16000
33+
34+# Max number of model <-> tools round trips for a single question.
35+# Raise it for tasks that need long chains of commands.
5536 maxTurns: 40
37+
38+# Where the `read_skill` tool looks for its markdown procedures.
39+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
5640 skillsDir: skills
5741
58-# Persistent sessions, per project — OFF by default.
59-#
60-# How to turn them on:
61-#
62-# 1. Set `enabled: true` below (keep `dir` unless you want another place).
63-# 2. Restart the agent from the project directory:
64-# mm agent.llamacpp.yaml
65-# or, from Zed, point AGENT_CONFIG at this file (absolute path) and use
66-# "Reload Agent" / open a new thread so the new process reads the key.
67-# 3. Check the banner: it now shows a `sessions:` line with the directory
68-# and the number of earlier sessions found there.
69-# 4. Add `.mm/` to the project's .gitignore (already done in this repository).
70-#
71-# What it does once on: every conversation is saved as one JSON file
72-# `<dir>/<id>.json`, rewritten after each turn, `/compact` and `/new`. `dir`
73-# is relative to the directory the agent is started from — the editor's
74-# project in ACP mode — so each project keeps its own sessions; an absolute
75-# path works too. A start always opens a NEW session: in the terminal,
76-# `/sessions` lists the stored ones and `/resume` continues the latest
77-# (`/resume <id>` a given one); in an editor, "Reload Agent" (ACP
78-# session/load) replays the whole thread, tool calls included, without
79-# running anything again. `/new` always starts a fresh session and file.
80-# Write failures are logged, never fatal; with `enabled: false` nothing is
81-# written and `/sessions` says so.
42+# Persistent sessions, per project.
43+# When enabled, every conversation is saved as one JSON file under `dir`.
44+# `/sessions` lists them, `/resume` continues one; in an editor, "Reload Agent" replays the thread.
8245 sessions:
8346 enabled: true
8447 dir: .mm/sessions
8548
49+# How many lines of a command's output are echoed to the terminal.
50+# 0 disables it.
8651 previewLines: 20
52+
8753 displayCommands: true
8854
55+# The system prompt: what the agent is, and what it is allowed to do.
56+# This is the knob to play with — it is the shortest path to changing behaviour.
8957 system: |
9058 Your name is Bob.
9159 You are a coding agent working in a terminal.
@@ -153,32 +121,35 @@ system: |
153121 rule you cannot guess. Stop every job you started before you finish, and say
154122 which ones you left running.
155123
156-# Same sampling as the DMR file. llama-server honours `parallel_tool_calls`
157-# (off by default on its side too) and `max_tokens`.
124+# Generation settings (OpenAI API keys). Low temperature for a coding agent:
125+# we want precise and reproducible answers, not creativity.
158126 sampling:
159127 temperature: 0.0
160128 parallel_tool_calls: false
161129 top_p: 0.9
162130 max_tokens: 4096
163131
164-# llama-server processes the whole prompt before the first token; on a laptop a
165-# 32k context can take a while. Same watchdog as DMR, raise it if it fires.
132+# llama-server processes the whole prompt before the first token;
133+# on a laptop a 32k context can take a while. Raise it if it fires.
166134 watchdogTimeout: 30s
167135
168-# Context compression (from part 08). ON here, because this is the one set-up
169-# where the agent knows the window without being told: `contextWindow: 0`
170-# above means "read n_ctx from /props", and that number is what `threshold`
171-# applies to — so the banner's `ctx: 32768 (/props)` and the trigger agree by
172-# construction. With a 32k window and a 33B MoE, the whole history is
173-# re-read at every turn; compressing at 75 % keeps the prefill — and the
174-# watchdog — inside the 30 s above. Set `enabled: false` to get the exact
175-# part-07 behaviour back; `/compact` still works.
136+# Context compression.
137+# ON here: the agent knows the window (read from /props), so `threshold` has a number to apply to.
176138 context:
177139 enabled: true
140+
141+ # Compress when the history reaches this share of the window.
178142 threshold: 75
179- # Kept as a net for a reverse proxy that hides /props: then the window is
180- # unknown and only this can trigger.
143+
144+ # Fallback on the message count, for when the window is unknown.
145+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
181146 maxMessages: 80
147+
148+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
149+ # The model works on the last few turns, so summarising them would make it redo what it just did.
182150 keepLastTurns: 3
151+
152+ # max_tokens of the summary request.
183153 summaryMaxTokens: 1200
154+
184155 showStats: true
@@ -1,91 +1,59 @@
1-# Same agent, served by llama.cpp's `llama-server` instead of Docker Model Runner.1+# Agent settings — same agent, served by llama.cpp's `llama-server` instead of Docker Model Runner.
2 #2 #
3-# llama-server -hf poolside/Laguna-XS-2.1-GGUF:Q4_K_M \3+# llama serve -hf JetBrains/Mellum2-12B-A2.5B-Instruct-GGUF-Q4_K_M:Q4_K_M
4-# -a poolside/Laguna-XS-2.1-GGUF:Q4_K_M --jinja -c 32768 --port 8080
5-# ./bob agent.llamacpp.yaml # from demo/, so that skills/ is found
6 #4 #
7-# `-hf <user>/<repo>:<quant>` downloads the GGUF from Hugging Face (the quant tag
8-# is optional and defaults to Q4_K_M; here the repo has exactly one such file,
9-# Laguna-XS-2.1-Q4_K_M.gguf, 20.3 GB). `-a` gives the served model the SAME name
10-# as the `model:` key below: llama-server routes requests on the `model` field,
11-# and without an alias it exposes the file's name on /v1/models, not the repo's.
12-# `--jinja` is not optional: without it llama-server refuses the `tools`
13-# parameter, and this agent is nothing but tool calls. The agent recognises that
14-# refusal and says so in one line instead of printing the server's stack.
15-# `-c` is the context the server SERVES (the model accepts up to 262,144); it is
16-# what shows up under `ctx:` at start-up, read from GET /props.
17-# Laguna XS 2.1 is a 33B MoE with 3B active parameters, built for coding; its
18-# card says the chat template does tool calling under --jinja, and that it
19-# needs a recent llama.cpp (the card points at PR #25165 — check it is merged
20-# in your build, or build that branch).
21-
22 provider: llamacpp5 provider: llamacpp
23 6
24-# Must match what GET /v1/models returns — the `-a` alias above. llama-server
25-# routes on this field; a name it does not serve is an error, not a fallback.
26-#model: poolside/Laguna-XS-2.1-GGUF:Q4_K_M
27-#model: unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M
28 model: jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M7 model: jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
29 8
30 # llama-server's default is 127.0.0.1:8080; the OpenAI routes sit under /v1.9 # llama-server's default is 127.0.0.1:8080; the OpenAI routes sit under /v1.
31-# Leave it out to get exactly this value.
32 baseUrl: http://127.0.0.1:8080/v110 baseUrl: http://127.0.0.1:8080/v1
33 11
34-# No fallback here: llama-server is wherever you started it.12+# No fallback: llama-server is wherever you started it.
35 fallback: ""13 fallback: ""
36 14
37 # Only when llama-server was started with --api-key; unset otherwise.15 # Only when llama-server was started with --api-key; unset otherwise.
38 # apiKeyEnv: LLAMA_API_KEY16 # apiKeyEnv: LLAMA_API_KEY
39 17
40-# The built-in file tools: read_file, write_file, edit_file. This is the switch18+# The `bash` tool.
41-# this part exists for. `true`: the model edits files through tools it can SEE19+# `false` removes it: the model can still read and edit files (editTools) and load skills,
42-# in its tool list. `false`: the agent is part 09 again — bash and read_skill —20+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
43-# and edits files through the `edit` CLI if it is on the PATH. Same binary,21+bashTool: true
44-# same prompts, two set-ups: measured side by side, that is the comparison22+
45-# (in part 07, `read_skill` as a tool was loaded 3/3 times where a catalogue23+# The built-in file tools: read_file, write_file, edit_file.
46-# in the prompt plus `cat` managed 1/11). Paths are relative to the current
47-# directory and are not confined to it.
48 editTools: true24 editTools: true
49 25
50-# 0 = read the served size from /props. Set it when the server hides /props26+# How many tokens the server actually serves.
51-# (a reverse proxy) or when you know better.27+# 0 = ask the server (llama-server tells it on /props)
52 contextWindow: 028 contextWindow: 0
53 29
30+# Max number of characters a tool returns to the model.
31+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
54 maxOutput: 1600032 maxOutput: 16000
33+
34+# Max number of model <-> tools round trips for a single question.
35+# Raise it for tasks that need long chains of commands.
55 maxTurns: 4036 maxTurns: 40
37+
38+# Where the `read_skill` tool looks for its markdown procedures.
39+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
56 skillsDir: skills40 skillsDir: skills
57 41
58-# Persistent sessions, per project — OFF by default.42+# Persistent sessions, per project.
59-#43+# When enabled, every conversation is saved as one JSON file under `dir`.
60-# How to turn them on:44+# `/sessions` lists them, `/resume` continues one; in an editor, "Reload Agent" replays the thread.
61-#
62-# 1. Set `enabled: true` below (keep `dir` unless you want another place).
63-# 2. Restart the agent from the project directory:
64-# mm agent.llamacpp.yaml
65-# or, from Zed, point AGENT_CONFIG at this file (absolute path) and use
66-# "Reload Agent" / open a new thread so the new process reads the key.
67-# 3. Check the banner: it now shows a `sessions:` line with the directory
68-# and the number of earlier sessions found there.
69-# 4. Add `.mm/` to the project's .gitignore (already done in this repository).
70-#
71-# What it does once on: every conversation is saved as one JSON file
72-# `<dir>/<id>.json`, rewritten after each turn, `/compact` and `/new`. `dir`
73-# is relative to the directory the agent is started from — the editor's
74-# project in ACP mode — so each project keeps its own sessions; an absolute
75-# path works too. A start always opens a NEW session: in the terminal,
76-# `/sessions` lists the stored ones and `/resume` continues the latest
77-# (`/resume <id>` a given one); in an editor, "Reload Agent" (ACP
78-# session/load) replays the whole thread, tool calls included, without
79-# running anything again. `/new` always starts a fresh session and file.
80-# Write failures are logged, never fatal; with `enabled: false` nothing is
81-# written and `/sessions` says so.
82 sessions:45 sessions:
83 enabled: true46 enabled: true
84 dir: .mm/sessions47 dir: .mm/sessions
85 48
49+# How many lines of a command's output are echoed to the terminal.
50+# 0 disables it.
86 previewLines: 2051 previewLines: 20
52+
87 displayCommands: true53 displayCommands: true
88 54
55+# The system prompt: what the agent is, and what it is allowed to do.
56+# This is the knob to play with — it is the shortest path to changing behaviour.
89 system: |57 system: |
90 Your name is Bob.58 Your name is Bob.
91 You are a coding agent working in a terminal.59 You are a coding agent working in a terminal.
@@ -153,32 +121,35 @@ system: |
153 rule you cannot guess. Stop every job you started before you finish, and say121 rule you cannot guess. Stop every job you started before you finish, and say
154 which ones you left running.122 which ones you left running.
155 123
156-# Same sampling as the DMR file. llama-server honours `parallel_tool_calls`124+# Generation settings (OpenAI API keys). Low temperature for a coding agent:
157-# (off by default on its side too) and `max_tokens`.125+# we want precise and reproducible answers, not creativity.
158 sampling:126 sampling:
159 temperature: 0.0127 temperature: 0.0
160 parallel_tool_calls: false128 parallel_tool_calls: false
161 top_p: 0.9129 top_p: 0.9
162 max_tokens: 4096130 max_tokens: 4096
163 131
164-# llama-server processes the whole prompt before the first token; on a laptop a132+# llama-server processes the whole prompt before the first token;
165-# 32k context can take a while. Same watchdog as DMR, raise it if it fires.133+# on a laptop a 32k context can take a while. Raise it if it fires.
166 watchdogTimeout: 30s134 watchdogTimeout: 30s
167 135
168-# Context compression (from part 08). ON here, because this is the one set-up136+# Context compression.
169-# where the agent knows the window without being told: `contextWindow: 0`137+# ON here: the agent knows the window (read from /props), so `threshold` has a number to apply to.
170-# above means "read n_ctx from /props", and that number is what `threshold`
171-# applies to — so the banner's `ctx: 32768 (/props)` and the trigger agree by
172-# construction. With a 32k window and a 33B MoE, the whole history is
173-# re-read at every turn; compressing at 75 % keeps the prefill — and the
174-# watchdog — inside the 30 s above. Set `enabled: false` to get the exact
175-# part-07 behaviour back; `/compact` still works.
176 context:138 context:
177 enabled: true139 enabled: true
140+
141+ # Compress when the history reaches this share of the window.
178 threshold: 75142 threshold: 75
179- # Kept as a net for a reverse proxy that hides /props: then the window is143+
180- # unknown and only this can trigger.144+ # Fallback on the message count, for when the window is unknown.
145+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
181 maxMessages: 80146 maxMessages: 80
147+
148+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
149+ # The model works on the last few turns, so summarising them would make it redo what it just did.
182 keepLastTurns: 3150 keepLastTurns: 3
151+
152+ # max_tokens of the summary request.
183 summaryMaxTokens: 1200153 summaryMaxTokens: 1200
154+
184 showStats: true155 showStats: true
modified docs/en/reference/cli.md +1 -1
@@ -43,7 +43,7 @@ Printed on stdout in terminal mode, on stderr in ACP mode. Dimmed when stdout is
4343
4444 | Line | Content |
4545 |------|---------|
46-| `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger. |
46+| `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all. |
4747 | `config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N` | Configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found. |
4848 | `Agent ready. Commands: …` | Terminal mode only. |
4949 | `Shortcut: Ctrl+C …` | Terminal mode only. |
@@ -43,7 +43,7 @@ Printed on stdout in terminal mode, on stderr in ACP mode. Dimmed when stdout is
43 43
44 | Line | Content |44 | Line | Content |
45 |------|---------|45 |------|---------|
46-| `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger. |46+| `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all. |
47 | `config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N` | Configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found. |47 | `config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N` | Configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found. |
48 | `Agent ready. Commands: …` | Terminal mode only. |48 | `Agent ready. Commands: …` | Terminal mode only. |
49 | `Shortcut: Ctrl+C …` | Terminal mode only. |49 | `Shortcut: Ctrl+C …` | Terminal mode only. |
modified docs/en/reference/configuration.md +1 -0
@@ -18,6 +18,7 @@
1818 | `displayCommands` | bool | `false` | Print the numbered list of commands and file operations after each answer. |
1919 | `system` | string | built-in prompt | The system prompt, first message of every history. |
2020 | `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+| `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. |
2122 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
2223 | `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`. |
2324 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
@@ -18,6 +18,7 @@
18 | `displayCommands` | bool | `false` | Print the numbered list of commands and file operations after each answer. |18 | `displayCommands` | bool | `false` | Print the numbered list of commands and file operations after each answer. |
19 | `system` | string | built-in prompt | The system prompt, first message of every history. |19 | `system` | string | built-in prompt | The system prompt, first message of every history. |
20 | `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. |20 | `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+| `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. |
21 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |22 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
22 | `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`. |23 | `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`. |
23 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |24 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
modified docs/en/reference/tools.md +1 -1
@@ -4,7 +4,7 @@
44
55 ## `bash`
66
7-Always declared.
7+Declared when `bashTool: true` (the default).
88
99 | Field | Type | Description |
1010 |-------|------|-------------|
@@ -4,7 +4,7 @@
4 4
5 ## `bash`5 ## `bash`
6 6
7-Always declared.7+Declared when `bashTool: true` (the default).
8 8
9 | Field | Type | Description |9 | Field | Type | Description |
10 |-------|------|-------------|10 |-------|------|-------------|
modified docs/fr/reference/cli.md +1 -1
@@ -43,7 +43,7 @@ Affichée sur stdout en mode terminal, sur stderr en mode ACP. Grisée quand std
4343
4444 | Ligne | Contenu |
4545 |-------|---------|
46-| `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher. |
46+| `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré. |
4747 | `config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N` | Chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés. |
4848 | `Agent ready. Commands: …` | Mode terminal seulement. |
4949 | `Shortcut: Ctrl+C …` | Mode terminal seulement. |
@@ -43,7 +43,7 @@ Affichée sur stdout en mode terminal, sur stderr en mode ACP. Grisée quand std
43 43
44 | Ligne | Contenu |44 | Ligne | Contenu |
45 |-------|---------|45 |-------|---------|
46-| `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher. |46+| `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré. |
47 | `config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N` | Chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés. |47 | `config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N` | Chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés. |
48 | `Agent ready. Commands: …` | Mode terminal seulement. |48 | `Agent ready. Commands: …` | Mode terminal seulement. |
49 | `Shortcut: Ctrl+C …` | Mode terminal seulement. |49 | `Shortcut: Ctrl+C …` | Mode terminal seulement. |
modified docs/fr/reference/configuration.md +1 -0
@@ -18,6 +18,7 @@
1818 | `displayCommands` | booléen | `false` | Affiche la liste numérotée des commandes et opérations sur fichiers après chaque réponse. |
1919 | `system` | chaîne | prompt intégré | Le prompt système, premier message de chaque historique. |
2020 | `skillsDir` | chaîne | `skills` | Répertoire des skills, sous forme de fichiers `<nom>.md` ou de répertoires `<nom>/SKILL.md`. Un chemin relatif est résolu depuis le répertoire de lancement de `mm` (mode terminal, ou mode ACP sans fichier de configuration), ou à côté du fichier de configuration (mode ACP avec fichier) ; la bannière avertit avec le chemin absolu quand rien n'y est trouvé. |
21+| `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. |
2122 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |
2223 | `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`. |
2324 | `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é. |
@@ -18,6 +18,7 @@
18 | `displayCommands` | booléen | `false` | Affiche la liste numérotée des commandes et opérations sur fichiers après chaque réponse. |18 | `displayCommands` | booléen | `false` | Affiche la liste numérotée des commandes et opérations sur fichiers après chaque réponse. |
19 | `system` | chaîne | prompt intégré | Le prompt système, premier message de chaque historique. |19 | `system` | chaîne | prompt intégré | Le prompt système, premier message de chaque historique. |
20 | `skillsDir` | chaîne | `skills` | Répertoire des skills, sous forme de fichiers `<nom>.md` ou de répertoires `<nom>/SKILL.md`. Un chemin relatif est résolu depuis le répertoire de lancement de `mm` (mode terminal, ou mode ACP sans fichier de configuration), ou à côté du fichier de configuration (mode ACP avec fichier) ; la bannière avertit avec le chemin absolu quand rien n'y est trouvé. |20 | `skillsDir` | chaîne | `skills` | Répertoire des skills, sous forme de fichiers `<nom>.md` ou de répertoires `<nom>/SKILL.md`. Un chemin relatif est résolu depuis le répertoire de lancement de `mm` (mode terminal, ou mode ACP sans fichier de configuration), ou à côté du fichier de configuration (mode ACP avec fichier) ; la bannière avertit avec le chemin absolu quand rien n'y est trouvé. |
21+| `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. |
21 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |22 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |
22 | `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`. |23 | `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`. |
23 | `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é. |24 | `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é. |
modified docs/fr/reference/tools.md +1 -1
@@ -4,7 +4,7 @@
44
55 ## `bash`
66
7-Toujours déclaré.
7+Déclaré quand `bashTool: true` (la valeur par défaut).
88
99 | Champ | Type | Description |
1010 |-------|------|-------------|
@@ -4,7 +4,7 @@
4 4
5 ## `bash`5 ## `bash`
6 6
7-Toujours déclaré.7+Déclaré quand `bashTool: true` (la valeur par défaut).
8 8
9 | Champ | Type | Description |9 | Champ | Type | Description |
10 |-------|------|-------------|10 |-------|------|-------------|
modified internal/config/config.go +9 -0
@@ -73,6 +73,14 @@ type Config struct {
7373 // tool. When it holds no *.md file, the tool is not declared at all.
7474 SkillsDir string `yaml:"skillsDir"`
7575
76+ // BashTool: declare the `bash` tool. On by default — it is the one tool
77+ // the talk is built on. Off, the model can only read and edit files (with
78+ // EditTools) and load skills: an agent that explains and patches code but
79+ // never runs a command, for a machine where a shell in the model's hands is
80+ // not wanted. The system prompt is the operator's: one that still says
81+ // "you have a bash tool" should be reworded with it.
82+ BashTool bool `yaml:"bashTool"`
83+
7684 // EditTools: declare the built-in file tools (read_file, write_file,
7785 // edit_file). Off, the agent has bash and read_skill only, exactly like
7886 // part 09 — and edits files through the `edit` CLI if it is on the PATH.
@@ -184,6 +192,7 @@ var Cfg = Config{
184192 // needs. `displayCommands: true` in the YAML turns it on.
185193 DisplayCommands: false,
186194 SkillsDir: "skills",
195+ BashTool: true,
187196 EditTools: true,
188197 System: `You are a coding agent working in a terminal.
189198 You have a "bash" tool to run shell commands.
@@ -73,6 +73,14 @@ type Config struct {
73 // tool. When it holds no *.md file, the tool is not declared at all.73 // tool. When it holds no *.md file, the tool is not declared at all.
74 SkillsDir string `yaml:"skillsDir"`74 SkillsDir string `yaml:"skillsDir"`
75 75
76+ // BashTool: declare the `bash` tool. On by default — it is the one tool
77+ // the talk is built on. Off, the model can only read and edit files (with
78+ // EditTools) and load skills: an agent that explains and patches code but
79+ // never runs a command, for a machine where a shell in the model's hands is
80+ // not wanted. The system prompt is the operator's: one that still says
81+ // "you have a bash tool" should be reworded with it.
82+ BashTool bool `yaml:"bashTool"`
83+
76 // EditTools: declare the built-in file tools (read_file, write_file,84 // EditTools: declare the built-in file tools (read_file, write_file,
77 // edit_file). Off, the agent has bash and read_skill only, exactly like85 // edit_file). Off, the agent has bash and read_skill only, exactly like
78 // part 09 — and edits files through the `edit` CLI if it is on the PATH.86 // part 09 — and edits files through the `edit` CLI if it is on the PATH.
@@ -184,6 +192,7 @@ var Cfg = Config{
184 // needs. `displayCommands: true` in the YAML turns it on.192 // needs. `displayCommands: true` in the YAML turns it on.
185 DisplayCommands: false,193 DisplayCommands: false,
186 SkillsDir: "skills",194 SkillsDir: "skills",
195+ BashTool: true,
187 EditTools: true,196 EditTools: true,
188 System: `You are a coding agent working in a terminal.197 System: `You are a coding agent working in a terminal.
189 You have a "bash" tool to run shell commands.198 You have a "bash" tool to run shell commands.
modified internal/config/config_test.go +36 -0
@@ -72,3 +72,39 @@ func TestTUIAndPermissionsConfig(t *testing.T) {
7272 t.Errorf("Load(bad.yaml) = %v, want a permissions validation error", err)
7373 }
7474 }
75+
76+// bash is declared by default, so every YAML written before `bashTool`
77+// existed keeps its tool; `bashTool: false` is read; a YAML that says nothing
78+// about it leaves the default alone even when it sets other keys.
79+func TestBashToolConfig(t *testing.T) {
80+ defaults := Cfg
81+ t.Cleanup(func() { Cfg = defaults })
82+
83+ if !Cfg.BashTool {
84+ t.Fatal("defaults: bashTool off, want on")
85+ }
86+
87+ dir := t.TempDir()
88+ off := filepath.Join(dir, "off.yaml")
89+ if err := os.WriteFile(off, []byte("bashTool: false\n"), 0o644); err != nil {
90+ t.Fatal(err)
91+ }
92+ if _, err := Load(off); err != nil {
93+ t.Fatal(err)
94+ }
95+ if Cfg.BashTool {
96+ t.Error("after off.yaml: bashTool still on")
97+ }
98+
99+ Cfg = defaults
100+ silent := filepath.Join(dir, "silent.yaml")
101+ if err := os.WriteFile(silent, []byte("maxTurns: 3\n"), 0o644); err != nil {
102+ t.Fatal(err)
103+ }
104+ if _, err := Load(silent); err != nil {
105+ t.Fatal(err)
106+ }
107+ if !Cfg.BashTool {
108+ t.Error("after silent.yaml: bashTool off, want the default kept")
109+ }
110+}
@@ -72,3 +72,39 @@ func TestTUIAndPermissionsConfig(t *testing.T) {
72 t.Errorf("Load(bad.yaml) = %v, want a permissions validation error", err)72 t.Errorf("Load(bad.yaml) = %v, want a permissions validation error", err)
73 }73 }
74 }74 }
75+
76+// bash is declared by default, so every YAML written before `bashTool`
77+// existed keeps its tool; `bashTool: false` is read; a YAML that says nothing
78+// about it leaves the default alone even when it sets other keys.
79+func TestBashToolConfig(t *testing.T) {
80+ defaults := Cfg
81+ t.Cleanup(func() { Cfg = defaults })
82+
83+ if !Cfg.BashTool {
84+ t.Fatal("defaults: bashTool off, want on")
85+ }
86+
87+ dir := t.TempDir()
88+ off := filepath.Join(dir, "off.yaml")
89+ if err := os.WriteFile(off, []byte("bashTool: false\n"), 0o644); err != nil {
90+ t.Fatal(err)
91+ }
92+ if _, err := Load(off); err != nil {
93+ t.Fatal(err)
94+ }
95+ if Cfg.BashTool {
96+ t.Error("after off.yaml: bashTool still on")
97+ }
98+
99+ Cfg = defaults
100+ silent := filepath.Join(dir, "silent.yaml")
101+ if err := os.WriteFile(silent, []byte("maxTurns: 3\n"), 0o644); err != nil {
102+ t.Fatal(err)
103+ }
104+ if _, err := Load(silent); err != nil {
105+ t.Fatal(err)
106+ }
107+ if !Cfg.BashTool {
108+ t.Error("after silent.yaml: bashTool off, want the default kept")
109+ }
110+}
modified main.go +20 -5
@@ -158,16 +158,21 @@ func runWindow(ctx context.Context, e *engine.Engine, agentTools []ai.ToolRef, d
158158 agent.Run(ctx, e, config.Cfg.System, agentTools, d)
159159 }
160160
161-// assembleTools declares the tools every front end gets: `bash` always;
162-// `read_skill` only when the skills directory holds something to read (its
161+// assembleTools declares the tools every front end gets: `bash` unless
162+// `bashTool: false` turned it off; `read_skill` only when the skills
163+// directory holds something to read (its
163164 // description carries the catalogue, so declaring it empty would advertise a
164165 // tool that can do nothing); the file tools behind `editTools` — the same
165166 // binary must run as part 09 did (bash + the `edit` CLI) and as this part
166167 // (built-in tools), so the two can be compared on the same prompts. It
167168 // returns the tools, their names for the banner, and the skill count.
168169 func assembleTools(e *engine.Engine, d *detector.LoopDetector) ([]ai.ToolRef, []string, int) {
169- agentTools := []ai.ToolRef{tools.Bash(e.G, d)}
170- toolNames := []string{"bash"}
170+ var agentTools []ai.ToolRef
171+ var toolNames []string
172+ if config.Cfg.BashTool {
173+ agentTools = append(agentTools, tools.Bash(e.G, d))
174+ toolNames = append(toolNames, "bash")
175+ }
171176 skillCount := len(skills.List(config.Cfg.SkillsDir))
172177 if skillTool := tools.ReadSkill(e.G, config.Cfg.SkillsDir, d); skillTool != nil {
173178 agentTools = append(agentTools, skillTool)
@@ -211,11 +216,21 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, toolNames [
211216 info.Warnings = append(info.Warnings,
212217 fmt.Sprintf("no skills found in %s (expected <name>.md or <name>/SKILL.md there)", config.Cfg.SkillsDir))
213218 }
219+ // No tool at all is a legal set-up — a chat with the model — but rarely the
220+ // intended one; say so where the "tools:" column would otherwise be empty.
221+ if len(toolNames) == 0 {
222+ info.Warnings = append(info.Warnings,
223+ "no tool is declared (bashTool and editTools are both off, and there are no skills): the model can only answer, not act")
224+ }
214225 for _, w := range info.Warnings {
215226 say("%s[warning: %s]%s", dim, w, off)
216227 }
228+ toolCol := strings.Join(toolNames, ", ")
229+ if toolCol == "" {
230+ toolCol = "none"
231+ }
217232 say("%sconfig: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d%s",
218- dim, path, e.Backend.Provider, config.Cfg.Model, ctxCol, strings.Join(toolNames, ", "), skillCount, off)
233+ dim, path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, off)
219234 }
220235
221236 // windowMode decides whether the window front end runs, and where the banner
@@ -158,16 +158,21 @@ func runWindow(ctx context.Context, e *engine.Engine, agentTools []ai.ToolRef, d
158 agent.Run(ctx, e, config.Cfg.System, agentTools, d)158 agent.Run(ctx, e, config.Cfg.System, agentTools, d)
159 }159 }
160 160
161-// assembleTools declares the tools every front end gets: `bash` always;161+// assembleTools declares the tools every front end gets: `bash` unless
162-// `read_skill` only when the skills directory holds something to read (its162+// `bashTool: false` turned it off; `read_skill` only when the skills
163+// directory holds something to read (its
163 // description carries the catalogue, so declaring it empty would advertise a164 // description carries the catalogue, so declaring it empty would advertise a
164 // tool that can do nothing); the file tools behind `editTools` — the same165 // tool that can do nothing); the file tools behind `editTools` — the same
165 // binary must run as part 09 did (bash + the `edit` CLI) and as this part166 // binary must run as part 09 did (bash + the `edit` CLI) and as this part
166 // (built-in tools), so the two can be compared on the same prompts. It167 // (built-in tools), so the two can be compared on the same prompts. It
167 // returns the tools, their names for the banner, and the skill count.168 // returns the tools, their names for the banner, and the skill count.
168 func assembleTools(e *engine.Engine, d *detector.LoopDetector) ([]ai.ToolRef, []string, int) {169 func assembleTools(e *engine.Engine, d *detector.LoopDetector) ([]ai.ToolRef, []string, int) {
169- agentTools := []ai.ToolRef{tools.Bash(e.G, d)}170+ var agentTools []ai.ToolRef
170- toolNames := []string{"bash"}171+ var toolNames []string
172+ if config.Cfg.BashTool {
173+ agentTools = append(agentTools, tools.Bash(e.G, d))
174+ toolNames = append(toolNames, "bash")
175+ }
171 skillCount := len(skills.List(config.Cfg.SkillsDir))176 skillCount := len(skills.List(config.Cfg.SkillsDir))
172 if skillTool := tools.ReadSkill(e.G, config.Cfg.SkillsDir, d); skillTool != nil {177 if skillTool := tools.ReadSkill(e.G, config.Cfg.SkillsDir, d); skillTool != nil {
173 agentTools = append(agentTools, skillTool)178 agentTools = append(agentTools, skillTool)
@@ -211,11 +216,21 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, toolNames [
211 info.Warnings = append(info.Warnings,216 info.Warnings = append(info.Warnings,
212 fmt.Sprintf("no skills found in %s (expected <name>.md or <name>/SKILL.md there)", config.Cfg.SkillsDir))217 fmt.Sprintf("no skills found in %s (expected <name>.md or <name>/SKILL.md there)", config.Cfg.SkillsDir))
213 }218 }
219+ // No tool at all is a legal set-up — a chat with the model — but rarely the
220+ // intended one; say so where the "tools:" column would otherwise be empty.
221+ if len(toolNames) == 0 {
222+ info.Warnings = append(info.Warnings,
223+ "no tool is declared (bashTool and editTools are both off, and there are no skills): the model can only answer, not act")
224+ }
214 for _, w := range info.Warnings {225 for _, w := range info.Warnings {
215 say("%s[warning: %s]%s", dim, w, off)226 say("%s[warning: %s]%s", dim, w, off)
216 }227 }
228+ toolCol := strings.Join(toolNames, ", ")
229+ if toolCol == "" {
230+ toolCol = "none"
231+ }
217 say("%sconfig: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d%s",232 say("%sconfig: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d%s",
218- dim, path, e.Backend.Provider, config.Cfg.Model, ctxCol, strings.Join(toolNames, ", "), skillCount, off)233+ dim, path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, off)
219 }234 }
220 235
221 // windowMode decides whether the window front end runs, and where the banner236 // windowMode decides whether the window front end runs, and where the banner