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 @@ | ||
| 8 | 8 | |
| 9 | 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 | 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 | 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 | 21 | ## 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 | 25 | ```bash |
| 26 | 26 | 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 | 44 | | `agent.lsp.yaml` | the same agent with a language server behind an `lsp` tool | |
| 45 | 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 | 51 | ## Use |
| 50 | 52 | |
| @@ -53,7 +55,7 @@ The base command is `mm`. Run it **from the directory of the project you want to | ||
| 53 | 55 | ### 1. Terminal, line by line |
| 54 | 56 | |
| 55 | 57 | ```bash |
| 56 | -mm # reads ./agent.yaml | |
| 58 | +mm # reads ./agent.yaml, else ./.mm/agent.yaml | |
| 57 | 59 | 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 | 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 | 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 | 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 | 105 |  |
| 103 | 106 | |
| @@ -108,6 +111,7 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In | ||
| 108 | 111 | ## More features |
| 109 | 112 | |
| 110 | 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 | 115 | - **One prompt and exit**: `mm -p "…"` — for scripts. See [Run a single prompt](docs/en/how-to/run-a-single-prompt.md). |
| 112 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 149 | | `demos/dmr/` | mm on Docker Model Runner, with skills, LSP and MCP configurations | |
| 144 | 150 | | `demos/llmman/` | mm on llmman, and an agent that asks other agents | |
| 145 | 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 | 154 | | `demos/structured-output/` | agents that answer in JSON, and a coordinator using them | |
| 147 | 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 | 156 | | `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 | ## Install | 21 | ## 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 | ```bash | 25 | ```bash |
| 26 | git clone https://git.rickub.com/bots-garden/mini-me.git | 26 | 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 | ## Use | 51 | ## 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 line | 55 | ### 1. Terminal, line by line |
| 54 | 56 | ||
| 55 | ```bash | 57 | ```bash |
| 56 | -mm # reads ./agent.yaml | 58 | +mm # reads ./agent.yaml, else ./.mm/agent.yaml |
| 57 | mm agent.dmr.yaml # or name the file | 59 | 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 |  | 105 |  |
| 103 | 106 | ||
| @@ -108,6 +111,7 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In | |||
| 108 | ## More features | 111 | ## 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 | ||
| 93 | 93 | |
| 94 | 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 | 98 | ## 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 | 102 | ```bash |
| 101 | 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 | 113 | ## What the container protects |
| 105 | 114 | |
| 106 | 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). |
| @@ -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 machine | 98 | ## 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 | ```bash | 102 | ```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 protects | 113 | ## 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 | ||
| 217 | 217 | |
| 218 | 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 | 222 | ## `mm sbxinit` — the sandbox file |
| 223 | 223 | |
| @@ -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 file | 222 | ## `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 | ||
| 93 | 93 | |
| 94 | 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 | 98 | ## 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 | 102 | ```bash |
| 101 | 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 | 113 | ## Ce que le conteneur protège |
| 105 | 114 | |
| 106 | 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). |
| @@ -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 machine | 98 | ## 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 | ```bash | 102 | ```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ège | 113 | ## 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 | ||
| 217 | 217 | |
| 218 | 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 | 222 | ## `mm sbxinit` — le fichier de la sandbox |
| 223 | 223 | |
| @@ -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 sandbox | 222 | ## `mm sbxinit` — le fichier de la sandbox |
| 223 | 223 | ||
modified
internal/dockerinit/dockerinit_test.go +42 -5 | @@ -13,7 +13,10 @@ import ( | ||
| 13 | 13 | // composeFile is the part of compose.yaml the tests read back. |
| 14 | 14 | type composeFile struct { |
| 15 | 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 | 20 | Image string `yaml:"image"` |
| 18 | 21 | ContainerName string `yaml:"container_name"` |
| 19 | 22 | User string `yaml:"user"` |
| @@ -34,12 +37,17 @@ func TestComposeIsValidYAMLWithWhatTheAgentNeeds(t *testing.T) { | ||
| 34 | 37 | if !ok { |
| 35 | 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 | 41 | t.Errorf("names: %+v", service) |
| 39 | 42 | } |
| 40 | 43 | if service.User != "${MM_UID:-501}:${MM_GID:-20}" { |
| 41 | 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 | 51 | if want := ".:" + service.WorkingDir; len(service.Volumes) != 1 || service.Volumes[0] != want { |
| 44 | 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 | 67 | func TestComposeNeverDefaultsToRoot(t *testing.T) { |
| 59 | 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}"`) { | |
| 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 | + } | |
| 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 | 103 | func TestDockerfileRunsTheLatestReleaseAsNonRootOnBothArchitectures(t *testing.T) { |
| 67 | 104 | text := Dockerfile() |
| 68 | 105 | 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; \ | ||
| 55 | 55 | rm -f "$asset" SHA256SUMS; \ |
| 56 | 56 | mm --version |
| 57 | 57 | |
| 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 | |
| 61 | 70 | ENV HOME=/home/mm |
| 62 | 71 | USER mm |
| 63 | 72 | |
| @@ -98,7 +107,14 @@ func compose(name string, uid, gid int, mm *scaffold.MMFolder) string { | ||
| 98 | 107 | # docker compose run --rm %[1]s %[5]s |
| 99 | 108 | services: |
| 100 | 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 | 118 | image: mm-%[1]s:latest |
| 103 | 119 | container_name: %[1]s-box |
| 104 | 120 | # 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 --version | 56 | mm --version |
| 57 | 57 | ||
| 58 | -# The agent never runs as root. compose.yaml may choose another uid, which has | 58 | +# 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/mm | 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 | ||
| 61 | ENV HOME=/home/mm | 70 | ENV HOME=/home/mm |
| 62 | USER mm | 71 | 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]s | 107 | # 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:latest | 118 | image: mm-%[1]s:latest |
| 103 | container_name: %[1]s-box | 119 | container_name: %[1]s-box |
| 104 | # Not root, and the files the agent creates belong to you. These numbers are | 120 | # Not root, and the files the agent creates belong to you. These numbers are |