bots-garden/mini-mepublic Fork
⑂ 9e1b913
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/dockerinit-account into main

rickub committed 2026-11-14T17:49:39Z Browse files
9e1b913 parents: e019565 49fd0a6
added .tickets/issues/0214-mm-acp-create-start-ollama-models-are-listed-fro.yaml +130 -0
new file mode 100644
@@ -0,0 +1,130 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 214
3+title: "mm acp-create (/start): Ollama models are listed from the container address instead of the local one"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-11-14T07:40:50.000Z
9+updatedAt: 2026-11-14T07:47:59.000Z
10+labels:
11+ - bug
12+ - kind::bug
13+milestone: v1.1.0
14+epic: ACP Create
15+related:
16+ - 210
17+body: |
18+ ## Seen (user, 2026-11-14)
19+
20+ In `mm --acp-create`, `/start`, server **ollama**: the address used to
21+ list Ollama's local models is not the right one — mm behaves as if it ran
22+ in a container (`host.docker.internal`) instead of using the local
23+ address (`localhost:11434`). Ollama runs on the same machine as mm.
24+
25+ ## How the address is chosen today (code read, not reproduced)
26+
27+ - The wizard (`agentinit.Flow`, shared by `mm init` and `mm acp-create`)
28+ resolves the server's address with `resolveBase`
29+ ([internal/agentinit/wizard.go](internal/agentinit/wizard.go)), which
30+ calls the provider's `Resolve`
31+ ([internal/engine/openai_compat.go](internal/engine/openai_compat.go)).
32+ - For `ollama`: default `http://localhost:11434/v1`, fallback
33+ `http://host.docker.internal:11434/v1`. `reachable` asks
34+ `<base>/models` with a **2 s** timeout (`probeClient`); anything but a
35+ 200 in time → the fallback is taken, silently. The fallback exists for
36+ mm running in a container or a Docker Sandbox, where `localhost` is the
37+ container itself.
38+ - The model list is then asked from that address (`Models`, 5 s
39+ timeout). The first question also probes every local server
40+ (`firstAnswering`).
41+
42+ ## Hypotheses to check
43+
44+ 1. The probe fails although Ollama is up: `/v1/models` answers slowly
45+ (Ollama busy, a model loading) and 2 s is too short.
46+ 2. `localhost` vs `127.0.0.1` / IPv6: Ollama listening on `127.0.0.1`
47+ only, `localhost` tried as `::1` first.
48+ 3. The environment of the process the editor starts differs from a
49+ terminal's (proxy variables such as `HTTP_PROXY` without `localhost`
50+ in `NO_PROXY`, `AGENT_BASE_URL` set in the editor's settings…).
51+ 4. On a machine with Docker Desktop, `host.docker.internal` resolves on
52+ the host too, so the fallback "works" and hides the failed probe.
53+
54+ Not known yet: whether `mm init` in a terminal does the same (same code
55+ path), and whether other providers with a fallback (dmr, llmman, kronk)
56+ are affected.
57+
58+ ## Expected
59+
60+ - mm on the host lists the models from the local address. The container
61+ fallback is for mm in a container or a sandbox — when mm is not in
62+ one, the wizard should not switch to it (or should try the local
63+ address more patiently first).
64+ - The model question says which address the list came from, so a wrong
65+ one is visible at once.
66+ - The written agent.yaml is unchanged (no `baseUrl` line for the default
67+ address).
68+
69+ ## Acceptance criteria
70+
71+ - Reproduced, cause identified and written here.
72+ - With Ollama on the host, `/start` → ollama lists the local models in
73+ `mm acp-create` (Zed) and in `mm init`.
74+ - Inside a container, the fallback still works.
75+ - Tests for the cause found.
76+tasks:
77+ - id: 1
78+ title: Reproduce - mm acp-create from Zed and mm init in a terminal, Ollama on the host, note the address used
79+ state: open
80+ priority: high
81+ author:
82+ name: k33g
83+ email: ph.charriere@gmail.com
84+ createdAt: 2026-11-14T07:40:50.000Z
85+ - id: 2
86+ title: Find why the local address is not taken (probe timeout, localhost vs 127.0.0.1 / IPv6, environment of the editor's process)
87+ state: open
88+ priority: high
89+ author:
90+ name: k33g
91+ email: ph.charriere@gmail.com
92+ createdAt: 2026-11-14T07:40:50.000Z
93+ - id: 3
94+ title: Fix - the wizard lists the models from the local address when mm is not in a container
95+ state: open
96+ priority: high
97+ author:
98+ name: k33g
99+ email: ph.charriere@gmail.com
100+ createdAt: 2026-11-14T07:40:50.000Z
101+ - id: 4
102+ title: Show the address the models were listed from in the model question (both modes)
103+ state: open
104+ priority: medium
105+ author:
106+ name: k33g
107+ email: ph.charriere@gmail.com
108+ createdAt: 2026-11-14T07:40:50.000Z
109+ - id: 5
110+ title: Tests - a local server answering slowly or only on 127.0.0.1 keeps the local address
111+ state: open
112+ priority: medium
113+ author:
114+ name: k33g
115+ email: ph.charriere@gmail.com
116+ createdAt: 2026-11-14T07:40:50.000Z
117+comments:
118+ - id: 1
119+ author:
120+ name: k33g
121+ email: ph.charriere@gmail.com
122+ createdAt: 2026-11-14T07:47:59.000Z
123+ body: |
124+ From #215 (user, 2026-11-14): with the same `/start` in Zed, **dmr**
125+ lists its local models — and its default address is
126+ `http://localhost:12434/engines/v1`, `localhost` too — and **llmman**
127+ (`127.0.0.1`) works. So `localhost` itself is not enough to explain
128+ it: look at what is specific to Ollama — its `/v1/models` answering
129+ later than 2 s or not 200, Ollama listening on IPv4 only while DMR
130+ answers on both, or `OLLAMA_HOST` on the user's machine.
new file mode 100644
@@ -0,0 +1,130 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 214
3+title: "mm acp-create (/start): Ollama models are listed from the container address instead of the local one"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-11-14T07:40:50.000Z
9+updatedAt: 2026-11-14T07:47:59.000Z
10+labels:
11+ - bug
12+ - kind::bug
13+milestone: v1.1.0
14+epic: ACP Create
15+related:
16+ - 210
17+body: |
18+ ## Seen (user, 2026-11-14)
19+
20+ In `mm --acp-create`, `/start`, server **ollama**: the address used to
21+ list Ollama's local models is not the right one — mm behaves as if it ran
22+ in a container (`host.docker.internal`) instead of using the local
23+ address (`localhost:11434`). Ollama runs on the same machine as mm.
24+
25+ ## How the address is chosen today (code read, not reproduced)
26+
27+ - The wizard (`agentinit.Flow`, shared by `mm init` and `mm acp-create`)
28+ resolves the server's address with `resolveBase`
29+ ([internal/agentinit/wizard.go](internal/agentinit/wizard.go)), which
30+ calls the provider's `Resolve`
31+ ([internal/engine/openai_compat.go](internal/engine/openai_compat.go)).
32+ - For `ollama`: default `http://localhost:11434/v1`, fallback
33+ `http://host.docker.internal:11434/v1`. `reachable` asks
34+ `<base>/models` with a **2 s** timeout (`probeClient`); anything but a
35+ 200 in time → the fallback is taken, silently. The fallback exists for
36+ mm running in a container or a Docker Sandbox, where `localhost` is the
37+ container itself.
38+ - The model list is then asked from that address (`Models`, 5 s
39+ timeout). The first question also probes every local server
40+ (`firstAnswering`).
41+
42+ ## Hypotheses to check
43+
44+ 1. The probe fails although Ollama is up: `/v1/models` answers slowly
45+ (Ollama busy, a model loading) and 2 s is too short.
46+ 2. `localhost` vs `127.0.0.1` / IPv6: Ollama listening on `127.0.0.1`
47+ only, `localhost` tried as `::1` first.
48+ 3. The environment of the process the editor starts differs from a
49+ terminal's (proxy variables such as `HTTP_PROXY` without `localhost`
50+ in `NO_PROXY`, `AGENT_BASE_URL` set in the editor's settings…).
51+ 4. On a machine with Docker Desktop, `host.docker.internal` resolves on
52+ the host too, so the fallback "works" and hides the failed probe.
53+
54+ Not known yet: whether `mm init` in a terminal does the same (same code
55+ path), and whether other providers with a fallback (dmr, llmman, kronk)
56+ are affected.
57+
58+ ## Expected
59+
60+ - mm on the host lists the models from the local address. The container
61+ fallback is for mm in a container or a sandbox — when mm is not in
62+ one, the wizard should not switch to it (or should try the local
63+ address more patiently first).
64+ - The model question says which address the list came from, so a wrong
65+ one is visible at once.
66+ - The written agent.yaml is unchanged (no `baseUrl` line for the default
67+ address).
68+
69+ ## Acceptance criteria
70+
71+ - Reproduced, cause identified and written here.
72+ - With Ollama on the host, `/start` → ollama lists the local models in
73+ `mm acp-create` (Zed) and in `mm init`.
74+ - Inside a container, the fallback still works.
75+ - Tests for the cause found.
76+tasks:
77+ - id: 1
78+ title: Reproduce - mm acp-create from Zed and mm init in a terminal, Ollama on the host, note the address used
79+ state: open
80+ priority: high
81+ author:
82+ name: k33g
83+ email: ph.charriere@gmail.com
84+ createdAt: 2026-11-14T07:40:50.000Z
85+ - id: 2
86+ title: Find why the local address is not taken (probe timeout, localhost vs 127.0.0.1 / IPv6, environment of the editor's process)
87+ state: open
88+ priority: high
89+ author:
90+ name: k33g
91+ email: ph.charriere@gmail.com
92+ createdAt: 2026-11-14T07:40:50.000Z
93+ - id: 3
94+ title: Fix - the wizard lists the models from the local address when mm is not in a container
95+ state: open
96+ priority: high
97+ author:
98+ name: k33g
99+ email: ph.charriere@gmail.com
100+ createdAt: 2026-11-14T07:40:50.000Z
101+ - id: 4
102+ title: Show the address the models were listed from in the model question (both modes)
103+ state: open
104+ priority: medium
105+ author:
106+ name: k33g
107+ email: ph.charriere@gmail.com
108+ createdAt: 2026-11-14T07:40:50.000Z
109+ - id: 5
110+ title: Tests - a local server answering slowly or only on 127.0.0.1 keeps the local address
111+ state: open
112+ priority: medium
113+ author:
114+ name: k33g
115+ email: ph.charriere@gmail.com
116+ createdAt: 2026-11-14T07:40:50.000Z
117+comments:
118+ - id: 1
119+ author:
120+ name: k33g
121+ email: ph.charriere@gmail.com
122+ createdAt: 2026-11-14T07:47:59.000Z
123+ body: |
124+ From #215 (user, 2026-11-14): with the same `/start` in Zed, **dmr**
125+ lists its local models — and its default address is
126+ `http://localhost:12434/engines/v1`, `localhost` too — and **llmman**
127+ (`127.0.0.1`) works. So `localhost` itself is not enough to explain
128+ it: look at what is specific to Ollama — its `/v1/models` answering
129+ later than 2 s or not 200, Ollama listening on IPv4 only while DMR
130+ answers on both, or `OLLAMA_HOST` on the user's machine.
added .tickets/issues/0215-mm-acp-create-start-test-every-provider-address.yaml +150 -0
new file mode 100644
@@ -0,0 +1,150 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 215
3+title: "mm acp-create (/start): test every provider - address used and model list"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-11-14T07:41:16.000Z
9+updatedAt: 2026-11-14T08:07:53.000Z
10+labels:
11+ - kind::chore
12+ - To be tested
13+milestone: v1.1.0
14+epic: ACP Create
15+related:
16+ - 214
17+body: |
18+ [#214](.tickets/issues/0214-mm-acp-create-start-ollama-models-are-listed-fro.yaml)
19+ found that with **ollama**, `/start` in `mm acp-create` lists the models
20+ from the container address instead of the local one. Check the other
21+ providers the same way, by hand, from an ACP client (Zed), and in
22+ `mm init` in a terminal for comparison.
23+
24+ ## The addresses (internal/engine, read 2026-11-14)
25+
26+ | Provider | Default | Fallback (mm in a container) |
27+ |----------|---------|------------------------------|
28+ | `dmr` | `http://localhost:12434/engines/v1` | `http://host.docker.internal:12434/engines/v1` |
29+ | `ollama` | `http://localhost:11434/v1` | `http://host.docker.internal:11434/v1` |
30+ | `llmman` | `http://127.0.0.1:17434/v1` | `http://host.docker.internal:17434/v1` |
31+ | `kronk` | `http://127.0.0.1:11435/v1` | `http://host.docker.internal:11435/v1` |
32+ | `llamacpp` | `http://127.0.0.1:8080/v1` | none |
33+ | `openai` | `https://api.openai.com/v1` (or the endpoint answered) | none |
34+
35+ The fallback is taken when `<default>/models` does not answer 200
36+ within 2 s. Note: `dmr` and `ollama` use `localhost`, the others
37+ `127.0.0.1` — worth watching if #214 turns out to be an IPv6 matter.
38+
39+ ## For each provider
40+
41+ With its server running on the host:
42+
43+ 1. `/start` in `mm acp-create` (Zed): is the provider marked as
44+ answering in the first question?
45+ 2. Which address are the models listed from (the `[acp-create]` log, or
46+ a `/preview`)? Is the list the server's?
47+ 3. The model's facts (family, window, sampling) read from the server —
48+ same as in `mm init`?
49+ 4. The written file starts with `mm acp <file>` and the model answers.
50+ 5. `mm init` in a terminal, same answers: same file?
51+
52+ Then the same inside a container (`/dockerinit`) and a Docker Sandbox
53+ (`/sbxinit`), where the fallback is the right address.
54+
55+ ## Done when
56+
57+ Every provider checked, results in a comment (mm version, client,
58+ address used, list ok or not), and a ticket filed for each bug found.
59+tasks:
60+ - id: 1
61+ title: dmr - Docker Model Runner on the host
62+ state: closed
63+ priority: high
64+ author:
65+ name: k33g
66+ email: ph.charriere@gmail.com
67+ createdAt: 2026-11-14T07:41:16.000Z
68+ updatedAt: 2026-11-14T07:47:59.000Z
69+ closedAt: 2026-11-14T07:47:59.000Z
70+ - id: 2
71+ title: llmman
72+ state: closed
73+ priority: high
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-11-14T07:41:16.000Z
78+ updatedAt: 2026-11-14T07:47:59.000Z
79+ closedAt: 2026-11-14T07:47:59.000Z
80+ - id: 3
81+ title: llamacpp - llama-server on 127.0.0.1:8080
82+ state: open
83+ priority: medium
84+ author:
85+ name: k33g
86+ email: ph.charriere@gmail.com
87+ createdAt: 2026-11-14T07:41:16.000Z
88+ - id: 4
89+ title: kronk
90+ state: closed
91+ priority: medium
92+ author:
93+ name: k33g
94+ email: ph.charriere@gmail.com
95+ createdAt: 2026-11-14T07:41:16.000Z
96+ updatedAt: 2026-11-14T07:51:27.000Z
97+ closedAt: 2026-11-14T07:51:27.000Z
98+ - id: 5
99+ title: openai - OpenAI-compatible service (endpoint, API key variable, model list)
100+ state: closed
101+ priority: medium
102+ author:
103+ name: k33g
104+ email: ph.charriere@gmail.com
105+ createdAt: 2026-11-14T07:41:16.000Z
106+ updatedAt: 2026-11-14T08:07:53.000Z
107+ closedAt: 2026-11-14T08:07:53.000Z
108+ - id: 6
109+ title: Same checks with mm in a container or a Docker Sandbox (the fallback must be taken there)
110+ state: open
111+ priority: medium
112+ author:
113+ name: k33g
114+ email: ph.charriere@gmail.com
115+ createdAt: 2026-11-14T07:41:16.000Z
116+ - id: 7
117+ title: Write the results in a comment (version, client, address used, list ok or not); a ticket per bug found
118+ state: open
119+ priority: low
120+ author:
121+ name: k33g
122+ email: ph.charriere@gmail.com
123+ createdAt: 2026-11-14T07:41:16.000Z
124+comments:
125+ - id: 1
126+ author:
127+ name: k33g
128+ email: ph.charriere@gmail.com
129+ createdAt: 2026-11-14T07:47:59.000Z
130+ body: |
131+ Tried by the user (2026-11-14), `/start` in `mm acp-create` from Zed:
132+ **dmr works** (local models listed) and **llmman works** (already
133+ did). Still to try: llamacpp, kronk, openai, and inside a container
134+ / sandbox.
135+ - id: 2
136+ author:
137+ name: k33g
138+ email: ph.charriere@gmail.com
139+ createdAt: 2026-11-14T07:51:27.000Z
140+ body: |
141+ User (2026-11-14): **kronk works** too. Still to try: llamacpp,
142+ openai, and inside a container / sandbox.
143+ - id: 3
144+ author:
145+ name: k33g
146+ email: ph.charriere@gmail.com
147+ createdAt: 2026-11-14T08:07:53.000Z
148+ body: |
149+ User (2026-11-14): **openai works** too. Still to try: llamacpp, and
150+ inside a container / sandbox.
new file mode 100644
@@ -0,0 +1,150 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 215
3+title: "mm acp-create (/start): test every provider - address used and model list"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-11-14T07:41:16.000Z
9+updatedAt: 2026-11-14T08:07:53.000Z
10+labels:
11+ - kind::chore
12+ - To be tested
13+milestone: v1.1.0
14+epic: ACP Create
15+related:
16+ - 214
17+body: |
18+ [#214](.tickets/issues/0214-mm-acp-create-start-ollama-models-are-listed-fro.yaml)
19+ found that with **ollama**, `/start` in `mm acp-create` lists the models
20+ from the container address instead of the local one. Check the other
21+ providers the same way, by hand, from an ACP client (Zed), and in
22+ `mm init` in a terminal for comparison.
23+
24+ ## The addresses (internal/engine, read 2026-11-14)
25+
26+ | Provider | Default | Fallback (mm in a container) |
27+ |----------|---------|------------------------------|
28+ | `dmr` | `http://localhost:12434/engines/v1` | `http://host.docker.internal:12434/engines/v1` |
29+ | `ollama` | `http://localhost:11434/v1` | `http://host.docker.internal:11434/v1` |
30+ | `llmman` | `http://127.0.0.1:17434/v1` | `http://host.docker.internal:17434/v1` |
31+ | `kronk` | `http://127.0.0.1:11435/v1` | `http://host.docker.internal:11435/v1` |
32+ | `llamacpp` | `http://127.0.0.1:8080/v1` | none |
33+ | `openai` | `https://api.openai.com/v1` (or the endpoint answered) | none |
34+
35+ The fallback is taken when `<default>/models` does not answer 200
36+ within 2 s. Note: `dmr` and `ollama` use `localhost`, the others
37+ `127.0.0.1` — worth watching if #214 turns out to be an IPv6 matter.
38+
39+ ## For each provider
40+
41+ With its server running on the host:
42+
43+ 1. `/start` in `mm acp-create` (Zed): is the provider marked as
44+ answering in the first question?
45+ 2. Which address are the models listed from (the `[acp-create]` log, or
46+ a `/preview`)? Is the list the server's?
47+ 3. The model's facts (family, window, sampling) read from the server —
48+ same as in `mm init`?
49+ 4. The written file starts with `mm acp <file>` and the model answers.
50+ 5. `mm init` in a terminal, same answers: same file?
51+
52+ Then the same inside a container (`/dockerinit`) and a Docker Sandbox
53+ (`/sbxinit`), where the fallback is the right address.
54+
55+ ## Done when
56+
57+ Every provider checked, results in a comment (mm version, client,
58+ address used, list ok or not), and a ticket filed for each bug found.
59+tasks:
60+ - id: 1
61+ title: dmr - Docker Model Runner on the host
62+ state: closed
63+ priority: high
64+ author:
65+ name: k33g
66+ email: ph.charriere@gmail.com
67+ createdAt: 2026-11-14T07:41:16.000Z
68+ updatedAt: 2026-11-14T07:47:59.000Z
69+ closedAt: 2026-11-14T07:47:59.000Z
70+ - id: 2
71+ title: llmman
72+ state: closed
73+ priority: high
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-11-14T07:41:16.000Z
78+ updatedAt: 2026-11-14T07:47:59.000Z
79+ closedAt: 2026-11-14T07:47:59.000Z
80+ - id: 3
81+ title: llamacpp - llama-server on 127.0.0.1:8080
82+ state: open
83+ priority: medium
84+ author:
85+ name: k33g
86+ email: ph.charriere@gmail.com
87+ createdAt: 2026-11-14T07:41:16.000Z
88+ - id: 4
89+ title: kronk
90+ state: closed
91+ priority: medium
92+ author:
93+ name: k33g
94+ email: ph.charriere@gmail.com
95+ createdAt: 2026-11-14T07:41:16.000Z
96+ updatedAt: 2026-11-14T07:51:27.000Z
97+ closedAt: 2026-11-14T07:51:27.000Z
98+ - id: 5
99+ title: openai - OpenAI-compatible service (endpoint, API key variable, model list)
100+ state: closed
101+ priority: medium
102+ author:
103+ name: k33g
104+ email: ph.charriere@gmail.com
105+ createdAt: 2026-11-14T07:41:16.000Z
106+ updatedAt: 2026-11-14T08:07:53.000Z
107+ closedAt: 2026-11-14T08:07:53.000Z
108+ - id: 6
109+ title: Same checks with mm in a container or a Docker Sandbox (the fallback must be taken there)
110+ state: open
111+ priority: medium
112+ author:
113+ name: k33g
114+ email: ph.charriere@gmail.com
115+ createdAt: 2026-11-14T07:41:16.000Z
116+ - id: 7
117+ title: Write the results in a comment (version, client, address used, list ok or not); a ticket per bug found
118+ state: open
119+ priority: low
120+ author:
121+ name: k33g
122+ email: ph.charriere@gmail.com
123+ createdAt: 2026-11-14T07:41:16.000Z
124+comments:
125+ - id: 1
126+ author:
127+ name: k33g
128+ email: ph.charriere@gmail.com
129+ createdAt: 2026-11-14T07:47:59.000Z
130+ body: |
131+ Tried by the user (2026-11-14), `/start` in `mm acp-create` from Zed:
132+ **dmr works** (local models listed) and **llmman works** (already
133+ did). Still to try: llamacpp, kronk, openai, and inside a container
134+ / sandbox.
135+ - id: 2
136+ author:
137+ name: k33g
138+ email: ph.charriere@gmail.com
139+ createdAt: 2026-11-14T07:51:27.000Z
140+ body: |
141+ User (2026-11-14): **kronk works** too. Still to try: llamacpp,
142+ openai, and inside a container / sandbox.
143+ - id: 3
144+ author:
145+ name: k33g
146+ email: ph.charriere@gmail.com
147+ createdAt: 2026-11-14T08:07:53.000Z
148+ body: |
149+ User (2026-11-14): **openai works** too. Still to try: llamacpp, and
150+ inside a container / sandbox.
added .tickets/issues/0216-mm-dockerinit-i-have-no-name-in-the-container-th.yaml +174 -0
new file mode 100644
@@ -0,0 +1,174 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 216
3+title: "mm dockerinit: \"I have no name!\" in the container - the mm account has uid 1000, compose runs as the user's uid"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-11-14T17:31:27.000Z
9+updatedAt: 2026-11-14T17:35:14.000Z
10+labels:
11+ - bug
12+ - kind::bug
13+milestone: v1.1.0
14+epic: Scaffolding
15+related:
16+ - 189
17+ - 212
18+body: |
19+ Reported from Sidekicks (its ticket #94, the mini-me agent creator),
20+ 2026-11-14.
21+
22+ ## Symptom
23+
24+ An agent made with the container option (`/dockerinit` of
25+ `mm --acp-create`), then a shell in the container
26+ (`docker exec -it jimmy-box bash -l`):
27+
28+ ```text
29+ I have no name!@cd5ed2bf5dad:/Users/k33g/Documents/jimmy$
30+ ```
31+
32+ ## Cause
33+
34+ The two files `mm dockerinit` writes disagree on the user
35+ ([internal/dockerinit/files.go](internal/dockerinit/files.go)):
36+
37+ - `compose.yaml` runs the container as the uid:gid of the machine that
38+ ran `mm dockerinit` (`user: "${MM_UID:-501}:${MM_GID:-20}"` on a Mac),
39+ so that the files the agent writes belong to the user;
40+ - the `Dockerfile` creates the `mm` account with uid **1000** only.
41+
42+ On a Mac uid 501 has no line in the image's `/etc/passwd`: bash cannot
43+ name `\u`. On Linux the user is often uid 1000, which is why nobody saw
44+ it. `HOME=/home/mm` is `chmod 777`, so mm runs, but every program that
45+ looks the current user up fails: `whoami` (`cannot find name for user ID
46+ 501`), `id` without a user name, git without `user.name`/`user.email`,
47+ ssh (the last two not tested).
48+
49+ ## Fix
50+
51+ Create the account with the user's uid and gid at build time, passed from
52+ `compose.yaml` as build arguments:
53+
54+ ```dockerfile
55+ ARG MM_UID=1000
56+ ARG MM_GID=1000
57+ RUN groupadd -o -g "$MM_GID" mm \
58+ && useradd -o --create-home --uid "$MM_UID" --gid "$MM_GID" mm \
59+ && chmod 777 /home/mm
60+ ```
61+
62+ ```yaml
63+ build:
64+ context: .
65+ args:
66+ MM_UID: ${MM_UID:-501}
67+ MM_GID: ${MM_GID:-20}
68+ ```
69+
70+ `-o` because the gid may exist already (20 is Debian's `dialout`). The
71+ args' defaults are the numbers already written in `user:` (`usableID`:
72+ never root, a fallback on Windows). The ARGs come right before
73+ `useradd`: a new uid rebuilds that layer only, not mm's download.
74+
75+ ## To say in the docs
76+
77+ - Changing `MM_UID` / `MM_GID` needs a rebuild
78+ (`docker compose up -d --build`), or the mismatch comes back.
79+ - An image built outside compose (`docker build`, the
80+ `docker buildx … --push` of the comments) keeps uid 1000.
81+ - An existing `compose.yaml` / `Dockerfile`: run `mm dockerinit` again
82+ (or `/dockerinit --overwrite`), then `docker compose up -d --build`.
83+
84+ ## Checked
85+
86+ - By Sidekicks, 2026-11-14, `debian:bookworm-slim` with the account lines
87+ only, run `-u 501:20`: before `whoami` → `cannot find name for user ID
88+ 501`; after (`--build-arg MM_UID=501 --build-arg MM_GID=20`) → `mm`,
89+ `id` → `uid=501(mm) gid=20(dialout)`, HOME writable.
90+ - Re-done in mini-me's sandbox (Docker 29.9.3, Linux), same three cases
91+ (before, after 501:20, after with the defaults run `-u 1000:1000`):
92+ same results.
93+
94+ Not checked yet: the full build with the real `Dockerfile`, the
95+ `mm@…` prompt with `docker compose exec <service> bash -l`, `git commit`
96+ and `mm tui` in the container.
97+tasks:
98+ - id: 1
99+ title: Dockerfile - the mm account with MM_UID / MM_GID build arguments (groupadd -o, useradd -o)
100+ state: closed
101+ priority: high
102+ author:
103+ name: k33g
104+ email: ph.charriere@gmail.com
105+ createdAt: 2026-11-14T17:31:27.000Z
106+ updatedAt: 2026-11-14T17:35:14.000Z
107+ closedAt: 2026-11-14T17:35:14.000Z
108+ - id: 2
109+ title: compose.yaml - build.args with the same numbers as user
110+ state: closed
111+ priority: high
112+ author:
113+ name: k33g
114+ email: ph.charriere@gmail.com
115+ createdAt: 2026-11-14T17:31:27.000Z
116+ updatedAt: 2026-11-14T17:35:14.000Z
117+ closedAt: 2026-11-14T17:35:14.000Z
118+ - id: 3
119+ title: Tests - args equal user, never root, account lines in the Dockerfile
120+ state: closed
121+ priority: medium
122+ author:
123+ name: k33g
124+ email: ph.charriere@gmail.com
125+ createdAt: 2026-11-14T17:31:27.000Z
126+ updatedAt: 2026-11-14T17:35:14.000Z
127+ closedAt: 2026-11-14T17:35:14.000Z
128+ - id: 4
129+ title: Docs EN+FR - run-in-a-container (MM_UID/MM_GID need a rebuild, images built outside compose keep 1000), cli reference
130+ state: closed
131+ priority: medium
132+ author:
133+ name: k33g
134+ email: ph.charriere@gmail.com
135+ createdAt: 2026-11-14T17:31:27.000Z
136+ updatedAt: 2026-11-14T17:35:14.000Z
137+ closedAt: 2026-11-14T17:35:14.000Z
138+ - id: 5
139+ title: Check with the real Dockerfile - full build, bash -l prompt, whoami, git commit, mm tui
140+ state: closed
141+ priority: medium
142+ author:
143+ name: k33g
144+ email: ph.charriere@gmail.com
145+ createdAt: 2026-11-14T17:31:27.000Z
146+comments:
147+ - id: 1
148+ author:
149+ name: k33g
150+ email: ph.charriere@gmail.com
151+ createdAt: 2026-11-14T17:35:14.000Z
152+ body: |
153+ Implemented on `feature/dockerinit-account` (uncommitted): the
154+ Dockerfile's account from `MM_UID`/`MM_GID` build args (after mm's
155+ download), `build.args` in compose.yaml with the numbers of `user:`.
156+ Tests: args equal user, never root (args too), account lines, ARG after
157+ the download. Docs EN+FR: run-in-a-container (rebuild after changing
158+ MM_UID, images built outside compose keep 1000, regenerate old files,
159+ git identity), cli reference.
160+
161+ Checked with the **real Dockerfile** (Docker 29.9.3, Linux, compose
162+ v5.7.1): `MM_UID=501 MM_GID=20 docker compose build` (mm downloaded),
163+ `up -d --wait`, then `docker compose exec … bash -l`: prompt
164+ `mm@<container>:<project>$`, `whoami` → `mm`, `id` →
165+ `uid=501(mm) gid=20(dialout)`, `HOME=/home/mm`, `mm --version` →
166+ `mm v1.0.9`.
167+
168+ **git commit still refuses** ("Author identity unknown — Please tell
169+ me who you are"): with an account, git still does not make up an
170+ identity. Unrelated to the fix — the docs say to set `user.name` /
171+ `user.email` or the `GIT_*` variables. Not tried: `mm tui` in the
172+ container (no terminal here), ssh, Docker Desktop on macOS.
173+ updatedAt: 2026-11-14T17:35:14.000Z
174+ closedAt: 2026-11-14T17:35:14.000Z
new file mode 100644
@@ -0,0 +1,174 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 216
3+title: "mm dockerinit: \"I have no name!\" in the container - the mm account has uid 1000, compose runs as the user's uid"
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-11-14T17:31:27.000Z
9+updatedAt: 2026-11-14T17:35:14.000Z
10+labels:
11+ - bug
12+ - kind::bug
13+milestone: v1.1.0
14+epic: Scaffolding
15+related:
16+ - 189
17+ - 212
18+body: |
19+ Reported from Sidekicks (its ticket #94, the mini-me agent creator),
20+ 2026-11-14.
21+
22+ ## Symptom
23+
24+ An agent made with the container option (`/dockerinit` of
25+ `mm --acp-create`), then a shell in the container
26+ (`docker exec -it jimmy-box bash -l`):
27+
28+ ```text
29+ I have no name!@cd5ed2bf5dad:/Users/k33g/Documents/jimmy$
30+ ```
31+
32+ ## Cause
33+
34+ The two files `mm dockerinit` writes disagree on the user
35+ ([internal/dockerinit/files.go](internal/dockerinit/files.go)):
36+
37+ - `compose.yaml` runs the container as the uid:gid of the machine that
38+ ran `mm dockerinit` (`user: "${MM_UID:-501}:${MM_GID:-20}"` on a Mac),
39+ so that the files the agent writes belong to the user;
40+ - the `Dockerfile` creates the `mm` account with uid **1000** only.
41+
42+ On a Mac uid 501 has no line in the image's `/etc/passwd`: bash cannot
43+ name `\u`. On Linux the user is often uid 1000, which is why nobody saw
44+ it. `HOME=/home/mm` is `chmod 777`, so mm runs, but every program that
45+ looks the current user up fails: `whoami` (`cannot find name for user ID
46+ 501`), `id` without a user name, git without `user.name`/`user.email`,
47+ ssh (the last two not tested).
48+
49+ ## Fix
50+
51+ Create the account with the user's uid and gid at build time, passed from
52+ `compose.yaml` as build arguments:
53+
54+ ```dockerfile
55+ ARG MM_UID=1000
56+ ARG MM_GID=1000
57+ RUN groupadd -o -g "$MM_GID" mm \
58+ && useradd -o --create-home --uid "$MM_UID" --gid "$MM_GID" mm \
59+ && chmod 777 /home/mm
60+ ```
61+
62+ ```yaml
63+ build:
64+ context: .
65+ args:
66+ MM_UID: ${MM_UID:-501}
67+ MM_GID: ${MM_GID:-20}
68+ ```
69+
70+ `-o` because the gid may exist already (20 is Debian's `dialout`). The
71+ args' defaults are the numbers already written in `user:` (`usableID`:
72+ never root, a fallback on Windows). The ARGs come right before
73+ `useradd`: a new uid rebuilds that layer only, not mm's download.
74+
75+ ## To say in the docs
76+
77+ - Changing `MM_UID` / `MM_GID` needs a rebuild
78+ (`docker compose up -d --build`), or the mismatch comes back.
79+ - An image built outside compose (`docker build`, the
80+ `docker buildx … --push` of the comments) keeps uid 1000.
81+ - An existing `compose.yaml` / `Dockerfile`: run `mm dockerinit` again
82+ (or `/dockerinit --overwrite`), then `docker compose up -d --build`.
83+
84+ ## Checked
85+
86+ - By Sidekicks, 2026-11-14, `debian:bookworm-slim` with the account lines
87+ only, run `-u 501:20`: before `whoami` → `cannot find name for user ID
88+ 501`; after (`--build-arg MM_UID=501 --build-arg MM_GID=20`) → `mm`,
89+ `id` → `uid=501(mm) gid=20(dialout)`, HOME writable.
90+ - Re-done in mini-me's sandbox (Docker 29.9.3, Linux), same three cases
91+ (before, after 501:20, after with the defaults run `-u 1000:1000`):
92+ same results.
93+
94+ Not checked yet: the full build with the real `Dockerfile`, the
95+ `mm@…` prompt with `docker compose exec <service> bash -l`, `git commit`
96+ and `mm tui` in the container.
97+tasks:
98+ - id: 1
99+ title: Dockerfile - the mm account with MM_UID / MM_GID build arguments (groupadd -o, useradd -o)
100+ state: closed
101+ priority: high
102+ author:
103+ name: k33g
104+ email: ph.charriere@gmail.com
105+ createdAt: 2026-11-14T17:31:27.000Z
106+ updatedAt: 2026-11-14T17:35:14.000Z
107+ closedAt: 2026-11-14T17:35:14.000Z
108+ - id: 2
109+ title: compose.yaml - build.args with the same numbers as user
110+ state: closed
111+ priority: high
112+ author:
113+ name: k33g
114+ email: ph.charriere@gmail.com
115+ createdAt: 2026-11-14T17:31:27.000Z
116+ updatedAt: 2026-11-14T17:35:14.000Z
117+ closedAt: 2026-11-14T17:35:14.000Z
118+ - id: 3
119+ title: Tests - args equal user, never root, account lines in the Dockerfile
120+ state: closed
121+ priority: medium
122+ author:
123+ name: k33g
124+ email: ph.charriere@gmail.com
125+ createdAt: 2026-11-14T17:31:27.000Z
126+ updatedAt: 2026-11-14T17:35:14.000Z
127+ closedAt: 2026-11-14T17:35:14.000Z
128+ - id: 4
129+ title: Docs EN+FR - run-in-a-container (MM_UID/MM_GID need a rebuild, images built outside compose keep 1000), cli reference
130+ state: closed
131+ priority: medium
132+ author:
133+ name: k33g
134+ email: ph.charriere@gmail.com
135+ createdAt: 2026-11-14T17:31:27.000Z
136+ updatedAt: 2026-11-14T17:35:14.000Z
137+ closedAt: 2026-11-14T17:35:14.000Z
138+ - id: 5
139+ title: Check with the real Dockerfile - full build, bash -l prompt, whoami, git commit, mm tui
140+ state: closed
141+ priority: medium
142+ author:
143+ name: k33g
144+ email: ph.charriere@gmail.com
145+ createdAt: 2026-11-14T17:31:27.000Z
146+comments:
147+ - id: 1
148+ author:
149+ name: k33g
150+ email: ph.charriere@gmail.com
151+ createdAt: 2026-11-14T17:35:14.000Z
152+ body: |
153+ Implemented on `feature/dockerinit-account` (uncommitted): the
154+ Dockerfile's account from `MM_UID`/`MM_GID` build args (after mm's
155+ download), `build.args` in compose.yaml with the numbers of `user:`.
156+ Tests: args equal user, never root (args too), account lines, ARG after
157+ the download. Docs EN+FR: run-in-a-container (rebuild after changing
158+ MM_UID, images built outside compose keep 1000, regenerate old files,
159+ git identity), cli reference.
160+
161+ Checked with the **real Dockerfile** (Docker 29.9.3, Linux, compose
162+ v5.7.1): `MM_UID=501 MM_GID=20 docker compose build` (mm downloaded),
163+ `up -d --wait`, then `docker compose exec … bash -l`: prompt
164+ `mm@<container>:<project>$`, `whoami` → `mm`, `id` →
165+ `uid=501(mm) gid=20(dialout)`, `HOME=/home/mm`, `mm --version` →
166+ `mm v1.0.9`.
167+
168+ **git commit still refuses** ("Author identity unknown — Please tell
169+ me who you are"): with an account, git still does not make up an
170+ identity. Unrelated to the fix — the docs say to set `user.name` /
171+ `user.email` or the `GIT_*` variables. Not tried: `mm tui` in the
172+ container (no terminal here), ssh, Docker Desktop on macOS.
173+ updatedAt: 2026-11-14T17:35:14.000Z
174+ closedAt: 2026-11-14T17:35:14.000Z
modified README.md +13 -5
@@ -8,7 +8,7 @@
88
99 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.
1010
11-From there it grew into the coding agent I wanted for local models (Docker Model Runner, llama.cpp, llmman, Ollama): small enough to be read in an afternoon, and careful about what a small model does wrong:
11+From there it grew into the coding agent I wanted for local models (Docker Model Runner, llama.cpp, llmman, Ollama, Kronk): small enough to be read in an afternoon, and careful about what a small model does wrong:
1212
1313 - **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;
1414 - **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;
@@ -20,7 +20,7 @@ What makes Mini Me a bit special is that it was designed from the start to be us
2020
2121 ## Install
2222
23-You need Go 1.27+ and a model server that can emit tool calls, through its OpenAI-compatible API: Docker Model Runner (`provider: dmr`, the default), llama.cpp's `llama-server` (`llamacpp`), [llmman](https://github.com/llmmanorg/llmman) (`llmman`) or Ollama (`ollama`) — or any other OpenAI-compatible server or service with `provider: openai` (the OpenAI API by default, Mistral AI, Cerebras, Hugging Face, vLLM, LM Studio…: `mm init` asks its endpoint and the variable that holds its API key).
23+You need Go 1.27+ and a model server that can emit tool calls, through its OpenAI-compatible API: Docker Model Runner (`provider: dmr`, the default), llama.cpp's `llama-server` (`llamacpp`), [llmman](https://github.com/llmmanorg/llmman) (`llmman`), Ollama (`ollama`) or [Kronk](https://www.kronkai.com), the Go inference server of Ardan Labs (`kronk`) — or any other OpenAI-compatible server or service with `provider: openai` (the OpenAI API by default, Mistral AI, Cerebras, Hugging Face, vLLM, LM Studio…: `mm init` asks its endpoint and the variable that holds its API key).
2424
2525 ```bash
2626 git clone https://git.rickub.com/bots-garden/mini-me.git
@@ -44,7 +44,9 @@ Then pick a configuration file. Copy one to `agent.yaml` in your project and adj
4444 | `agent.lsp.yaml` | the same agent with a language server behind an `lsp` tool |
4545 | `agent.mcp-example.yaml` | the same agent with MCP servers |
4646
47-For Ollama, see [Run mini-me on Ollama](docs/en/how-to/run-with-ollama.md) and `demos/ollama/`.
47+For Ollama, see [Run mini-me on Ollama](docs/en/how-to/run-with-ollama.md) and `demos/ollama/`; for Kronk, [Run mini-me on Kronk](docs/en/how-to/run-with-kronk.md) and `demos/kronk/`.
48+
49+Or let `mm init` write the file for you: see [More features](#more-features). To keep a project's mini-me files together, put the agent in `.mm/agent.yaml`: `mm` finds it on its own, and its paths are written from the project root — see [Keep a project's mini-me files in a `.mm` folder](docs/en/how-to/keep-the-config-in-a-mm-folder.md).
4850
4951 ## Use
5052
@@ -53,7 +55,7 @@ The base command is `mm`. Run it **from the directory of the project you want to
5355 ### 1. Terminal, line by line
5456
5557 ```bash
56-mm # reads ./agent.yaml
58+mm # reads ./agent.yaml, else ./.mm/agent.yaml
5759 mm agent.dmr.yaml # or name the file
5860 ```
5961
@@ -96,8 +98,9 @@ Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand
9698 `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.
9799
98100 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 file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked.
99-4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu).
101+4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; **Reload Agent** restarts `mm`: with `sessions.enabled: true` in the YAML it brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu); without it, the conversation starts over and mm says so.
100102 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.
103+6. No agent file yet? Register `mm acp-create` too (`"args": ["acp-create"]`): it asks the questions of `mm init` in the Agent panel, writes the file in the project, and `/zedconfig` then gives the entry to paste here. See [Create an agent from your editor](docs/en/how-to/create-an-agent-from-an-editor.md).
101104
102105 ![mm-acp.png](/bots-garden/mini-me/raw/main/assets/mm-acp.png)
103106
@@ -108,6 +111,7 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
108111 ## More features
109112
110113 - **Create an agent**: `mm init` (or `mm --init`) asks which server, which model (from the server's list), what the agent is for and which machine runs it, then writes a commented agent.yaml — the model's facts read from the server (window, the GGUF's sampling, the reasoning switch), a family table sourced from model cards and the measured demos, every value with its reason; `--check` proves the model answers, flags make it scriptable. For an OpenAI-compatible service (`openai`) it also asks the endpoint and the API key's variable, and lists the service's models when the key is set. See [Create an agent with `mm init`](docs/en/how-to/create-an-agent.md) and `demos/open-ai-compliant/`. From an editor, `mm acp-create` asks the same questions over ACP — forms when the client shows them, a conversation otherwise — and its `/dockerinit`, `/sbxinit` and `/zedconfig` commands prepare the container, the sandbox and the editor's settings: see [Create an agent from your editor](docs/en/how-to/create-an-agent-from-an-editor.md).
114+- **Create a skill**: `mm skillcreate` asks a skill's name, description, keywords and text, then writes `<skillsDir>/<name>/SKILL.md` where the agent reads its skills (`.mm/agent.yaml` included), and checks that mm reads it back. See [Add a skill](docs/en/how-to/add-a-skill.md).
111115 - **One prompt and exit**: `mm -p "…"` — for scripts. See [Run a single prompt](docs/en/how-to/run-a-single-prompt.md).
112116 - **Other agents**: list other mm agents under `mmConfigs`; you ask them with `/ask-<name>`, your agent calls them itself with its `ask_agent` tool, a script with `mm --ask <name>`. See [Let your agent call other agents](docs/en/how-to/let-the-agent-call-other-agents.md).
113117 - **Questions to you**: the agent asks with its `ask_user` tool — pick one, pick several, type a text, yes or no — as numbered choices in the terminal, an arrow-key box in the window, a form or a dialog in an editor; a called agent's questions come up to you too. `askUserTool: false` turns it off. See [Let the agent ask you questions](docs/en/how-to/let-the-agent-ask-you.md).
@@ -129,6 +133,8 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
129133 - **Struggle signals**: when the model loops, fails several tool calls in a row, breaks a tool call, stalls, runs out of turns or fills its context window, mm says so apart from the answer — a coloured badge in the window's footer, a line in the terminal, a paragraph in an editor. The window also shows the completion's duration (`showCompletionTime`). See [Struggling models](docs/en/explanation/struggling-models.md) and `demos/struggle-signals/`.
130134 - **Download the models at start-up**: `autoPull: true` pulls the models the agent will use that the server does not have yet — its own, its `mmConfigs` agents', its routing deciders' — with the progress on screen (llmman, Ollama, Docker Model Runner; llama.cpp gets advice instead). See [Run mini-me with llmman](docs/en/how-to/run-with-llmman.md) and `demos/auto-pull/`.
131135 - **Models that take turns in memory**: `unloadModels: true` on an `mmConfigs` entry unloads the asking agent's model before the hand-off and the asked agent's model after it, so two models are never loaded together. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/coding-helpers-unload-option/`.
136+- **Tools lent by an ACP client**: the program that drives `mm -acp` can give the agent functions of its own — an editor's buffers, an application's data —, which the model calls as `client_<name>` like any other tool (`acp.clientTools`). See [Offer tools from your ACP client](docs/en/how-to/offer-tools-from-an-acp-client.md) and `demo.acp.sdk/client-tools-nodejs/`.
137+- **Models whose template reads `xml_tools`**: SmolLM3 reads its tools from `xml_tools` and writes `<tool_call>` text; `toolFormat: xml_tools` makes it call mm's tools, and `mm init` writes it for the `smollm3` family. See [Give tools to a model whose template reads `xml_tools`](docs/en/how-to/use-a-model-that-reads-xml-tools.md) and `demos/smollm3-demo/`.
132138 - **Use mini-me from another program**: drive `mm -acp` over ACP — `demo.acp.sdk/nodejs/minime.js` gives Node.js `complete`, `stream`, cancellation and a permission callback, with no dependency. See [`demo.acp.sdk/`](demo.acp.sdk/README.en.md).
133139 - **Test a model**: `testing/` holds two benches run by mm itself — a coordinator agent gives small tasks to a candidate agent whose model is under test, checks each result with a command, and writes a Markdown report: the model as a working agent (`testing/model-check/`) and function calling alone, with tools declared in the agent file (`testing/function-calling/`). See [`testing/`](testing/README.en.md).
134140 - **Containers**: `mm dockerinit` writes a `Dockerfile` (the latest release, for amd64 and arm64, not root) and a `compose.yaml` that mounts your project — with a `.mm` project, `.mm/` read-only and the sessions and trace folders writable. See [run mini-me in a container](docs/en/how-to/run-in-a-container.md).
@@ -143,6 +149,8 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
143149 | `demos/dmr/` | mm on Docker Model Runner, with skills, LSP and MCP configurations |
144150 | `demos/llmman/` | mm on llmman, and an agent that asks other agents |
145151 | `demos/ollama/` | mm on Ollama |
152+| `demos/kronk/` | mm on Kronk, a coding agent with `mradermacher/Qwopus3.5-4B-Coder.Q8_0`, and the line to change for Qwen3.5 2B |
153+| `demos/smollm3-demo/` | an agent written by `mm init` for SmolLM3-3B in a `.mm` folder, with `toolFormat: xml_tools` and reasoning off |
146154 | `demos/structured-output/` | agents that answer in JSON, and a coordinator using them |
147155 | `demos/structured-output-pizza/` | one JSON agent declared with Go types: a pizza's name → its ingredients (quantity, unit, category, allergens), whether it is vegetarian, its baking time (Gemma 4 E4B on llmman) |
148156 | `demos/agents-md-file/` | an agent following a project's `AGENTS.md` |
@@ -8,7 +8,7 @@
8 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.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 10
11-From there it grew into the coding agent I wanted for local models (Docker Model Runner, llama.cpp, llmman, Ollama): small enough to be read in an afternoon, and careful about what a small model does wrong:11+From there it grew into the coding agent I wanted for local models (Docker Model Runner, llama.cpp, llmman, Ollama, Kronk): small enough to be read in an afternoon, and careful about what a small model does wrong:
12 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;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;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;
@@ -20,7 +20,7 @@ What makes Mini Me a bit special is that it was designed from the start to be us
20 20
21 ## Install21 ## Install
22 22
23-You need Go 1.27+ and a model server that can emit tool calls, through its OpenAI-compatible API: Docker Model Runner (`provider: dmr`, the default), llama.cpp's `llama-server` (`llamacpp`), [llmman](https://github.com/llmmanorg/llmman) (`llmman`) or Ollama (`ollama`) — or any other OpenAI-compatible server or service with `provider: openai` (the OpenAI API by default, Mistral AI, Cerebras, Hugging Face, vLLM, LM Studio…: `mm init` asks its endpoint and the variable that holds its API key).23+You need Go 1.27+ and a model server that can emit tool calls, through its OpenAI-compatible API: Docker Model Runner (`provider: dmr`, the default), llama.cpp's `llama-server` (`llamacpp`), [llmman](https://github.com/llmmanorg/llmman) (`llmman`), Ollama (`ollama`) or [Kronk](https://www.kronkai.com), the Go inference server of Ardan Labs (`kronk`) — or any other OpenAI-compatible server or service with `provider: openai` (the OpenAI API by default, Mistral AI, Cerebras, Hugging Face, vLLM, LM Studio…: `mm init` asks its endpoint and the variable that holds its API key).
24 24
25 ```bash25 ```bash
26 git clone https://git.rickub.com/bots-garden/mini-me.git26 git clone https://git.rickub.com/bots-garden/mini-me.git
@@ -44,7 +44,9 @@ Then pick a configuration file. Copy one to `agent.yaml` in your project and adj
44 | `agent.lsp.yaml` | the same agent with a language server behind an `lsp` tool |44 | `agent.lsp.yaml` | the same agent with a language server behind an `lsp` tool |
45 | `agent.mcp-example.yaml` | the same agent with MCP servers |45 | `agent.mcp-example.yaml` | the same agent with MCP servers |
46 46
47-For Ollama, see [Run mini-me on Ollama](docs/en/how-to/run-with-ollama.md) and `demos/ollama/`.47+For Ollama, see [Run mini-me on Ollama](docs/en/how-to/run-with-ollama.md) and `demos/ollama/`; for Kronk, [Run mini-me on Kronk](docs/en/how-to/run-with-kronk.md) and `demos/kronk/`.
48+
49+Or let `mm init` write the file for you: see [More features](#more-features). To keep a project's mini-me files together, put the agent in `.mm/agent.yaml`: `mm` finds it on its own, and its paths are written from the project root — see [Keep a project's mini-me files in a `.mm` folder](docs/en/how-to/keep-the-config-in-a-mm-folder.md).
48 50
49 ## Use51 ## Use
50 52
@@ -53,7 +55,7 @@ The base command is `mm`. Run it **from the directory of the project you want to
53 ### 1. Terminal, line by line55 ### 1. Terminal, line by line
54 56
55 ```bash57 ```bash
56-mm # reads ./agent.yaml58+mm # reads ./agent.yaml, else ./.mm/agent.yaml
57 mm agent.dmr.yaml # or name the file59 mm agent.dmr.yaml # or name the file
58 ```60 ```
59 61
@@ -96,8 +98,9 @@ Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand
96 `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.98 `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.
97 99
98 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 file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked.100 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 file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked.
99-4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu).101+4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; **Reload Agent** restarts `mm`: with `sessions.enabled: true` in the YAML it brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu); without it, the conversation starts over and mm says so.
100 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.102 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.
103+6. No agent file yet? Register `mm acp-create` too (`"args": ["acp-create"]`): it asks the questions of `mm init` in the Agent panel, writes the file in the project, and `/zedconfig` then gives the entry to paste here. See [Create an agent from your editor](docs/en/how-to/create-an-agent-from-an-editor.md).
101 104
102 ![mm-acp.png](/bots-garden/mini-me/raw/main/assets/mm-acp.png)105 ![mm-acp.png](/bots-garden/mini-me/raw/main/assets/mm-acp.png)
103 106
@@ -108,6 +111,7 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
108 ## More features111 ## More features
109 112
110 - **Create an agent**: `mm init` (or `mm --init`) asks which server, which model (from the server's list), what the agent is for and which machine runs it, then writes a commented agent.yaml — the model's facts read from the server (window, the GGUF's sampling, the reasoning switch), a family table sourced from model cards and the measured demos, every value with its reason; `--check` proves the model answers, flags make it scriptable. For an OpenAI-compatible service (`openai`) it also asks the endpoint and the API key's variable, and lists the service's models when the key is set. See [Create an agent with `mm init`](docs/en/how-to/create-an-agent.md) and `demos/open-ai-compliant/`. From an editor, `mm acp-create` asks the same questions over ACP — forms when the client shows them, a conversation otherwise — and its `/dockerinit`, `/sbxinit` and `/zedconfig` commands prepare the container, the sandbox and the editor's settings: see [Create an agent from your editor](docs/en/how-to/create-an-agent-from-an-editor.md).113 - **Create an agent**: `mm init` (or `mm --init`) asks which server, which model (from the server's list), what the agent is for and which machine runs it, then writes a commented agent.yaml — the model's facts read from the server (window, the GGUF's sampling, the reasoning switch), a family table sourced from model cards and the measured demos, every value with its reason; `--check` proves the model answers, flags make it scriptable. For an OpenAI-compatible service (`openai`) it also asks the endpoint and the API key's variable, and lists the service's models when the key is set. See [Create an agent with `mm init`](docs/en/how-to/create-an-agent.md) and `demos/open-ai-compliant/`. From an editor, `mm acp-create` asks the same questions over ACP — forms when the client shows them, a conversation otherwise — and its `/dockerinit`, `/sbxinit` and `/zedconfig` commands prepare the container, the sandbox and the editor's settings: see [Create an agent from your editor](docs/en/how-to/create-an-agent-from-an-editor.md).
114+- **Create a skill**: `mm skillcreate` asks a skill's name, description, keywords and text, then writes `<skillsDir>/<name>/SKILL.md` where the agent reads its skills (`.mm/agent.yaml` included), and checks that mm reads it back. See [Add a skill](docs/en/how-to/add-a-skill.md).
111 - **One prompt and exit**: `mm -p "…"` — for scripts. See [Run a single prompt](docs/en/how-to/run-a-single-prompt.md).115 - **One prompt and exit**: `mm -p "…"` — for scripts. See [Run a single prompt](docs/en/how-to/run-a-single-prompt.md).
112 - **Other agents**: list other mm agents under `mmConfigs`; you ask them with `/ask-<name>`, your agent calls them itself with its `ask_agent` tool, a script with `mm --ask <name>`. See [Let your agent call other agents](docs/en/how-to/let-the-agent-call-other-agents.md).116 - **Other agents**: list other mm agents under `mmConfigs`; you ask them with `/ask-<name>`, your agent calls them itself with its `ask_agent` tool, a script with `mm --ask <name>`. See [Let your agent call other agents](docs/en/how-to/let-the-agent-call-other-agents.md).
113 - **Questions to you**: the agent asks with its `ask_user` tool — pick one, pick several, type a text, yes or no — as numbered choices in the terminal, an arrow-key box in the window, a form or a dialog in an editor; a called agent's questions come up to you too. `askUserTool: false` turns it off. See [Let the agent ask you questions](docs/en/how-to/let-the-agent-ask-you.md).117 - **Questions to you**: the agent asks with its `ask_user` tool — pick one, pick several, type a text, yes or no — as numbered choices in the terminal, an arrow-key box in the window, a form or a dialog in an editor; a called agent's questions come up to you too. `askUserTool: false` turns it off. See [Let the agent ask you questions](docs/en/how-to/let-the-agent-ask-you.md).
@@ -129,6 +133,8 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
129 - **Struggle signals**: when the model loops, fails several tool calls in a row, breaks a tool call, stalls, runs out of turns or fills its context window, mm says so apart from the answer — a coloured badge in the window's footer, a line in the terminal, a paragraph in an editor. The window also shows the completion's duration (`showCompletionTime`). See [Struggling models](docs/en/explanation/struggling-models.md) and `demos/struggle-signals/`.133 - **Struggle signals**: when the model loops, fails several tool calls in a row, breaks a tool call, stalls, runs out of turns or fills its context window, mm says so apart from the answer — a coloured badge in the window's footer, a line in the terminal, a paragraph in an editor. The window also shows the completion's duration (`showCompletionTime`). See [Struggling models](docs/en/explanation/struggling-models.md) and `demos/struggle-signals/`.
130 - **Download the models at start-up**: `autoPull: true` pulls the models the agent will use that the server does not have yet — its own, its `mmConfigs` agents', its routing deciders' — with the progress on screen (llmman, Ollama, Docker Model Runner; llama.cpp gets advice instead). See [Run mini-me with llmman](docs/en/how-to/run-with-llmman.md) and `demos/auto-pull/`.134 - **Download the models at start-up**: `autoPull: true` pulls the models the agent will use that the server does not have yet — its own, its `mmConfigs` agents', its routing deciders' — with the progress on screen (llmman, Ollama, Docker Model Runner; llama.cpp gets advice instead). See [Run mini-me with llmman](docs/en/how-to/run-with-llmman.md) and `demos/auto-pull/`.
131 - **Models that take turns in memory**: `unloadModels: true` on an `mmConfigs` entry unloads the asking agent's model before the hand-off and the asked agent's model after it, so two models are never loaded together. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/coding-helpers-unload-option/`.135 - **Models that take turns in memory**: `unloadModels: true` on an `mmConfigs` entry unloads the asking agent's model before the hand-off and the asked agent's model after it, so two models are never loaded together. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/coding-helpers-unload-option/`.
136+- **Tools lent by an ACP client**: the program that drives `mm -acp` can give the agent functions of its own — an editor's buffers, an application's data —, which the model calls as `client_<name>` like any other tool (`acp.clientTools`). See [Offer tools from your ACP client](docs/en/how-to/offer-tools-from-an-acp-client.md) and `demo.acp.sdk/client-tools-nodejs/`.
137+- **Models whose template reads `xml_tools`**: SmolLM3 reads its tools from `xml_tools` and writes `<tool_call>` text; `toolFormat: xml_tools` makes it call mm's tools, and `mm init` writes it for the `smollm3` family. See [Give tools to a model whose template reads `xml_tools`](docs/en/how-to/use-a-model-that-reads-xml-tools.md) and `demos/smollm3-demo/`.
132 - **Use mini-me from another program**: drive `mm -acp` over ACP — `demo.acp.sdk/nodejs/minime.js` gives Node.js `complete`, `stream`, cancellation and a permission callback, with no dependency. See [`demo.acp.sdk/`](demo.acp.sdk/README.en.md).138 - **Use mini-me from another program**: drive `mm -acp` over ACP — `demo.acp.sdk/nodejs/minime.js` gives Node.js `complete`, `stream`, cancellation and a permission callback, with no dependency. See [`demo.acp.sdk/`](demo.acp.sdk/README.en.md).
133 - **Test a model**: `testing/` holds two benches run by mm itself — a coordinator agent gives small tasks to a candidate agent whose model is under test, checks each result with a command, and writes a Markdown report: the model as a working agent (`testing/model-check/`) and function calling alone, with tools declared in the agent file (`testing/function-calling/`). See [`testing/`](testing/README.en.md).139 - **Test a model**: `testing/` holds two benches run by mm itself — a coordinator agent gives small tasks to a candidate agent whose model is under test, checks each result with a command, and writes a Markdown report: the model as a working agent (`testing/model-check/`) and function calling alone, with tools declared in the agent file (`testing/function-calling/`). See [`testing/`](testing/README.en.md).
134 - **Containers**: `mm dockerinit` writes a `Dockerfile` (the latest release, for amd64 and arm64, not root) and a `compose.yaml` that mounts your project — with a `.mm` project, `.mm/` read-only and the sessions and trace folders writable. See [run mini-me in a container](docs/en/how-to/run-in-a-container.md).140 - **Containers**: `mm dockerinit` writes a `Dockerfile` (the latest release, for amd64 and arm64, not root) and a `compose.yaml` that mounts your project — with a `.mm` project, `.mm/` read-only and the sessions and trace folders writable. See [run mini-me in a container](docs/en/how-to/run-in-a-container.md).
@@ -143,6 +149,8 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
143 | `demos/dmr/` | mm on Docker Model Runner, with skills, LSP and MCP configurations |149 | `demos/dmr/` | mm on Docker Model Runner, with skills, LSP and MCP configurations |
144 | `demos/llmman/` | mm on llmman, and an agent that asks other agents |150 | `demos/llmman/` | mm on llmman, and an agent that asks other agents |
145 | `demos/ollama/` | mm on Ollama |151 | `demos/ollama/` | mm on Ollama |
152+| `demos/kronk/` | mm on Kronk, a coding agent with `mradermacher/Qwopus3.5-4B-Coder.Q8_0`, and the line to change for Qwen3.5 2B |
153+| `demos/smollm3-demo/` | an agent written by `mm init` for SmolLM3-3B in a `.mm` folder, with `toolFormat: xml_tools` and reasoning off |
146 | `demos/structured-output/` | agents that answer in JSON, and a coordinator using them |154 | `demos/structured-output/` | agents that answer in JSON, and a coordinator using them |
147 | `demos/structured-output-pizza/` | one JSON agent declared with Go types: a pizza's name → its ingredients (quantity, unit, category, allergens), whether it is vegetarian, its baking time (Gemma 4 E4B on llmman) |155 | `demos/structured-output-pizza/` | one JSON agent declared with Go types: a pizza's name → its ingredients (quantity, unit, category, allergens), whether it is vegetarian, its baking time (Gemma 4 E4B on llmman) |
148 | `demos/agents-md-file/` | an agent following a project's `AGENTS.md` |156 | `demos/agents-md-file/` | an agent following a project's `AGENTS.md` |
modified docs/en/how-to/run-in-a-container.md +10 -1
@@ -93,14 +93,23 @@ docker buildx build --platform linux/amd64,linux/arm64 -t <registry>/mm-bob:late
9393
9494 Building for the other architecture needs emulation (QEMU, included in Docker Desktop). Without it, the build stops with `exec format error`.
9595
96+An image built this way, outside compose, gives its `mm` account the uid and gid 1000. Whoever runs it with other numbers gets a user with no name in the container: add `--build-arg MM_UID=… --build-arg MM_GID=…` to build it for them.
97+
9698 ## Use the files on another machine
9799
98-`compose.yaml` records the uid and gid of the machine that ran `mm dockerinit`, so that the files the agent creates belong to you. On a machine where yours differ, set them:
100+`compose.yaml` records the uid and gid of the machine that ran `mm dockerinit`. The container runs as that user, so that the files the agent creates belong to you, and the image's `mm` account is built with the same numbers, so that every program in the container knows who it runs as — `whoami`, the shell's prompt, ssh. On a machine where yours differ, set them, then rebuild the image:
99101
100102 ```bash
101103 export MM_UID=$(id -u) MM_GID=$(id -g)
104+docker compose up -d --build
102105 ```
103106
107+Without the rebuild, the container runs as your uid while the image's account keeps the old one: the shell's prompt then reads `I have no name!` and `whoami` fails.
108+
109+Files written by `mm dockerinit` before v1.1.0 always built the account with uid 1000 — on a Mac, whose user is uid 501, the shell said `I have no name!`. Run `mm dockerinit` again (answer `yes` to replace both files), then `docker compose up -d --build`.
110+
111+git in the container still needs an identity to commit: the account has a name, but git does not make one up from it. Set `user.name` and `user.email` in the project (`git config user.name …`), or pass `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME` and `GIT_COMMITTER_EMAIL` to the container.
112+
104113 ## What the container protects
105114
106115 The agent does not run as root, but it can still change and delete everything in the mounted project, and it has network access. Keep the project under version control, and use `yolo: false` if you want to approve each command: see [confirm tool calls](confirm-tool-calls.md).
@@ -93,14 +93,23 @@ docker buildx build --platform linux/amd64,linux/arm64 -t <registry>/mm-bob:late
93 93
94 Building for the other architecture needs emulation (QEMU, included in Docker Desktop). Without it, the build stops with `exec format error`.94 Building for the other architecture needs emulation (QEMU, included in Docker Desktop). Without it, the build stops with `exec format error`.
95 95
96+An image built this way, outside compose, gives its `mm` account the uid and gid 1000. Whoever runs it with other numbers gets a user with no name in the container: add `--build-arg MM_UID=… --build-arg MM_GID=…` to build it for them.
97+
96 ## Use the files on another machine98 ## Use the files on another machine
97 99
98-`compose.yaml` records the uid and gid of the machine that ran `mm dockerinit`, so that the files the agent creates belong to you. On a machine where yours differ, set them:100+`compose.yaml` records the uid and gid of the machine that ran `mm dockerinit`. The container runs as that user, so that the files the agent creates belong to you, and the image's `mm` account is built with the same numbers, so that every program in the container knows who it runs as — `whoami`, the shell's prompt, ssh. On a machine where yours differ, set them, then rebuild the image:
99 101
100 ```bash102 ```bash
101 export MM_UID=$(id -u) MM_GID=$(id -g)103 export MM_UID=$(id -u) MM_GID=$(id -g)
104+docker compose up -d --build
102 ```105 ```
103 106
107+Without the rebuild, the container runs as your uid while the image's account keeps the old one: the shell's prompt then reads `I have no name!` and `whoami` fails.
108+
109+Files written by `mm dockerinit` before v1.1.0 always built the account with uid 1000 — on a Mac, whose user is uid 501, the shell said `I have no name!`. Run `mm dockerinit` again (answer `yes` to replace both files), then `docker compose up -d --build`.
110+
111+git in the container still needs an identity to commit: the account has a name, but git does not make one up from it. Set `user.name` and `user.email` in the project (`git config user.name …`), or pass `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME` and `GIT_COMMITTER_EMAIL` to the container.
112+
104 ## What the container protects113 ## What the container protects
105 114
106 The agent does not run as root, but it can still change and delete everything in the mounted project, and it has network access. Keep the project under version control, and use `yolo: false` if you want to approve each command: see [confirm tool calls](confirm-tool-calls.md).115 The agent does not run as root, but it can still change and delete everything in the mounted project, and it has network access. Keep the project under version control, and use `yolo: false` if you want to approve each command: see [confirm tool calls](confirm-tool-calls.md).
modified docs/en/reference/cli.md +1 -1
@@ -217,7 +217,7 @@ Each existing file is asked about, the default being `no`: that file is kept, th
217217
218218 | In `compose.yaml` | Meaning |
219219 |-------------------|---------|
220-| `MM_UID`, `MM_GID` | The user the agent runs as. Default: the uid and gid of the machine that ran `mm dockerinit`, or `1000` if that is root or if there is none |
220+| `MM_UID`, `MM_GID` | The user the agent runs as (`user:`), and the uid and gid of the image's `mm` account (build arguments, so a change needs `docker compose up -d --build`). Default: the uid and gid of the machine that ran `mm dockerinit`, or `1000` if that is root or if there is none; `1000` for an image built without compose |
221221
222222 ## `mm sbxinit` — the sandbox file
223223
@@ -217,7 +217,7 @@ Each existing file is asked about, the default being `no`: that file is kept, th
217 217
218 | In `compose.yaml` | Meaning |218 | In `compose.yaml` | Meaning |
219 |-------------------|---------|219 |-------------------|---------|
220-| `MM_UID`, `MM_GID` | The user the agent runs as. Default: the uid and gid of the machine that ran `mm dockerinit`, or `1000` if that is root or if there is none |220+| `MM_UID`, `MM_GID` | The user the agent runs as (`user:`), and the uid and gid of the image's `mm` account (build arguments, so a change needs `docker compose up -d --build`). Default: the uid and gid of the machine that ran `mm dockerinit`, or `1000` if that is root or if there is none; `1000` for an image built without compose |
221 221
222 ## `mm sbxinit` — the sandbox file222 ## `mm sbxinit` — the sandbox file
223 223
modified docs/fr/how-to/run-in-a-container.md +10 -1
@@ -93,14 +93,23 @@ docker buildx build --platform linux/amd64,linux/arm64 -t <registry>/mm-bob:late
9393
9494 Construire pour l'autre architecture demande une émulation (QEMU, inclus dans Docker Desktop). Sans elle, le build s'arrête sur `exec format error`.
9595
96+Une image construite ainsi, hors de compose, donne à son compte `mm` l'uid et le gid 1000. Qui la lance avec d'autres numéros obtient un utilisateur sans nom dans le conteneur : ajoutez `--build-arg MM_UID=… --build-arg MM_GID=…` pour la construire pour lui.
97+
9698 ## Utiliser les fichiers sur une autre machine
9799
98-`compose.yaml` enregistre l'uid et le gid de la machine qui a lancé `mm dockerinit`, pour que les fichiers créés par l'agent vous appartiennent. Sur une machine où les vôtres diffèrent, définissez-les :
100+`compose.yaml` enregistre l'uid et le gid de la machine qui a lancé `mm dockerinit`. Le conteneur tourne sous cet utilisateur, pour que les fichiers créés par l'agent vous appartiennent, et le compte `mm` de l'image est construit avec les mêmes numéros, pour que chaque programme du conteneur sache sous quel utilisateur il tourne — `whoami`, le prompt du shell, ssh. Sur une machine où les vôtres diffèrent, définissez-les, puis reconstruisez l'image :
99101
100102 ```bash
101103 export MM_UID=$(id -u) MM_GID=$(id -g)
104+docker compose up -d --build
102105 ```
103106
107+Sans reconstruction, le conteneur tourne sous votre uid alors que le compte de l'image garde l'ancien : le prompt du shell affiche alors `I have no name!` et `whoami` échoue.
108+
109+Les fichiers écrits par `mm dockerinit` avant la v1.1.0 construisaient toujours le compte avec l'uid 1000 — sur un Mac, dont l'utilisateur a l'uid 501, le shell affichait `I have no name!`. Relancez `mm dockerinit` (répondez `yes` pour remplacer les deux fichiers), puis `docker compose up -d --build`.
110+
111+Pour committer, git a toujours besoin d'une identité dans le conteneur : le compte a un nom, mais git n'en déduit pas d'identité. Définissez `user.name` et `user.email` dans le projet (`git config user.name …`), ou passez `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME` et `GIT_COMMITTER_EMAIL` au conteneur.
112+
104113 ## Ce que le conteneur protège
105114
106115 L'agent ne tourne pas en root, mais il peut quand même modifier et supprimer tout ce qui se trouve dans le projet monté, et il a accès au réseau. Gardez le projet sous gestion de version, et utilisez `yolo: false` si vous voulez approuver chaque commande : voir [confirmer les appels d'outils](confirm-tool-calls.md).
@@ -93,14 +93,23 @@ docker buildx build --platform linux/amd64,linux/arm64 -t <registry>/mm-bob:late
93 93
94 Construire pour l'autre architecture demande une émulation (QEMU, inclus dans Docker Desktop). Sans elle, le build s'arrête sur `exec format error`.94 Construire pour l'autre architecture demande une émulation (QEMU, inclus dans Docker Desktop). Sans elle, le build s'arrête sur `exec format error`.
95 95
96+Une image construite ainsi, hors de compose, donne à son compte `mm` l'uid et le gid 1000. Qui la lance avec d'autres numéros obtient un utilisateur sans nom dans le conteneur : ajoutez `--build-arg MM_UID=… --build-arg MM_GID=…` pour la construire pour lui.
97+
96 ## Utiliser les fichiers sur une autre machine98 ## Utiliser les fichiers sur une autre machine
97 99
98-`compose.yaml` enregistre l'uid et le gid de la machine qui a lancé `mm dockerinit`, pour que les fichiers créés par l'agent vous appartiennent. Sur une machine où les vôtres diffèrent, définissez-les :100+`compose.yaml` enregistre l'uid et le gid de la machine qui a lancé `mm dockerinit`. Le conteneur tourne sous cet utilisateur, pour que les fichiers créés par l'agent vous appartiennent, et le compte `mm` de l'image est construit avec les mêmes numéros, pour que chaque programme du conteneur sache sous quel utilisateur il tourne — `whoami`, le prompt du shell, ssh. Sur une machine où les vôtres diffèrent, définissez-les, puis reconstruisez l'image :
99 101
100 ```bash102 ```bash
101 export MM_UID=$(id -u) MM_GID=$(id -g)103 export MM_UID=$(id -u) MM_GID=$(id -g)
104+docker compose up -d --build
102 ```105 ```
103 106
107+Sans reconstruction, le conteneur tourne sous votre uid alors que le compte de l'image garde l'ancien : le prompt du shell affiche alors `I have no name!` et `whoami` échoue.
108+
109+Les fichiers écrits par `mm dockerinit` avant la v1.1.0 construisaient toujours le compte avec l'uid 1000 — sur un Mac, dont l'utilisateur a l'uid 501, le shell affichait `I have no name!`. Relancez `mm dockerinit` (répondez `yes` pour remplacer les deux fichiers), puis `docker compose up -d --build`.
110+
111+Pour committer, git a toujours besoin d'une identité dans le conteneur : le compte a un nom, mais git n'en déduit pas d'identité. Définissez `user.name` et `user.email` dans le projet (`git config user.name …`), ou passez `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME` et `GIT_COMMITTER_EMAIL` au conteneur.
112+
104 ## Ce que le conteneur protège113 ## Ce que le conteneur protège
105 114
106 L'agent ne tourne pas en root, mais il peut quand même modifier et supprimer tout ce qui se trouve dans le projet monté, et il a accès au réseau. Gardez le projet sous gestion de version, et utilisez `yolo: false` si vous voulez approuver chaque commande : voir [confirmer les appels d'outils](confirm-tool-calls.md).115 L'agent ne tourne pas en root, mais il peut quand même modifier et supprimer tout ce qui se trouve dans le projet monté, et il a accès au réseau. Gardez le projet sous gestion de version, et utilisez `yolo: false` si vous voulez approuver chaque commande : voir [confirmer les appels d'outils](confirm-tool-calls.md).
modified docs/fr/reference/cli.md +1 -1
@@ -217,7 +217,7 @@ La question est posée pour chaque fichier existant, avec `no` par défaut : ce
217217
218218 | Dans `compose.yaml` | Signification |
219219 |---------------------|---------------|
220-| `MM_UID`, `MM_GID` | L'utilisateur de l'agent. Par défaut : l'uid et le gid de la machine qui a lancé `mm dockerinit`, ou `1000` si c'est root ou s'il n'y en a pas |
220+| `MM_UID`, `MM_GID` | L'utilisateur de l'agent (`user:`), et l'uid et le gid du compte `mm` de l'image (arguments de build : un changement demande `docker compose up -d --build`). Par défaut : l'uid et le gid de la machine qui a lancé `mm dockerinit`, ou `1000` si c'est root ou s'il n'y en a pas ; `1000` pour une image construite sans compose |
221221
222222 ## `mm sbxinit` — le fichier de la sandbox
223223
@@ -217,7 +217,7 @@ La question est posée pour chaque fichier existant, avec `no` par défaut : ce
217 217
218 | Dans `compose.yaml` | Signification |218 | Dans `compose.yaml` | Signification |
219 |---------------------|---------------|219 |---------------------|---------------|
220-| `MM_UID`, `MM_GID` | L'utilisateur de l'agent. Par défaut : l'uid et le gid de la machine qui a lancé `mm dockerinit`, ou `1000` si c'est root ou s'il n'y en a pas |220+| `MM_UID`, `MM_GID` | L'utilisateur de l'agent (`user:`), et l'uid et le gid du compte `mm` de l'image (arguments de build : un changement demande `docker compose up -d --build`). Par défaut : l'uid et le gid de la machine qui a lancé `mm dockerinit`, ou `1000` si c'est root ou s'il n'y en a pas ; `1000` pour une image construite sans compose |
221 221
222 ## `mm sbxinit` — le fichier de la sandbox222 ## `mm sbxinit` — le fichier de la sandbox
223 223
modified internal/dockerinit/dockerinit_test.go +42 -5
@@ -13,7 +13,10 @@ import (
1313 // composeFile is the part of compose.yaml the tests read back.
1414 type composeFile struct {
1515 Services map[string]struct {
16- Build string `yaml:"build"`
16+ Build struct {
17+ Context string `yaml:"context"`
18+ Args map[string]string `yaml:"args"`
19+ } `yaml:"build"`
1720 Image string `yaml:"image"`
1821 ContainerName string `yaml:"container_name"`
1922 User string `yaml:"user"`
@@ -34,12 +37,17 @@ func TestComposeIsValidYAMLWithWhatTheAgentNeeds(t *testing.T) {
3437 if !ok {
3538 t.Fatalf("no service bob: %+v", parsed)
3639 }
37- if service.Build != "." || service.Image != "mm-bob:latest" || service.ContainerName != "bob-box" {
40+ if service.Build.Context != "." || service.Image != "mm-bob:latest" || service.ContainerName != "bob-box" {
3841 t.Errorf("names: %+v", service)
3942 }
4043 if service.User != "${MM_UID:-501}:${MM_GID:-20}" {
4144 t.Errorf("user = %q", service.User)
4245 }
46+ // The image's account has the numbers the container runs as (ticket
47+ // 216): with other numbers, uid 501 has no name ("I have no name!").
48+ if service.User != service.Build.Args["MM_UID"]+":"+service.Build.Args["MM_GID"] {
49+ t.Errorf("build args %v do not match user %q", service.Build.Args, service.User)
50+ }
4351 if want := ".:" + service.WorkingDir; len(service.Volumes) != 1 || service.Volumes[0] != want {
4452 t.Errorf("the project is not mounted at the working directory: %v, %q", service.Volumes, service.WorkingDir)
4553 }
@@ -54,15 +62,44 @@ func TestComposeIsValidYAMLWithWhatTheAgentNeeds(t *testing.T) {
5462 }
5563 }
5664
57-// The compose file is written by whoever runs mm dockerinit, sudo included.
65+// The compose file is written by whoever runs mm dockerinit, sudo included:
66+// neither the container's user nor the image's account is ever root.
5867 func TestComposeNeverDefaultsToRoot(t *testing.T) {
5968 for _, ids := range [][2]int{{0, 0}, {-1, -1}} {
60- if text := Compose("bob", ids[0], ids[1]); !strings.Contains(text, `"${MM_UID:-1000}:${MM_GID:-1000}"`) {
61- t.Errorf("ids %v:\n%s", ids, text)
69+ text := Compose("bob", ids[0], ids[1])
70+ for _, want := range []string{`"${MM_UID:-1000}:${MM_GID:-1000}"`, "MM_UID: ${MM_UID:-1000}\n", "MM_GID: ${MM_GID:-1000}\n"} {
71+ if !strings.Contains(text, want) {
72+ t.Errorf("ids %v: no %q in\n%s", ids, want, text)
73+ }
6274 }
6375 }
6476 }
6577
78+// The image's account takes the uid and gid compose passes (ticket 216),
79+// tolerates numbers that already exist in the image (-o), keeps 1000 as the
80+// default for an image built without compose, and is created after mm's
81+// download, so that a new uid does not download mm again.
82+func TestDockerfileAccountTakesTheUsersNumbers(t *testing.T) {
83+ text := Dockerfile()
84+ for _, want := range []string{
85+ "ARG MM_UID=1000",
86+ "ARG MM_GID=1000",
87+ `groupadd -o -g "$MM_GID" mm`,
88+ `useradd -o --create-home --uid "$MM_UID" --gid "$MM_GID" mm`,
89+ "chmod 777 /home/mm",
90+ } {
91+ if !strings.Contains(text, want) {
92+ t.Errorf("missing %q", want)
93+ }
94+ }
95+ if strings.Contains(text, "--uid 1000") {
96+ t.Error("the account still has a fixed uid")
97+ }
98+ if strings.Index(text, "ARG MM_UID") < strings.Index(text, "install -m 755") {
99+ t.Error("ARG MM_UID comes before mm's download: a new uid would download mm again")
100+ }
101+}
102+
66103 func TestDockerfileRunsTheLatestReleaseAsNonRootOnBothArchitectures(t *testing.T) {
67104 text := Dockerfile()
68105 for _, want := range []string{
@@ -13,7 +13,10 @@ import (
13 // composeFile is the part of compose.yaml the tests read back.13 // composeFile is the part of compose.yaml the tests read back.
14 type composeFile struct {14 type composeFile struct {
15 Services map[string]struct {15 Services map[string]struct {
16- Build string `yaml:"build"`16+ Build struct {
17+ Context string `yaml:"context"`
18+ Args map[string]string `yaml:"args"`
19+ } `yaml:"build"`
17 Image string `yaml:"image"`20 Image string `yaml:"image"`
18 ContainerName string `yaml:"container_name"`21 ContainerName string `yaml:"container_name"`
19 User string `yaml:"user"`22 User string `yaml:"user"`
@@ -34,12 +37,17 @@ func TestComposeIsValidYAMLWithWhatTheAgentNeeds(t *testing.T) {
34 if !ok {37 if !ok {
35 t.Fatalf("no service bob: %+v", parsed)38 t.Fatalf("no service bob: %+v", parsed)
36 }39 }
37- if service.Build != "." || service.Image != "mm-bob:latest" || service.ContainerName != "bob-box" {40+ if service.Build.Context != "." || service.Image != "mm-bob:latest" || service.ContainerName != "bob-box" {
38 t.Errorf("names: %+v", service)41 t.Errorf("names: %+v", service)
39 }42 }
40 if service.User != "${MM_UID:-501}:${MM_GID:-20}" {43 if service.User != "${MM_UID:-501}:${MM_GID:-20}" {
41 t.Errorf("user = %q", service.User)44 t.Errorf("user = %q", service.User)
42 }45 }
46+ // The image's account has the numbers the container runs as (ticket
47+ // 216): with other numbers, uid 501 has no name ("I have no name!").
48+ if service.User != service.Build.Args["MM_UID"]+":"+service.Build.Args["MM_GID"] {
49+ t.Errorf("build args %v do not match user %q", service.Build.Args, service.User)
50+ }
43 if want := ".:" + service.WorkingDir; len(service.Volumes) != 1 || service.Volumes[0] != want {51 if want := ".:" + service.WorkingDir; len(service.Volumes) != 1 || service.Volumes[0] != want {
44 t.Errorf("the project is not mounted at the working directory: %v, %q", service.Volumes, service.WorkingDir)52 t.Errorf("the project is not mounted at the working directory: %v, %q", service.Volumes, service.WorkingDir)
45 }53 }
@@ -54,15 +62,44 @@ func TestComposeIsValidYAMLWithWhatTheAgentNeeds(t *testing.T) {
54 }62 }
55 }63 }
56 64
57-// The compose file is written by whoever runs mm dockerinit, sudo included.65+// The compose file is written by whoever runs mm dockerinit, sudo included:
66+// neither the container's user nor the image's account is ever root.
58 func TestComposeNeverDefaultsToRoot(t *testing.T) {67 func TestComposeNeverDefaultsToRoot(t *testing.T) {
59 for _, ids := range [][2]int{{0, 0}, {-1, -1}} {68 for _, ids := range [][2]int{{0, 0}, {-1, -1}} {
60- if text := Compose("bob", ids[0], ids[1]); !strings.Contains(text, `"${MM_UID:-1000}:${MM_GID:-1000}"`) {69+ text := Compose("bob", ids[0], ids[1])
61- t.Errorf("ids %v:\n%s", ids, text)70+ for _, want := range []string{`"${MM_UID:-1000}:${MM_GID:-1000}"`, "MM_UID: ${MM_UID:-1000}\n", "MM_GID: ${MM_GID:-1000}\n"} {
71+ if !strings.Contains(text, want) {
72+ t.Errorf("ids %v: no %q in\n%s", ids, want, text)
73+ }
62 }74 }
63 }75 }
64 }76 }
65 77
78+// The image's account takes the uid and gid compose passes (ticket 216),
79+// tolerates numbers that already exist in the image (-o), keeps 1000 as the
80+// default for an image built without compose, and is created after mm's
81+// download, so that a new uid does not download mm again.
82+func TestDockerfileAccountTakesTheUsersNumbers(t *testing.T) {
83+ text := Dockerfile()
84+ for _, want := range []string{
85+ "ARG MM_UID=1000",
86+ "ARG MM_GID=1000",
87+ `groupadd -o -g "$MM_GID" mm`,
88+ `useradd -o --create-home --uid "$MM_UID" --gid "$MM_GID" mm`,
89+ "chmod 777 /home/mm",
90+ } {
91+ if !strings.Contains(text, want) {
92+ t.Errorf("missing %q", want)
93+ }
94+ }
95+ if strings.Contains(text, "--uid 1000") {
96+ t.Error("the account still has a fixed uid")
97+ }
98+ if strings.Index(text, "ARG MM_UID") < strings.Index(text, "install -m 755") {
99+ t.Error("ARG MM_UID comes before mm's download: a new uid would download mm again")
100+ }
101+}
102+
66 func TestDockerfileRunsTheLatestReleaseAsNonRootOnBothArchitectures(t *testing.T) {103 func TestDockerfileRunsTheLatestReleaseAsNonRootOnBothArchitectures(t *testing.T) {
67 text := Dockerfile()104 text := Dockerfile()
68 for _, want := range []string{105 for _, want := range []string{
modified internal/dockerinit/files.go +20 -4
@@ -55,9 +55,18 @@ RUN set -eu; \
5555 rm -f "$asset" SHA256SUMS; \
5656 mm --version
5757
58-# The agent never runs as root. compose.yaml may choose another uid, which has
59-# no account in the image: HOME is written to by whichever uid it is.
60-RUN useradd --create-home --uid 1000 mm && chmod 777 /home/mm
58+# The agent never runs as root. Its account has the uid and gid of the user
59+# who runs compose (compose.yaml passes MM_UID and MM_GID), so that every
60+# program finds it in /etc/passwd — whoami, git, ssh, bash's prompt — and the
61+# files it writes in the project belong to that user. -o: the gid, or the uid,
62+# may already exist in the image (20 is Debian's dialout). An image built
63+# without compose gets 1000. Here, not at the top: a new uid rebuilds this
64+# layer only, not mm's download. HOME stays writable for any uid.
65+ARG MM_UID=1000
66+ARG MM_GID=1000
67+RUN groupadd -o -g "$MM_GID" mm \
68+ && useradd -o --create-home --uid "$MM_UID" --gid "$MM_GID" mm \
69+ && chmod 777 /home/mm
6170 ENV HOME=/home/mm
6271 USER mm
6372
@@ -98,7 +107,14 @@ func compose(name string, uid, gid int, mm *scaffold.MMFolder) string {
98107 # docker compose run --rm %[1]s %[5]s
99108 services:
100109 %[1]s:
101- build: .
110+ build:
111+ context: .
112+ # The image's mm account gets the same numbers as user: below, so that
113+ # programs in the container know who they run as. After changing
114+ # MM_UID or MM_GID, rebuild: docker compose up -d --build
115+ args:
116+ MM_UID: ${MM_UID:-%[3]d}
117+ MM_GID: ${MM_GID:-%[4]d}
102118 image: mm-%[1]s:latest
103119 container_name: %[1]s-box
104120 # Not root, and the files the agent creates belong to you. These numbers are
@@ -55,9 +55,18 @@ RUN set -eu; \
55 rm -f "$asset" SHA256SUMS; \55 rm -f "$asset" SHA256SUMS; \
56 mm --version56 mm --version
57 57
58-# The agent never runs as root. compose.yaml may choose another uid, which has58+# The agent never runs as root. Its account has the uid and gid of the user
59-# no account in the image: HOME is written to by whichever uid it is.59+# who runs compose (compose.yaml passes MM_UID and MM_GID), so that every
60-RUN useradd --create-home --uid 1000 mm && chmod 777 /home/mm60+# program finds it in /etc/passwd — whoami, git, ssh, bash's prompt — and the
61+# files it writes in the project belong to that user. -o: the gid, or the uid,
62+# may already exist in the image (20 is Debian's dialout). An image built
63+# without compose gets 1000. Here, not at the top: a new uid rebuilds this
64+# layer only, not mm's download. HOME stays writable for any uid.
65+ARG MM_UID=1000
66+ARG MM_GID=1000
67+RUN groupadd -o -g "$MM_GID" mm \
68+ && useradd -o --create-home --uid "$MM_UID" --gid "$MM_GID" mm \
69+ && chmod 777 /home/mm
61 ENV HOME=/home/mm70 ENV HOME=/home/mm
62 USER mm71 USER mm
63 72
@@ -98,7 +107,14 @@ func compose(name string, uid, gid int, mm *scaffold.MMFolder) string {
98 # docker compose run --rm %[1]s %[5]s107 # docker compose run --rm %[1]s %[5]s
99 services:108 services:
100 %[1]s:109 %[1]s:
101- build: .110+ build:
111+ context: .
112+ # The image's mm account gets the same numbers as user: below, so that
113+ # programs in the container know who they run as. After changing
114+ # MM_UID or MM_GID, rebuild: docker compose up -d --build
115+ args:
116+ MM_UID: ${MM_UID:-%[3]d}
117+ MM_GID: ${MM_GID:-%[4]d}
102 image: mm-%[1]s:latest118 image: mm-%[1]s:latest
103 container_name: %[1]s-box119 container_name: %[1]s-box
104 # Not root, and the files the agent creates belong to you. These numbers are120 # Not root, and the files the agent creates belong to you. These numbers are