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

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

Download ZIP

🛟 Updated.Unverified

k33g committed 2026-10-08T06:13:47+02:00 Browse files
7b6177a parent: ad14030
modified docs/en/README.md +2 -1
@@ -14,6 +14,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
1414 - [Run mini-me on llmman](how-to/run-with-llmman.md) — `provider: llmman`, the demo config in `demo-llmman/`
1515 - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/`
1616 - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>`
17+- [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq
1718 - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo`
1819 - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`
1920 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)
@@ -61,7 +62,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
6162 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission
6263 - [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits
6364 - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive
64-- [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides
65+- [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides (and a per-agent `yolo` in `mmConfigs`)
6566 - [Asking another agent](explanation/asking-another-agent.md) — why another mm agent is a child process spoken to over ACP, the history pair, fresh sessions, `ask_agent` and per-agent `yolo`, the depth limit
6667 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
6768
@@ -14,6 +14,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
14 - [Run mini-me on llmman](how-to/run-with-llmman.md) — `provider: llmman`, the demo config in `demo-llmman/`14 - [Run mini-me on llmman](how-to/run-with-llmman.md) — `provider: llmman`, the demo config in `demo-llmman/`
15 - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/`15 - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/`
16 - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>`16 - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>`
17+- [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq
17 - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo`18 - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo`
18 - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`19 - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`
19 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)20 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)
@@ -61,7 +62,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
61 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission62 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission
62 - [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits63 - [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits
63 - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive64 - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive
64-- [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides65+- [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides (and a per-agent `yolo` in `mmConfigs`)
65 - [Asking another agent](explanation/asking-another-agent.md) — why another mm agent is a child process spoken to over ACP, the history pair, fresh sessions, `ask_agent` and per-agent `yolo`, the depth limit66 - [Asking another agent](explanation/asking-another-agent.md) — why another mm agent is a child process spoken to over ACP, the history pair, fresh sessions, `ask_agent` and per-agent `yolo`, the depth limit
66 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)67 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
67 68
modified docs/en/explanation/architecture.md +3 -3
@@ -9,13 +9,13 @@ The entry point, `main.go`, only does wiring: it loads the settings, opens the e
99 | Package | Responsibility |
1010 |---------|----------------|
1111 | `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. |
12-| `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, and the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)). |
13-| `tools` | The built-in tools the model can call. |
12+| `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)), and the optional trace of every request and response (`trace.go`). |
13+| `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). |
1414 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |
1515 | `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. |
1616 | `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. |
1717 | `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. |
18-| `ask` | `/ask-<name>`: starts another mm agent (config `mmConfigs`) as a child `mm -acp`, talks to it as an ACP client, streams its answer and tool calls through the asking front end's `ui.Sink`, and returns the exchange for the history. See [Asking another agent](asking-another-agent.md). |
18+| `ask` | `/ask-<name>`: starts another mm agent (config `mmConfigs`) as a child `mm -acp`, talks to it as an ACP client, streams its answer and tool calls through the asking front end's `ui.Sink`, and returns the exchange for the history; the same pool serves the `ask_agent` tool and `mm --ask`. See [Asking another agent](asking-another-agent.md). |
1919 | `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. |
2020 | `store` | Per-project session files (`<dir>/<id>.json`, whole rewrite, atomic): create, save, load, list, latest. Used by both front ends when `sessions.enabled`. |
2121 | `mention` | The `@path` notation of a question: existing paths become `[attached file: …]` lines, the same shape as an editor's `resource_link`. Used by both front ends. |
@@ -9,13 +9,13 @@ The entry point, `main.go`, only does wiring: it loads the settings, opens the e
9 | Package | Responsibility |9 | Package | Responsibility |
10 |---------|----------------|10 |---------|----------------|
11 | `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. |11 | `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. |
12-| `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, and the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)). |12+| `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)), and the optional trace of every request and response (`trace.go`). |
13-| `tools` | The built-in tools the model can call. |13+| `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). |
14 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |14 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |
15 | `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. |15 | `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. |
16 | `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. |16 | `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. |
17 | `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. |17 | `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. |
18-| `ask` | `/ask-<name>`: starts another mm agent (config `mmConfigs`) as a child `mm -acp`, talks to it as an ACP client, streams its answer and tool calls through the asking front end's `ui.Sink`, and returns the exchange for the history. See [Asking another agent](asking-another-agent.md). |18+| `ask` | `/ask-<name>`: starts another mm agent (config `mmConfigs`) as a child `mm -acp`, talks to it as an ACP client, streams its answer and tool calls through the asking front end's `ui.Sink`, and returns the exchange for the history; the same pool serves the `ask_agent` tool and `mm --ask`. See [Asking another agent](asking-another-agent.md). |
19 | `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. |19 | `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. |
20 | `store` | Per-project session files (`<dir>/<id>.json`, whole rewrite, atomic): create, save, load, list, latest. Used by both front ends when `sessions.enabled`. |20 | `store` | Per-project session files (`<dir>/<id>.json`, whole rewrite, atomic): create, save, load, list, latest. Used by both front ends when `sessions.enabled`. |
21 | `mention` | The `@path` notation of a question: existing paths become `[attached file: …]` lines, the same shape as an editor's `resource_link`. Used by both front ends. |21 | `mention` | The `@path` notation of a question: existing paths become `[attached file: …]` lines, the same shape as an editor's `resource_link`. Used by both front ends. |
modified docs/en/explanation/yolo-and-permissions.md +2 -0
@@ -33,6 +33,8 @@ With `mmConfigs`, one agent can ask another (`/ask-jan`, `mm --ask jan`). Two co
3333
3434 Technically, the asked agent is started with `AGENT_YOLO=false`, so it submits every call it would gate through the ACP permission request — the same channel an editor uses. The asking agent receives it and runs it through its own `consent.Gate`: in yolo mode it allows at once; otherwise it asks its own user, in its own front end, and remembers the answer under `jan/bash` so that allowing Jan's shell does not allow yours. The asked agent itself never remembers anything: it is asked again each time, and the asking agent's memory answers.
3535
36+One refinement keeps the same principle: the user's file may give an asked agent its own `yolo`, in that agent's `mmConfigs` entry (`homer: {path: …, yolo: true}`). It then decides that agent's calls instead of the asking agent's `yolo`: `true` runs them — a coder trusted with edits once the hand-off was approved —, `false` asks for each one even when the asking agent runs freely. It is still the file the user started that decides; the asked agent's own file still does not count.
37+
3638 ## Modes that cannot ask
3739
3840 `mm -p` and `mm --ask` have nobody to ask: the first is a one-shot run, the second reads its question from stdin. With `yolo: false` they reject the calls that would ask and say so once on stderr. Allowing them silently would turn `yolo: false` into a setting that depends on how you launched mm, which is exactly the inconsistency this design removes; hanging on an invisible question would block scripts forever.
@@ -33,6 +33,8 @@ With `mmConfigs`, one agent can ask another (`/ask-jan`, `mm --ask jan`). Two co
33 33
34 Technically, the asked agent is started with `AGENT_YOLO=false`, so it submits every call it would gate through the ACP permission request — the same channel an editor uses. The asking agent receives it and runs it through its own `consent.Gate`: in yolo mode it allows at once; otherwise it asks its own user, in its own front end, and remembers the answer under `jan/bash` so that allowing Jan's shell does not allow yours. The asked agent itself never remembers anything: it is asked again each time, and the asking agent's memory answers.34 Technically, the asked agent is started with `AGENT_YOLO=false`, so it submits every call it would gate through the ACP permission request — the same channel an editor uses. The asking agent receives it and runs it through its own `consent.Gate`: in yolo mode it allows at once; otherwise it asks its own user, in its own front end, and remembers the answer under `jan/bash` so that allowing Jan's shell does not allow yours. The asked agent itself never remembers anything: it is asked again each time, and the asking agent's memory answers.
35 35
36+One refinement keeps the same principle: the user's file may give an asked agent its own `yolo`, in that agent's `mmConfigs` entry (`homer: {path: …, yolo: true}`). It then decides that agent's calls instead of the asking agent's `yolo`: `true` runs them — a coder trusted with edits once the hand-off was approved —, `false` asks for each one even when the asking agent runs freely. It is still the file the user started that decides; the asked agent's own file still does not count.
37+
36 ## Modes that cannot ask38 ## Modes that cannot ask
37 39
38 `mm -p` and `mm --ask` have nobody to ask: the first is a one-shot run, the second reads its question from stdin. With `yolo: false` they reject the calls that would ask and say so once on stderr. Allowing them silently would turn `yolo: false` into a setting that depends on how you launched mm, which is exactly the inconsistency this design removes; hanging on an invisible question would block scripts forever.40 `mm -p` and `mm --ask` have nobody to ask: the first is a one-shot run, the second reads its question from stdin. With `yolo: false` they reject the calls that would ask and say so once on stderr. Allowing them silently would turn `yolo: false` into a setting that depends on how you launched mm, which is exactly the inconsistency this design removes; hanging on an invisible question would block scripts forever.
modified docs/en/how-to/ask-another-agent.md +1 -1
@@ -50,7 +50,7 @@ This guide shows how to put a question to another mm agent — another model, an
5050
5151 - **Let the other agent see the conversation.** Use `/ask-with-context-lucy <prompt>`: Lucy receives a transcript of the conversation (questions, answers, one line per tool call — never tool outputs) before your question.
5252 - **In the terminal or the window, by name.** `/ask lucy <prompt>` and `/ask-with-context lucy <prompt>` are the same commands with the agent as an argument.
53-- **In an editor (ACP).** The menu lists `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy`; when Lucy wants to run a command, your agent's `yolo` decides: with `yolo: true` it runs, with `yolo: false` the editor's own permission dialog asks you — whatever Lucy's own file says. See [How to confirm tool calls](confirm-tool-calls.md).
53+- **In an editor (ACP).** The menu lists `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy`; when Lucy wants to run a command, your agent's `yolo` decides: with `yolo: true` it runs, with `yolo: false` the editor's own permission dialog asks you — whatever Lucy's own file says. A `yolo` in Lucy's entry of YOUR `mmConfigs` (`lucy: {path: agent.lucy.yaml, yolo: true}`) decides for her instead. See [How to confirm tool calls](confirm-tool-calls.md).
5454 - **Stop a long answer.** `/abort`, `Esc` or `Ctrl+C` — nothing is kept from a cancelled ask.
5555 - **Agents on other providers.** Any mm configuration works: an Ollama agent next to an llmman one is just another `mmConfigs` entry.
5656
@@ -50,7 +50,7 @@ This guide shows how to put a question to another mm agent — another model, an
50 50
51 - **Let the other agent see the conversation.** Use `/ask-with-context-lucy <prompt>`: Lucy receives a transcript of the conversation (questions, answers, one line per tool call — never tool outputs) before your question.51 - **Let the other agent see the conversation.** Use `/ask-with-context-lucy <prompt>`: Lucy receives a transcript of the conversation (questions, answers, one line per tool call — never tool outputs) before your question.
52 - **In the terminal or the window, by name.** `/ask lucy <prompt>` and `/ask-with-context lucy <prompt>` are the same commands with the agent as an argument.52 - **In the terminal or the window, by name.** `/ask lucy <prompt>` and `/ask-with-context lucy <prompt>` are the same commands with the agent as an argument.
53-- **In an editor (ACP).** The menu lists `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy`; when Lucy wants to run a command, your agent's `yolo` decides: with `yolo: true` it runs, with `yolo: false` the editor's own permission dialog asks you — whatever Lucy's own file says. See [How to confirm tool calls](confirm-tool-calls.md).53+- **In an editor (ACP).** The menu lists `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy`; when Lucy wants to run a command, your agent's `yolo` decides: with `yolo: true` it runs, with `yolo: false` the editor's own permission dialog asks you — whatever Lucy's own file says. A `yolo` in Lucy's entry of YOUR `mmConfigs` (`lucy: {path: agent.lucy.yaml, yolo: true}`) decides for her instead. See [How to confirm tool calls](confirm-tool-calls.md).
54 - **Stop a long answer.** `/abort`, `Esc` or `Ctrl+C` — nothing is kept from a cancelled ask.54 - **Stop a long answer.** `/abort`, `Esc` or `Ctrl+C` — nothing is kept from a cancelled ask.
55 - **Agents on other providers.** Any mm configuration works: an Ollama agent next to an llmman one is just another `mmConfigs` entry.55 - **Agents on other providers.** Any mm configuration works: an Ollama agent next to an llmman one is just another `mmConfigs` entry.
56 56
added docs/en/how-to/inspect-model-traffic.md +62 -0
new file mode 100644
@@ -0,0 +1,62 @@
1+# How to see the JSON sent to the model and its answer
2+
3+This guide shows how to keep, for every call to the model, the exact request mm sent and the exact response the server returned — to check a system prompt, the tool definitions, a sampling key, what another agent received, or what a model really answered. It assumes you already run `mm` with a configuration file.
4+
5+## Steps
6+
7+1. Turn the trace on, for one run:
8+
9+ ```bash
10+ AGENT_TRACE=true mm agent.yaml
11+ ```
12+
13+ or for good, in the configuration:
14+
15+ ```yaml
16+ trace:
17+ enabled: true
18+ dir: .mm/traces # the default
19+ ```
20+
21+ The banner ends with `| trace: /abs/path/.mm/traces` while it is on.
22+
23+2. Ask a question as usual. Each call to the model leaves a pair of files:
24+
25+ ```text
26+ .mm/traces/20261007-123237.903-bob-0001.request.json
27+ .mm/traces/20261007-123237.903-bob-0001.response-200.sse
28+ ```
29+
30+ One question can make several calls: one per round trip with the tools, plus a compaction summary. The agent's `acp.name` is in each name, so an agent called through `ask_agent` or `/ask` — it inherits `AGENT_TRACE` — writes its own files beside yours (`…-jan-0001…`).
31+
32+3. Read the request. It is the body exactly as sent, indented:
33+
34+ ```bash
35+ jq '{model, temperature, stream_options, chat_template_kwargs}' .mm/traces/*-0001.request.json
36+ jq -r '.messages[] | "\(.role): \(.content | tostring | .[0:120])"' .mm/traces/*-0001.request.json
37+ jq -r '.tools[].function.name' .mm/traces/*-0001.request.json
38+ ```
39+
40+4. Read the response. A streamed one (`.sse`) is the server's lines as they came, one `data: {…}` chunk each; rebuild the answer text with:
41+
42+ ```bash
43+ grep '^data: {' .mm/traces/*-0001.response-200.sse | sed 's/^data: //' \
44+ | jq -rj '.choices[0].delta.content // empty'; echo
45+ ```
46+
47+ and its reasoning with `.choices[0].delta.reasoning_content`, its tool calls with `.choices[0].delta.tool_calls`, its token counts with `.usage` (the last chunk). A response that was not streamed is a `.json` file, indented. An error status is in the name (`.response-400.json`); a call that got no response at all leaves `.response-error.txt`.
48+
49+5. Turn it off when you are done: the files hold your prompts, file contents and command outputs in full. `.mm/` is git-ignored in this repository; add it to your project's `.gitignore` too.
50+
51+## Variants
52+
53+- **In an editor (ACP).** A relative `trace.dir` is in the session's `cwd` — the project the editor opened — not in the directory the editor launched `mm` from. Set `AGENT_TRACE` in the editor's agent settings (`env`), or `trace.enabled` in the file: the editor does not see your shell's variables.
54+- **Somewhere else.** `trace.dir: /tmp/mm-traces` collects every project's calls in one place.
55+- **Only the agents you ask.** Put `trace.enabled: true` in that agent's own file instead of using `AGENT_TRACE`.
56+- **Before the files: what mm adds.** The request already carries what mm adds itself — `stream_options.include_usage`, `return_progress` for a llama.cpp server, `chat_template_kwargs.enable_thinking: false` on a summary — so the trace is also how to check those.
57+
58+## See also
59+
60+- The `trace` keys and the file names: [configuration reference](../reference/configuration.md)
61+- `AGENT_TRACE`: [command-line reference](../reference/cli.md)
62+- Why reasoning arrives in its own field: [Thinking models](../explanation/thinking-models.md)
new file mode 100644
@@ -0,0 +1,62 @@
1+# How to see the JSON sent to the model and its answer
2+
3+This guide shows how to keep, for every call to the model, the exact request mm sent and the exact response the server returned — to check a system prompt, the tool definitions, a sampling key, what another agent received, or what a model really answered. It assumes you already run `mm` with a configuration file.
4+
5+## Steps
6+
7+1. Turn the trace on, for one run:
8+
9+ ```bash
10+ AGENT_TRACE=true mm agent.yaml
11+ ```
12+
13+ or for good, in the configuration:
14+
15+ ```yaml
16+ trace:
17+ enabled: true
18+ dir: .mm/traces # the default
19+ ```
20+
21+ The banner ends with `| trace: /abs/path/.mm/traces` while it is on.
22+
23+2. Ask a question as usual. Each call to the model leaves a pair of files:
24+
25+ ```text
26+ .mm/traces/20261007-123237.903-bob-0001.request.json
27+ .mm/traces/20261007-123237.903-bob-0001.response-200.sse
28+ ```
29+
30+ One question can make several calls: one per round trip with the tools, plus a compaction summary. The agent's `acp.name` is in each name, so an agent called through `ask_agent` or `/ask` — it inherits `AGENT_TRACE` — writes its own files beside yours (`…-jan-0001…`).
31+
32+3. Read the request. It is the body exactly as sent, indented:
33+
34+ ```bash
35+ jq '{model, temperature, stream_options, chat_template_kwargs}' .mm/traces/*-0001.request.json
36+ jq -r '.messages[] | "\(.role): \(.content | tostring | .[0:120])"' .mm/traces/*-0001.request.json
37+ jq -r '.tools[].function.name' .mm/traces/*-0001.request.json
38+ ```
39+
40+4. Read the response. A streamed one (`.sse`) is the server's lines as they came, one `data: {…}` chunk each; rebuild the answer text with:
41+
42+ ```bash
43+ grep '^data: {' .mm/traces/*-0001.response-200.sse | sed 's/^data: //' \
44+ | jq -rj '.choices[0].delta.content // empty'; echo
45+ ```
46+
47+ and its reasoning with `.choices[0].delta.reasoning_content`, its tool calls with `.choices[0].delta.tool_calls`, its token counts with `.usage` (the last chunk). A response that was not streamed is a `.json` file, indented. An error status is in the name (`.response-400.json`); a call that got no response at all leaves `.response-error.txt`.
48+
49+5. Turn it off when you are done: the files hold your prompts, file contents and command outputs in full. `.mm/` is git-ignored in this repository; add it to your project's `.gitignore` too.
50+
51+## Variants
52+
53+- **In an editor (ACP).** A relative `trace.dir` is in the session's `cwd` — the project the editor opened — not in the directory the editor launched `mm` from. Set `AGENT_TRACE` in the editor's agent settings (`env`), or `trace.enabled` in the file: the editor does not see your shell's variables.
54+- **Somewhere else.** `trace.dir: /tmp/mm-traces` collects every project's calls in one place.
55+- **Only the agents you ask.** Put `trace.enabled: true` in that agent's own file instead of using `AGENT_TRACE`.
56+- **Before the files: what mm adds.** The request already carries what mm adds itself — `stream_options.include_usage`, `return_progress` for a llama.cpp server, `chat_template_kwargs.enable_thinking: false` on a summary — so the trace is also how to check those.
57+
58+## See also
59+
60+- The `trace` keys and the file names: [configuration reference](../reference/configuration.md)
61+- `AGENT_TRACE`: [command-line reference](../reference/cli.md)
62+- Why reasoning arrives in its own field: [Thinking models](../explanation/thinking-models.md)
modified docs/en/how-to/use-from-an-editor.md +1 -0
@@ -32,6 +32,7 @@ This guide shows how to host `mm` inside an editor that speaks the Agent Client
3232 - **Compress or restart the conversation.** Send `/compact` or `/new`; editors that list the agent's commands offer both in the slash-command menu. See the [slash commands reference](../reference/slash-commands.md#acp-mode).
3333 - **Attach a file with `@`.** The editor's picker sends a `resource_link`, passed to the model as `[attached file: <path>]`; a path typed by hand as `@path` is understood the same way (see [`@path`](attach-a-file.md)). The model reads the file with its tools.
3434 - **Reload the agent without losing the thread.** With `sessions.enabled: true` in the config file, the editor's Reload Agent (Zed) calls `session/load` and the whole conversation, tool calls included, is replayed into the thread. See [Keep sessions and resume them](keep-sessions.md).
35+- **See the JSON exchanged with the model.** Turn the trace on, in the config file (`trace: {enabled: true}`) or in the editor's settings (`"env": { "AGENT_CONFIG": "…", "AGENT_TRACE": "true" }` — the editor does not pass your shell's variables). The files land in `<project>/.mm/traces/`, the project being the session's `cwd`; the banner in the ACP logs says `trace: <session cwd>/.mm/traces`. See [How to see the JSON sent to the model](inspect-model-traffic.md).
3536 - **Change the name the editor sees.** Add an `acp:` block to the config file; `name` is the identifier the editor logs, `title` the name it may display (leave `title` empty to not send one). The version sent is always the binary's, the one `mm --version` prints — check it with the `initialize` request below. See the [`acp` keys](../reference/configuration.md#acp-keys).
3637
3738 ```yaml
@@ -32,6 +32,7 @@ This guide shows how to host `mm` inside an editor that speaks the Agent Client
32 - **Compress or restart the conversation.** Send `/compact` or `/new`; editors that list the agent's commands offer both in the slash-command menu. See the [slash commands reference](../reference/slash-commands.md#acp-mode).32 - **Compress or restart the conversation.** Send `/compact` or `/new`; editors that list the agent's commands offer both in the slash-command menu. See the [slash commands reference](../reference/slash-commands.md#acp-mode).
33 - **Attach a file with `@`.** The editor's picker sends a `resource_link`, passed to the model as `[attached file: <path>]`; a path typed by hand as `@path` is understood the same way (see [`@path`](attach-a-file.md)). The model reads the file with its tools.33 - **Attach a file with `@`.** The editor's picker sends a `resource_link`, passed to the model as `[attached file: <path>]`; a path typed by hand as `@path` is understood the same way (see [`@path`](attach-a-file.md)). The model reads the file with its tools.
34 - **Reload the agent without losing the thread.** With `sessions.enabled: true` in the config file, the editor's Reload Agent (Zed) calls `session/load` and the whole conversation, tool calls included, is replayed into the thread. See [Keep sessions and resume them](keep-sessions.md).34 - **Reload the agent without losing the thread.** With `sessions.enabled: true` in the config file, the editor's Reload Agent (Zed) calls `session/load` and the whole conversation, tool calls included, is replayed into the thread. See [Keep sessions and resume them](keep-sessions.md).
35+- **See the JSON exchanged with the model.** Turn the trace on, in the config file (`trace: {enabled: true}`) or in the editor's settings (`"env": { "AGENT_CONFIG": "…", "AGENT_TRACE": "true" }` — the editor does not pass your shell's variables). The files land in `<project>/.mm/traces/`, the project being the session's `cwd`; the banner in the ACP logs says `trace: <session cwd>/.mm/traces`. See [How to see the JSON sent to the model](inspect-model-traffic.md).
35 - **Change the name the editor sees.** Add an `acp:` block to the config file; `name` is the identifier the editor logs, `title` the name it may display (leave `title` empty to not send one). The version sent is always the binary's, the one `mm --version` prints — check it with the `initialize` request below. See the [`acp` keys](../reference/configuration.md#acp-keys).36 - **Change the name the editor sees.** Add an `acp:` block to the config file; `name` is the identifier the editor logs, `title` the name it may display (leave `title` empty to not send one). The version sent is always the binary's, the one `mm --version` prints — check it with the `initialize` request below. See the [`acp` keys](../reference/configuration.md#acp-keys).
36 37
37 ```yaml38 ```yaml
modified docs/en/how-to/use-the-window-mode.md +1 -0
@@ -28,6 +28,7 @@ This guide shows how to run mini-me as a single full-screen window — the conve
2828
2929 - **Ask before every `bash` and file tool.** Set `yolo: false` in the YAML. The box is replaced by the question `? bash: rm -rf build` and four keys answer it: `a` allow once, `s` allow this tool for the session, `r` (or Esc) reject, `n` never allow this tool in this session. A rejected tool tells the model `[command rejected by the user]`. The default, `yolo: true`, runs tools without asking. See [How to confirm tool calls](confirm-tool-calls.md).
3030 - **Point the model at a file.** `@path` works exactly as in the line mode: existing paths are attached and echoed as `📎` lines. See [Point the model at a file with `@path`](attach-a-file.md).
31+- **See which agent answers.** The header and the label above each answer name the agent: `mm`, or `mm [<acp.title>]` when the config gives a title. While another agent works — `/ask-<name>`, or the `ask_agent` tool — the header names it with its model and `asked by` your agent, and its answer is labelled with its title (or its `mmConfigs` name); then both come back to your agent. See [How to let your agent call other agents](let-the-agent-call-other-agents.md).
3132 - **Keep and resume sessions.** With `sessions.enabled: true`, the header shows the session id being saved; `/sessions` and `/resume` work as in the line mode. See [Keep sessions and resume them](keep-sessions.md).
3233 - **Scroll the conversation.** PgUp/PgDn page; **Tab** moves the focus to the conversation so ↑/↓, Home and End scroll it; End resumes following the end. **Ctrl-L** clears the displayed conversation without touching the history. The mouse wheel scrolls only after **F2** (see below).
3334 - **Not a terminal.** `mm -tui | tee log.txt`, or any run where stdin or stdout is not a terminal, prints `[tui: stdin or stdout is not a terminal — running the line mode]` and runs the line mode. Nothing else changes.
@@ -28,6 +28,7 @@ This guide shows how to run mini-me as a single full-screen window — the conve
28 28
29 - **Ask before every `bash` and file tool.** Set `yolo: false` in the YAML. The box is replaced by the question `? bash: rm -rf build` and four keys answer it: `a` allow once, `s` allow this tool for the session, `r` (or Esc) reject, `n` never allow this tool in this session. A rejected tool tells the model `[command rejected by the user]`. The default, `yolo: true`, runs tools without asking. See [How to confirm tool calls](confirm-tool-calls.md).29 - **Ask before every `bash` and file tool.** Set `yolo: false` in the YAML. The box is replaced by the question `? bash: rm -rf build` and four keys answer it: `a` allow once, `s` allow this tool for the session, `r` (or Esc) reject, `n` never allow this tool in this session. A rejected tool tells the model `[command rejected by the user]`. The default, `yolo: true`, runs tools without asking. See [How to confirm tool calls](confirm-tool-calls.md).
30 - **Point the model at a file.** `@path` works exactly as in the line mode: existing paths are attached and echoed as `📎` lines. See [Point the model at a file with `@path`](attach-a-file.md).30 - **Point the model at a file.** `@path` works exactly as in the line mode: existing paths are attached and echoed as `📎` lines. See [Point the model at a file with `@path`](attach-a-file.md).
31+- **See which agent answers.** The header and the label above each answer name the agent: `mm`, or `mm [<acp.title>]` when the config gives a title. While another agent works — `/ask-<name>`, or the `ask_agent` tool — the header names it with its model and `asked by` your agent, and its answer is labelled with its title (or its `mmConfigs` name); then both come back to your agent. See [How to let your agent call other agents](let-the-agent-call-other-agents.md).
31 - **Keep and resume sessions.** With `sessions.enabled: true`, the header shows the session id being saved; `/sessions` and `/resume` work as in the line mode. See [Keep sessions and resume them](keep-sessions.md).32 - **Keep and resume sessions.** With `sessions.enabled: true`, the header shows the session id being saved; `/sessions` and `/resume` work as in the line mode. See [Keep sessions and resume them](keep-sessions.md).
32 - **Scroll the conversation.** PgUp/PgDn page; **Tab** moves the focus to the conversation so ↑/↓, Home and End scroll it; End resumes following the end. **Ctrl-L** clears the displayed conversation without touching the history. The mouse wheel scrolls only after **F2** (see below).33 - **Scroll the conversation.** PgUp/PgDn page; **Tab** moves the focus to the conversation so ↑/↓, Home and End scroll it; End resumes following the end. **Ctrl-L** clears the displayed conversation without touching the history. The mouse wheel scrolls only after **F2** (see below).
33 - **Not a terminal.** `mm -tui | tee log.txt`, or any run where stdin or stdout is not a terminal, prints `[tui: stdin or stdout is not a terminal — running the line mode]` and runs the line mode. Nothing else changes.34 - **Not a terminal.** `mm -tui | tee log.txt`, or any run where stdin or stdout is not a terminal, prints `[tui: stdin or stdout is not a terminal — running the line mode]` and runs the line mode. Nothing else changes.
modified docs/en/reference/cli.md +4 -3
@@ -60,7 +60,8 @@ The first existing source wins.
6060 | `AGENT_BASE_URL` | Overrides `baseUrl` and disables the fallback probe. |
6161 | `DMR_BASE_URL` | Same as `AGENT_BASE_URL`, for the `dmr` provider only; `AGENT_BASE_URL` wins when both are set. |
6262 | `AGENT_SESSIONS` | `off` turns session persistence off for this run, whatever `sessions.enabled` says. Set by `/ask` on the agents it starts. |
63-| `AGENT_YOLO` | Overrides the `yolo` key: `true` or `false` (any `strconv.ParseBool` spelling; anything else is a configuration error). Set to `false` by `/ask` on the agents it starts, so the asking agent decides. |
63+| `AGENT_TRACE` | Overrides `trace.enabled`: `true` or `false` (any `strconv.ParseBool` spelling; anything else is a configuration error). Inherited by the agents `/ask` and `ask_agent` start. |
64+| `AGENT_YOLO` | Overrides the `yolo` key: `true` or `false` (any `strconv.ParseBool` spelling; anything else is a configuration error). Set to `false` by `/ask` and `ask_agent` on the agents they start, so the asking side decides (the agent's `mmConfigs` entry `yolo`, else the asking agent's). |
6465 | `MM_BIN` | Set by `mm` for the commands its `bash` tool runs: the path of the running `mm` binary. |
6566 | `MM_CONFIG` | Set by `mm` for the commands its `bash` tool runs: the absolute path of its configuration file. Read by `--ask` / `--ask-with-context` when no `-config` and no positional path are given. |
6667 | `MM_ASK_DEPTH` | How many `/ask` levels deep this agent runs (unset = 0). Set by `/ask`; at 2, `/ask` is refused. |
@@ -73,7 +74,7 @@ Printed on stdout in terminal mode, on stderr in ACP mode. Dimmed when stdout is
7374 | Line | Content |
7475 |------|---------|
7576 | `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all, an `mmConfigs` file not found (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), the deprecated `permissions` key. |
76-| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`. |
77+| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`., and, while `trace.enabled` is on, `trace: <absolute dir>` (`trace: <session cwd>/<dir>` in ACP mode). |
7778 | `Agent ready. Commands: …` | Terminal mode only. |
7879 | `Shortcut: Ctrl+C …` | Terminal mode only. |
7980 | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Terminal mode, `sessions.enabled` only: the resolved sessions directory and how many sessions it already holds. |
@@ -141,7 +142,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
141142 | Context (`--ask-with-context`) | stdin. Without `-p`, stdin is `<context>`, a line `---`, then the question. |
142143 | stdout | The agent's answer, and nothing else, followed by a newline. |
143144 | stderr | `🗣 asking <agent> (<provider>/<model>)…`, one `🛠️ <agent> · <tool>: <args>` line per tool call, `[ask: …]` on error. |
144-| The agent's tool calls | Decided by this configuration's `yolo`: `true` = allowed; `false` = rejected, with the same stderr line as in prompt mode (stdin carried the question, nobody can answer). |
145+| The agent's tool calls | Decided by the agent's `mmConfigs` entry `yolo` when set, else by this configuration's `yolo`: `true` = allowed; `false` = rejected, with the same stderr line as in prompt mode (stdin carried the question, nobody can answer). |
145146 | Parent process | Watched every 500 ms: when it is gone, the ask is cancelled and the agent stopped. |
146147 | Session | None: neither this process nor the agent writes session files. |
147148
@@ -60,7 +60,8 @@ The first existing source wins.
60 | `AGENT_BASE_URL` | Overrides `baseUrl` and disables the fallback probe. |60 | `AGENT_BASE_URL` | Overrides `baseUrl` and disables the fallback probe. |
61 | `DMR_BASE_URL` | Same as `AGENT_BASE_URL`, for the `dmr` provider only; `AGENT_BASE_URL` wins when both are set. |61 | `DMR_BASE_URL` | Same as `AGENT_BASE_URL`, for the `dmr` provider only; `AGENT_BASE_URL` wins when both are set. |
62 | `AGENT_SESSIONS` | `off` turns session persistence off for this run, whatever `sessions.enabled` says. Set by `/ask` on the agents it starts. |62 | `AGENT_SESSIONS` | `off` turns session persistence off for this run, whatever `sessions.enabled` says. Set by `/ask` on the agents it starts. |
63-| `AGENT_YOLO` | Overrides the `yolo` key: `true` or `false` (any `strconv.ParseBool` spelling; anything else is a configuration error). Set to `false` by `/ask` on the agents it starts, so the asking agent decides. |63+| `AGENT_TRACE` | Overrides `trace.enabled`: `true` or `false` (any `strconv.ParseBool` spelling; anything else is a configuration error). Inherited by the agents `/ask` and `ask_agent` start. |
64+| `AGENT_YOLO` | Overrides the `yolo` key: `true` or `false` (any `strconv.ParseBool` spelling; anything else is a configuration error). Set to `false` by `/ask` and `ask_agent` on the agents they start, so the asking side decides (the agent's `mmConfigs` entry `yolo`, else the asking agent's). |
64 | `MM_BIN` | Set by `mm` for the commands its `bash` tool runs: the path of the running `mm` binary. |65 | `MM_BIN` | Set by `mm` for the commands its `bash` tool runs: the path of the running `mm` binary. |
65 | `MM_CONFIG` | Set by `mm` for the commands its `bash` tool runs: the absolute path of its configuration file. Read by `--ask` / `--ask-with-context` when no `-config` and no positional path are given. |66 | `MM_CONFIG` | Set by `mm` for the commands its `bash` tool runs: the absolute path of its configuration file. Read by `--ask` / `--ask-with-context` when no `-config` and no positional path are given. |
66 | `MM_ASK_DEPTH` | How many `/ask` levels deep this agent runs (unset = 0). Set by `/ask`; at 2, `/ask` is refused. |67 | `MM_ASK_DEPTH` | How many `/ask` levels deep this agent runs (unset = 0). Set by `/ask`; at 2, `/ask` is refused. |
@@ -73,7 +74,7 @@ Printed on stdout in terminal mode, on stderr in ACP mode. Dimmed when stdout is
73 | Line | Content |74 | Line | Content |
74 |------|---------|75 |------|---------|
75 | `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all, an `mmConfigs` file not found (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), the deprecated `permissions` key. |76 | `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all, an `mmConfigs` file not found (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), the deprecated `permissions` key. |
76-| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`. |77+| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`., and, while `trace.enabled` is on, `trace: <absolute dir>` (`trace: <session cwd>/<dir>` in ACP mode). |
77 | `Agent ready. Commands: …` | Terminal mode only. |78 | `Agent ready. Commands: …` | Terminal mode only. |
78 | `Shortcut: Ctrl+C …` | Terminal mode only. |79 | `Shortcut: Ctrl+C …` | Terminal mode only. |
79 | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Terminal mode, `sessions.enabled` only: the resolved sessions directory and how many sessions it already holds. |80 | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Terminal mode, `sessions.enabled` only: the resolved sessions directory and how many sessions it already holds. |
@@ -141,7 +142,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
141 | Context (`--ask-with-context`) | stdin. Without `-p`, stdin is `<context>`, a line `---`, then the question. |142 | Context (`--ask-with-context`) | stdin. Without `-p`, stdin is `<context>`, a line `---`, then the question. |
142 | stdout | The agent's answer, and nothing else, followed by a newline. |143 | stdout | The agent's answer, and nothing else, followed by a newline. |
143 | stderr | `🗣 asking <agent> (<provider>/<model>)…`, one `🛠️ <agent> · <tool>: <args>` line per tool call, `[ask: …]` on error. |144 | stderr | `🗣 asking <agent> (<provider>/<model>)…`, one `🛠️ <agent> · <tool>: <args>` line per tool call, `[ask: …]` on error. |
144-| The agent's tool calls | Decided by this configuration's `yolo`: `true` = allowed; `false` = rejected, with the same stderr line as in prompt mode (stdin carried the question, nobody can answer). |145+| The agent's tool calls | Decided by the agent's `mmConfigs` entry `yolo` when set, else by this configuration's `yolo`: `true` = allowed; `false` = rejected, with the same stderr line as in prompt mode (stdin carried the question, nobody can answer). |
145 | Parent process | Watched every 500 ms: when it is gone, the ask is cancelled and the agent stopped. |146 | Parent process | Watched every 500 ms: when it is gone, the ask is cancelled and the agent stopped. |
146 | Session | None: neither this process nor the agent writes session files. |147 | Session | None: neither this process nor the agent writes session files. |
147 148
modified docs/en/reference/configuration.md +12 -1
@@ -28,10 +28,11 @@
2828 | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |
2929 | `context` | map | see below | Automatic history compression. |
3030 | `sessions` | map | see below | Persistent, per-project sessions. |
31+| `trace` | map | see below | Write every request to the model and every response to files. |
3132 | `acp` | map | see below | How `mm -acp` introduces itself to the editor. |
3233 | `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. |
3334 | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. |
34-| `yolo` | bool | `true` | `true`: the tools run without asking. `false`: every call that changes something — `bash`, `write_file`, `edit_file`, `fetch_content` with `output`, `lsp` `rename`, `plan` `create`/`update`/`next`/`delete` — waits for the user's answer in every front end (line mode, window, editor), and so does every MCP tool, whatever `mcp.permissions` says. Answers: allow once, allow this tool for the session, reject this call, never (this tool, for the session). `read_file`, `read_skill`, `fetch_content` without `output` and the `lsp` navigation actions never ask. In `-p` and `mm --ask`, nobody can answer: those calls are rejected. An agent started by `/ask` runs with `AGENT_YOLO=false` and submits its calls to the asking agent, whose `yolo` decides. Overridden by `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` still decide which tools exist. See [How to confirm tool calls](../how-to/confirm-tool-calls.md). |
35+| `yolo` | bool | `true` | `true`: the tools run without asking. `false`: every call that changes something — `bash`, `write_file`, `edit_file`, `fetch_content` with `output`, `lsp` `rename`, `plan` `create`/`update`/`next`/`delete` — waits for the user's answer in every front end (line mode, window, editor), and so does every MCP tool, whatever `mcp.permissions` says. Answers: allow once, allow this tool for the session, reject this call, never (this tool, for the session). `read_file`, `read_skill`, `fetch_content` without `output` and the `lsp` navigation actions never ask. In `-p` and `mm --ask`, nobody can answer: those calls are rejected. An agent started by `/ask`, `ask_agent` or `mm --ask` runs with `AGENT_YOLO=false` and submits its calls to the asking agent: the `yolo` of its `mmConfigs` entry decides when set, else the asking agent's `yolo`. Overridden by `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` still decide which tools exist. See [How to confirm tool calls](../how-to/confirm-tool-calls.md). |
3536 | `permissions` | string | absent | Deprecated, replaced by `yolo`. `ask` = `yolo: false` (and wins over `yolo: true`); `auto` changes nothing. Either value adds a start-up warning. Must be `auto` or `ask`. |
3637 | `mmConfigs` | map name → path, or name → `{path, description, yolo}` | none | Other mm agents this one can ask. Each name generates `/ask-<name>` and `/ask-with-context-<name>` (see [slash commands](slash-commands.md#asking-another-agent)), and the list declares the [`ask_agent`](tools.md#ask_agent) tool, through which the model calls them itself. The long form adds `description` (when to call that agent; shown in `ask_agent`'s description, falling back to the agent file's `acp.title`) and `yolo` (decides that agent's tool calls when it is asked, by `/ask`, `ask_agent` or `mm --ask`, instead of this agent's `yolo`: `true` they run, `false` each one is asked; absent, this agent's `yolo` decides). `path` must not be empty. A relative path is relative to the directory of the file that declares it. Names: lowercase letters, digits and `-`, starting with a letter or a digit, not starting with `with-context`. A path whose file does not exist is a start-up warning, not an error. |
3738
@@ -54,6 +55,15 @@
5455 | `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |
5556 | `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |
5657
58+## `trace` keys
59+
60+| Key | Type | Default | Description |
61+|-----|------|---------|-------------|
62+| `trace.enabled` | bool | `false` | Write one pair of files per model call: the request body as sent (after mm's own fields: `stream_options`, `return_progress`, a summary's reasoning switch), indented, and the response body as received. Overridden by `AGENT_TRACE`. Headers are never written. The banner ends with ` \| trace: <absolute dir>` while it is on (` \| trace: <session cwd>/<dir>` in ACP mode). |
63+| `trace.dir` | string | `.mm/traces` | Directory of the files. A relative path is resolved per call in the working directory of the turn — the session's `cwd` in ACP mode, like `sessions.dir` — else in the directory `mm` runs in; an absolute path is used as-is. A directory that cannot be created prints `[trace: cannot write to <dir>: <error> — nothing is traced]` once (on stderr in ACP mode); the calls go on. Created when needed. Must not be empty when the trace is on. |
64+
65+File names: `<YYYYMMDD-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, then `….response-<status>.sse` (a streamed answer, the server's SSE lines byte for byte, written as they arrive), `….response-<status>.json` (a whole JSON body, indented), or `….response-error.txt` (no response: the method, the URL and the error). `NNNN` counts the model calls of the process; a retry by the HTTP client is a call of its own. An agent started by `/ask` or `ask_agent` inherits `AGENT_TRACE` and writes beside its asker, under its own `acp.name`.
66+
5767 ## `acp` keys
5868
5969 | Key | Type | Default | Description |
@@ -113,6 +123,7 @@ Reported as `[config error: <path>: <message>]`, exit code 1.
113123 | `context.summaryMaxTokens must be > 0` | zero or negative |
114124 | `context.maxMessages must be >= 0` | negative value |
115125 | `sessions.dir must not be empty` | empty string |
126+| `trace.dir must not be empty` | empty string with the trace on |
116127 | `acp.name must not be empty` | empty or blank string |
117128 | `lsp.servers[N].name must not be empty` | a server without `name` |
118129 | `lsp.servers[N].command must not be empty` | a server without `command` |
@@ -28,10 +28,11 @@
28 | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |28 | `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |
29 | `context` | map | see below | Automatic history compression. |29 | `context` | map | see below | Automatic history compression. |
30 | `sessions` | map | see below | Persistent, per-project sessions. |30 | `sessions` | map | see below | Persistent, per-project sessions. |
31+| `trace` | map | see below | Write every request to the model and every response to files. |
31 | `acp` | map | see below | How `mm -acp` introduces itself to the editor. |32 | `acp` | map | see below | How `mm -acp` introduces itself to the editor. |
32 | `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. |33 | `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. |
33 | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. |34 | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. |
34-| `yolo` | bool | `true` | `true`: the tools run without asking. `false`: every call that changes something — `bash`, `write_file`, `edit_file`, `fetch_content` with `output`, `lsp` `rename`, `plan` `create`/`update`/`next`/`delete` — waits for the user's answer in every front end (line mode, window, editor), and so does every MCP tool, whatever `mcp.permissions` says. Answers: allow once, allow this tool for the session, reject this call, never (this tool, for the session). `read_file`, `read_skill`, `fetch_content` without `output` and the `lsp` navigation actions never ask. In `-p` and `mm --ask`, nobody can answer: those calls are rejected. An agent started by `/ask` runs with `AGENT_YOLO=false` and submits its calls to the asking agent, whose `yolo` decides. Overridden by `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` still decide which tools exist. See [How to confirm tool calls](../how-to/confirm-tool-calls.md). |35+| `yolo` | bool | `true` | `true`: the tools run without asking. `false`: every call that changes something — `bash`, `write_file`, `edit_file`, `fetch_content` with `output`, `lsp` `rename`, `plan` `create`/`update`/`next`/`delete` — waits for the user's answer in every front end (line mode, window, editor), and so does every MCP tool, whatever `mcp.permissions` says. Answers: allow once, allow this tool for the session, reject this call, never (this tool, for the session). `read_file`, `read_skill`, `fetch_content` without `output` and the `lsp` navigation actions never ask. In `-p` and `mm --ask`, nobody can answer: those calls are rejected. An agent started by `/ask`, `ask_agent` or `mm --ask` runs with `AGENT_YOLO=false` and submits its calls to the asking agent: the `yolo` of its `mmConfigs` entry decides when set, else the asking agent's `yolo`. Overridden by `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` still decide which tools exist. See [How to confirm tool calls](../how-to/confirm-tool-calls.md). |
35 | `permissions` | string | absent | Deprecated, replaced by `yolo`. `ask` = `yolo: false` (and wins over `yolo: true`); `auto` changes nothing. Either value adds a start-up warning. Must be `auto` or `ask`. |36 | `permissions` | string | absent | Deprecated, replaced by `yolo`. `ask` = `yolo: false` (and wins over `yolo: true`); `auto` changes nothing. Either value adds a start-up warning. Must be `auto` or `ask`. |
36 | `mmConfigs` | map name → path, or name → `{path, description, yolo}` | none | Other mm agents this one can ask. Each name generates `/ask-<name>` and `/ask-with-context-<name>` (see [slash commands](slash-commands.md#asking-another-agent)), and the list declares the [`ask_agent`](tools.md#ask_agent) tool, through which the model calls them itself. The long form adds `description` (when to call that agent; shown in `ask_agent`'s description, falling back to the agent file's `acp.title`) and `yolo` (decides that agent's tool calls when it is asked, by `/ask`, `ask_agent` or `mm --ask`, instead of this agent's `yolo`: `true` they run, `false` each one is asked; absent, this agent's `yolo` decides). `path` must not be empty. A relative path is relative to the directory of the file that declares it. Names: lowercase letters, digits and `-`, starting with a letter or a digit, not starting with `with-context`. A path whose file does not exist is a start-up warning, not an error. |37 | `mmConfigs` | map name → path, or name → `{path, description, yolo}` | none | Other mm agents this one can ask. Each name generates `/ask-<name>` and `/ask-with-context-<name>` (see [slash commands](slash-commands.md#asking-another-agent)), and the list declares the [`ask_agent`](tools.md#ask_agent) tool, through which the model calls them itself. The long form adds `description` (when to call that agent; shown in `ask_agent`'s description, falling back to the agent file's `acp.title`) and `yolo` (decides that agent's tool calls when it is asked, by `/ask`, `ask_agent` or `mm --ask`, instead of this agent's `yolo`: `true` they run, `false` each one is asked; absent, this agent's `yolo` decides). `path` must not be empty. A relative path is relative to the directory of the file that declares it. Names: lowercase letters, digits and `-`, starting with a letter or a digit, not starting with `with-context`. A path whose file does not exist is a start-up warning, not an error. |
37 38
@@ -54,6 +55,15 @@
54 | `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |55 | `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |
55 | `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |56 | `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |
56 57
58+## `trace` keys
59+
60+| Key | Type | Default | Description |
61+|-----|------|---------|-------------|
62+| `trace.enabled` | bool | `false` | Write one pair of files per model call: the request body as sent (after mm's own fields: `stream_options`, `return_progress`, a summary's reasoning switch), indented, and the response body as received. Overridden by `AGENT_TRACE`. Headers are never written. The banner ends with ` \| trace: <absolute dir>` while it is on (` \| trace: <session cwd>/<dir>` in ACP mode). |
63+| `trace.dir` | string | `.mm/traces` | Directory of the files. A relative path is resolved per call in the working directory of the turn — the session's `cwd` in ACP mode, like `sessions.dir` — else in the directory `mm` runs in; an absolute path is used as-is. A directory that cannot be created prints `[trace: cannot write to <dir>: <error> — nothing is traced]` once (on stderr in ACP mode); the calls go on. Created when needed. Must not be empty when the trace is on. |
64+
65+File names: `<YYYYMMDD-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, then `….response-<status>.sse` (a streamed answer, the server's SSE lines byte for byte, written as they arrive), `….response-<status>.json` (a whole JSON body, indented), or `….response-error.txt` (no response: the method, the URL and the error). `NNNN` counts the model calls of the process; a retry by the HTTP client is a call of its own. An agent started by `/ask` or `ask_agent` inherits `AGENT_TRACE` and writes beside its asker, under its own `acp.name`.
66+
57 ## `acp` keys67 ## `acp` keys
58 68
59 | Key | Type | Default | Description |69 | Key | Type | Default | Description |
@@ -113,6 +123,7 @@ Reported as `[config error: <path>: <message>]`, exit code 1.
113 | `context.summaryMaxTokens must be > 0` | zero or negative |123 | `context.summaryMaxTokens must be > 0` | zero or negative |
114 | `context.maxMessages must be >= 0` | negative value |124 | `context.maxMessages must be >= 0` | negative value |
115 | `sessions.dir must not be empty` | empty string |125 | `sessions.dir must not be empty` | empty string |
126+| `trace.dir must not be empty` | empty string with the trace on |
116 | `acp.name must not be empty` | empty or blank string |127 | `acp.name must not be empty` | empty or blank string |
117 | `lsp.servers[N].name must not be empty` | a server without `name` |128 | `lsp.servers[N].name must not be empty` | a server without `name` |
118 | `lsp.servers[N].command must not be empty` | a server without `command` |129 | `lsp.servers[N].command must not be empty` | a server without `command` |
modified docs/fr/README.md +2 -1
@@ -14,6 +14,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
1414 - [Faire tourner mini-me sur llmman](how-to/run-with-llmman.md) — `provider: llmman`, la config de démo dans `demo-llmman/`
1515 - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/`
1616 - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>`
17+- [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq
1718 - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent
1819 - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`
1920 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)
@@ -61,7 +62,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
6162 - [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission
6263 - [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues
6364 - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie
64-- [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide
65+- [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide (et un `yolo` par agent dans `mmConfigs`)
6566 - [Interroger un autre agent](explanation/asking-another-agent.md) — pourquoi un autre agent mm est un processus enfant interrogé en ACP, la paire de messages, les sessions neuves, `ask_agent` et le `yolo` par agent, la limite de profondeur
6667 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
6768
@@ -14,6 +14,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
14 - [Faire tourner mini-me sur llmman](how-to/run-with-llmman.md) — `provider: llmman`, la config de démo dans `demo-llmman/`14 - [Faire tourner mini-me sur llmman](how-to/run-with-llmman.md) — `provider: llmman`, la config de démo dans `demo-llmman/`
15 - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/`15 - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/`
16 - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>`16 - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>`
17+- [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq
17 - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent18 - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent
18 - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`19 - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context`
19 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)20 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)
@@ -61,7 +62,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
61 - [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission62 - [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission
62 - [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues63 - [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues
63 - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie64 - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie
64-- [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide65+- [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide (et un `yolo` par agent dans `mmConfigs`)
65 - [Interroger un autre agent](explanation/asking-another-agent.md) — pourquoi un autre agent mm est un processus enfant interrogé en ACP, la paire de messages, les sessions neuves, `ask_agent` et le `yolo` par agent, la limite de profondeur66 - [Interroger un autre agent](explanation/asking-another-agent.md) — pourquoi un autre agent mm est un processus enfant interrogé en ACP, la paire de messages, les sessions neuves, `ask_agent` et le `yolo` par agent, la limite de profondeur
66 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)67 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
67 68
modified docs/fr/explanation/architecture.md +3 -3
@@ -9,13 +9,13 @@ Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages
99 | Paquet | Responsabilité |
1010 |--------|----------------|
1111 | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. |
12-| `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, et le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)). |
13-| `tools` | Les outils intégrés que le modèle peut appeler. |
12+| `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)), et la trace optionnelle de chaque requête et réponse (`trace.go`). |
13+| `tools` | Les outils intégrés que le modèle peut appeler, `ask_agent` compris (il interroge un agent de `mmConfigs` via `ask`). |
1414 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |
1515 | `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. |
1616 | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. |
1717 | `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. |
18-| `ask` | `/ask-<nom>` : démarre un autre agent mm (clé `mmConfigs`) comme enfant `mm -acp`, lui parle en client ACP, affiche sa réponse et ses appels d'outils via le `ui.Sink` de la façade qui pose la question, et renvoie l'échange pour l'historique. Voir [Interroger un autre agent](asking-another-agent.md). |
18+| `ask` | `/ask-<nom>` : démarre un autre agent mm (clé `mmConfigs`) comme enfant `mm -acp`, lui parle en client ACP, affiche sa réponse et ses appels d'outils via le `ui.Sink` de la façade qui pose la question, et renvoie l'échange pour l'historique ; le même pool sert l'outil `ask_agent` et `mm --ask`. Voir [Interroger un autre agent](asking-another-agent.md). |
1919 | `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. |
2020 | `store` | Fichiers de session par projet (`<dir>/<id>.json`, réécriture complète, atomique) : création, sauvegarde, chargement, liste, plus récente. Utilisé par les deux façades quand `sessions.enabled`. |
2121 | `mention` | La notation `@chemin` d'une question : les chemins existants deviennent des lignes `[attached file: …]`, la même forme que le `resource_link` d'un éditeur. Utilisé par les deux façades. |
@@ -9,13 +9,13 @@ Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages
9 | Paquet | Responsabilité |9 | Paquet | Responsabilité |
10 |--------|----------------|10 |--------|----------------|
11 | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. |11 | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. |
12-| `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, et le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)). |12+| `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)), et la trace optionnelle de chaque requête et réponse (`trace.go`). |
13-| `tools` | Les outils intégrés que le modèle peut appeler. |13+| `tools` | Les outils intégrés que le modèle peut appeler, `ask_agent` compris (il interroge un agent de `mmConfigs` via `ask`). |
14 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |14 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |
15 | `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. |15 | `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. |
16 | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. |16 | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. |
17 | `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. |17 | `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. |
18-| `ask` | `/ask-<nom>` : démarre un autre agent mm (clé `mmConfigs`) comme enfant `mm -acp`, lui parle en client ACP, affiche sa réponse et ses appels d'outils via le `ui.Sink` de la façade qui pose la question, et renvoie l'échange pour l'historique. Voir [Interroger un autre agent](asking-another-agent.md). |18+| `ask` | `/ask-<nom>` : démarre un autre agent mm (clé `mmConfigs`) comme enfant `mm -acp`, lui parle en client ACP, affiche sa réponse et ses appels d'outils via le `ui.Sink` de la façade qui pose la question, et renvoie l'échange pour l'historique ; le même pool sert l'outil `ask_agent` et `mm --ask`. Voir [Interroger un autre agent](asking-another-agent.md). |
19 | `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. |19 | `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. |
20 | `store` | Fichiers de session par projet (`<dir>/<id>.json`, réécriture complète, atomique) : création, sauvegarde, chargement, liste, plus récente. Utilisé par les deux façades quand `sessions.enabled`. |20 | `store` | Fichiers de session par projet (`<dir>/<id>.json`, réécriture complète, atomique) : création, sauvegarde, chargement, liste, plus récente. Utilisé par les deux façades quand `sessions.enabled`. |
21 | `mention` | La notation `@chemin` d'une question : les chemins existants deviennent des lignes `[attached file: …]`, la même forme que le `resource_link` d'un éditeur. Utilisé par les deux façades. |21 | `mention` | La notation `@chemin` d'une question : les chemins existants deviennent des lignes `[attached file: …]`, la même forme que le `resource_link` d'un éditeur. Utilisé par les deux façades. |
modified docs/fr/explanation/yolo-and-permissions.md +2 -0
@@ -33,6 +33,8 @@ Avec `mmConfigs`, un agent peut en interroger un autre (`/ask-jan`, `mm --ask ja
3333
3434 Techniquement, l'agent interrogé est lancé avec `AGENT_YOLO=false` : il soumet donc chaque appel qu'il filtrerait par la requête de permission ACP — le même canal qu'utilise un éditeur. L'agent qui interroge la reçoit et la fait passer par son propre `consent.Gate` : en mode yolo, il autorise aussitôt ; sinon, il demande à son propre utilisateur, dans sa propre façade, et mémorise la réponse sous `jan/bash`, pour qu'autoriser le shell de Jan n'autorise pas le vôtre. L'agent interrogé, lui, ne retient rien : on lui redemande à chaque fois, et c'est la mémoire de l'agent qui interroge qui répond.
3535
36+Un raffinement garde le même principe : le fichier de l'utilisateur peut donner à un agent interrogé son propre `yolo`, dans l'entrée `mmConfigs` de cet agent (`homer: {path: …, yolo: true}`). Il décide alors des appels de cet agent à la place du `yolo` de l'agent qui interroge : `true` les exécute — un codeur à qui l'on confie les modifications une fois le passage de main approuvé —, `false` demande pour chacun même quand l'agent qui interroge s'exécute librement. C'est toujours le fichier lancé par l'utilisateur qui décide ; le propre fichier de l'agent interrogé ne compte toujours pas.
37+
3638 ## Les modes qui ne peuvent pas demander
3739
3840 `mm -p` et `mm --ask` n'ont personne à qui demander : le premier est une exécution unique, le second lit sa question sur stdin. Avec `yolo: false`, ils refusent les appels qui demanderaient et le disent une fois sur stderr. Les autoriser en silence ferait de `yolo: false` un réglage qui dépend de la façon dont on a lancé mm, exactement l'incohérence que cette conception supprime ; rester bloqué sur une question invisible bloquerait les scripts pour toujours.
@@ -33,6 +33,8 @@ Avec `mmConfigs`, un agent peut en interroger un autre (`/ask-jan`, `mm --ask ja
33 33
34 Techniquement, l'agent interrogé est lancé avec `AGENT_YOLO=false` : il soumet donc chaque appel qu'il filtrerait par la requête de permission ACP — le même canal qu'utilise un éditeur. L'agent qui interroge la reçoit et la fait passer par son propre `consent.Gate` : en mode yolo, il autorise aussitôt ; sinon, il demande à son propre utilisateur, dans sa propre façade, et mémorise la réponse sous `jan/bash`, pour qu'autoriser le shell de Jan n'autorise pas le vôtre. L'agent interrogé, lui, ne retient rien : on lui redemande à chaque fois, et c'est la mémoire de l'agent qui interroge qui répond.34 Techniquement, l'agent interrogé est lancé avec `AGENT_YOLO=false` : il soumet donc chaque appel qu'il filtrerait par la requête de permission ACP — le même canal qu'utilise un éditeur. L'agent qui interroge la reçoit et la fait passer par son propre `consent.Gate` : en mode yolo, il autorise aussitôt ; sinon, il demande à son propre utilisateur, dans sa propre façade, et mémorise la réponse sous `jan/bash`, pour qu'autoriser le shell de Jan n'autorise pas le vôtre. L'agent interrogé, lui, ne retient rien : on lui redemande à chaque fois, et c'est la mémoire de l'agent qui interroge qui répond.
35 35
36+Un raffinement garde le même principe : le fichier de l'utilisateur peut donner à un agent interrogé son propre `yolo`, dans l'entrée `mmConfigs` de cet agent (`homer: {path: …, yolo: true}`). Il décide alors des appels de cet agent à la place du `yolo` de l'agent qui interroge : `true` les exécute — un codeur à qui l'on confie les modifications une fois le passage de main approuvé —, `false` demande pour chacun même quand l'agent qui interroge s'exécute librement. C'est toujours le fichier lancé par l'utilisateur qui décide ; le propre fichier de l'agent interrogé ne compte toujours pas.
37+
36 ## Les modes qui ne peuvent pas demander38 ## Les modes qui ne peuvent pas demander
37 39
38 `mm -p` et `mm --ask` n'ont personne à qui demander : le premier est une exécution unique, le second lit sa question sur stdin. Avec `yolo: false`, ils refusent les appels qui demanderaient et le disent une fois sur stderr. Les autoriser en silence ferait de `yolo: false` un réglage qui dépend de la façon dont on a lancé mm, exactement l'incohérence que cette conception supprime ; rester bloqué sur une question invisible bloquerait les scripts pour toujours.40 `mm -p` et `mm --ask` n'ont personne à qui demander : le premier est une exécution unique, le second lit sa question sur stdin. Avec `yolo: false`, ils refusent les appels qui demanderaient et le disent une fois sur stderr. Les autoriser en silence ferait de `yolo: false` un réglage qui dépend de la façon dont on a lancé mm, exactement l'incohérence que cette conception supprime ; rester bloqué sur une question invisible bloquerait les scripts pour toujours.
modified docs/fr/how-to/ask-another-agent.md +1 -1
@@ -50,7 +50,7 @@ Ce guide montre comment poser une question à un autre agent mm — un autre mod
5050
5151 - **Laisser l'autre agent voir la conversation.** Utilisez `/ask-with-context-lucy <prompt>` : Lucy reçoit une transcription de la conversation (questions, réponses, une ligne par appel d'outil — jamais les sorties d'outils) avant votre question.
5252 - **Dans le terminal ou la fenêtre, par nom.** `/ask lucy <prompt>` et `/ask-with-context lucy <prompt>` sont les mêmes commandes, avec l'agent en argument.
53-- **Dans un éditeur (ACP).** Le menu liste `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy` ; quand Lucy veut lancer une commande, c'est le `yolo` de votre agent qui décide : avec `yolo: true` elle s'exécute, avec `yolo: false` c'est la boîte de dialogue de permission de l'éditeur qui vous le demande — quoi que dise le fichier de Lucy. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).
53+- **Dans un éditeur (ACP).** Le menu liste `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy` ; quand Lucy veut lancer une commande, c'est le `yolo` de votre agent qui décide : avec `yolo: true` elle s'exécute, avec `yolo: false` c'est la boîte de dialogue de permission de l'éditeur qui vous le demande — quoi que dise le fichier de Lucy. Un `yolo` dans l'entrée de Lucy de VOS `mmConfigs` (`lucy: {path: agent.lucy.yaml, yolo: true}`) décide pour elle à la place. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).
5454 - **Arrêter une longue réponse.** `/abort`, `Échap` ou `Ctrl+C` — rien n'est gardé d'une demande annulée.
5555 - **Des agents sur d'autres fournisseurs.** N'importe quelle configuration mm convient : un agent Ollama à côté d'un agent llmman n'est qu'une entrée de plus dans `mmConfigs`.
5656
@@ -50,7 +50,7 @@ Ce guide montre comment poser une question à un autre agent mm — un autre mod
50 50
51 - **Laisser l'autre agent voir la conversation.** Utilisez `/ask-with-context-lucy <prompt>` : Lucy reçoit une transcription de la conversation (questions, réponses, une ligne par appel d'outil — jamais les sorties d'outils) avant votre question.51 - **Laisser l'autre agent voir la conversation.** Utilisez `/ask-with-context-lucy <prompt>` : Lucy reçoit une transcription de la conversation (questions, réponses, une ligne par appel d'outil — jamais les sorties d'outils) avant votre question.
52 - **Dans le terminal ou la fenêtre, par nom.** `/ask lucy <prompt>` et `/ask-with-context lucy <prompt>` sont les mêmes commandes, avec l'agent en argument.52 - **Dans le terminal ou la fenêtre, par nom.** `/ask lucy <prompt>` et `/ask-with-context lucy <prompt>` sont les mêmes commandes, avec l'agent en argument.
53-- **Dans un éditeur (ACP).** Le menu liste `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy` ; quand Lucy veut lancer une commande, c'est le `yolo` de votre agent qui décide : avec `yolo: true` elle s'exécute, avec `yolo: false` c'est la boîte de dialogue de permission de l'éditeur qui vous le demande — quoi que dise le fichier de Lucy. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).53+- **Dans un éditeur (ACP).** Le menu liste `/ask-jan`, `/ask-with-context-jan`, `/ask-lucy`, `/ask-with-context-lucy` ; quand Lucy veut lancer une commande, c'est le `yolo` de votre agent qui décide : avec `yolo: true` elle s'exécute, avec `yolo: false` c'est la boîte de dialogue de permission de l'éditeur qui vous le demande — quoi que dise le fichier de Lucy. Un `yolo` dans l'entrée de Lucy de VOS `mmConfigs` (`lucy: {path: agent.lucy.yaml, yolo: true}`) décide pour elle à la place. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).
54 - **Arrêter une longue réponse.** `/abort`, `Échap` ou `Ctrl+C` — rien n'est gardé d'une demande annulée.54 - **Arrêter une longue réponse.** `/abort`, `Échap` ou `Ctrl+C` — rien n'est gardé d'une demande annulée.
55 - **Des agents sur d'autres fournisseurs.** N'importe quelle configuration mm convient : un agent Ollama à côté d'un agent llmman n'est qu'une entrée de plus dans `mmConfigs`.55 - **Des agents sur d'autres fournisseurs.** N'importe quelle configuration mm convient : un agent Ollama à côté d'un agent llmman n'est qu'une entrée de plus dans `mmConfigs`.
56 56
added docs/fr/how-to/inspect-model-traffic.md +62 -0
new file mode 100644
@@ -0,0 +1,62 @@
1+# Comment voir le JSON envoyé au modèle et sa réponse
2+
3+Ce guide montre comment garder, pour chaque appel au modèle, la requête exacte envoyée par mm et la réponse exacte renvoyée par le serveur — pour vérifier un prompt système, les définitions d'outils, une clé d'échantillonnage, ce qu'un autre agent a reçu, ou ce qu'un modèle a vraiment répondu. Il suppose que vous lancez déjà `mm` avec un fichier de configuration.
4+
5+## Étapes
6+
7+1. Activez la trace, pour une exécution :
8+
9+ ```bash
10+ AGENT_TRACE=true mm agent.yaml
11+ ```
12+
13+ ou durablement, dans la configuration :
14+
15+ ```yaml
16+ trace:
17+ enabled: true
18+ dir: .mm/traces # la valeur par défaut
19+ ```
20+
21+ La bannière se termine par `| trace: /chemin/absolu/.mm/traces` tant qu'elle est active.
22+
23+2. Posez une question comme d'habitude. Chaque appel au modèle laisse une paire de fichiers :
24+
25+ ```text
26+ .mm/traces/20261007-123237.903-bob-0001.request.json
27+ .mm/traces/20261007-123237.903-bob-0001.response-200.sse
28+ ```
29+
30+ Une question peut faire plusieurs appels : un par aller-retour avec les outils, plus un résumé de compaction. L'`acp.name` de l'agent figure dans chaque nom, donc un agent appelé par `ask_agent` ou `/ask` — il hérite de `AGENT_TRACE` — écrit ses propres fichiers à côté des vôtres (`…-jan-0001…`).
31+
32+3. Lisez la requête. C'est le corps exactement tel qu'envoyé, indenté :
33+
34+ ```bash
35+ jq '{model, temperature, stream_options, chat_template_kwargs}' .mm/traces/*-0001.request.json
36+ jq -r '.messages[] | "\(.role): \(.content | tostring | .[0:120])"' .mm/traces/*-0001.request.json
37+ jq -r '.tools[].function.name' .mm/traces/*-0001.request.json
38+ ```
39+
40+4. Lisez la réponse. Une réponse en streaming (`.sse`) est faite des lignes du serveur telles qu'elles sont arrivées, un chunk `data: {…}` chacune ; reconstituez le texte de la réponse avec :
41+
42+ ```bash
43+ grep '^data: {' .mm/traces/*-0001.response-200.sse | sed 's/^data: //' \
44+ | jq -rj '.choices[0].delta.content // empty'; echo
45+ ```
46+
47+ son raisonnement avec `.choices[0].delta.reasoning_content`, ses appels d'outils avec `.choices[0].delta.tool_calls`, ses comptes de tokens avec `.usage` (le dernier chunk). Une réponse sans streaming est un fichier `.json`, indenté. Un statut d'erreur figure dans le nom (`.response-400.json`) ; un appel resté sans réponse laisse `.response-error.txt`.
48+
49+5. Désactivez-la quand vous avez fini : les fichiers contiennent vos prompts, le contenu des fichiers lus et les sorties des commandes, en entier. `.mm/` est ignoré par git dans ce dépôt ; ajoutez-le aussi au `.gitignore` de votre projet.
50+
51+## Variantes
52+
53+- **Dans un éditeur (ACP).** Un `trace.dir` relatif est dans le `cwd` de la session — le projet ouvert par l'éditeur — et non dans le répertoire d'où l'éditeur a lancé `mm`. Mettez `AGENT_TRACE` dans les réglages de l'agent de l'éditeur (`env`), ou `trace.enabled` dans le fichier : l'éditeur ne voit pas les variables de votre shell.
54+- **Ailleurs.** `trace.dir: /tmp/mm-traces` rassemble les appels de tous les projets au même endroit.
55+- **Seulement les agents interrogés.** Mettez `trace.enabled: true` dans le fichier de cet agent au lieu d'utiliser `AGENT_TRACE`.
56+- **Ce que mm ajoute.** La requête porte déjà ce que mm ajoute lui-même — `stream_options.include_usage`, `return_progress` pour un serveur llama.cpp, `chat_template_kwargs.enable_thinking: false` sur un résumé — la trace sert donc aussi à vérifier ces ajouts.
57+
58+## Voir aussi
59+
60+- Les clés `trace` et les noms des fichiers : [référence de la configuration](../reference/configuration.md)
61+- `AGENT_TRACE` : [référence de la ligne de commande](../reference/cli.md)
62+- Pourquoi le raisonnement arrive dans son propre champ : [Modèles à raisonnement](../explanation/thinking-models.md)
new file mode 100644
@@ -0,0 +1,62 @@
1+# Comment voir le JSON envoyé au modèle et sa réponse
2+
3+Ce guide montre comment garder, pour chaque appel au modèle, la requête exacte envoyée par mm et la réponse exacte renvoyée par le serveur — pour vérifier un prompt système, les définitions d'outils, une clé d'échantillonnage, ce qu'un autre agent a reçu, ou ce qu'un modèle a vraiment répondu. Il suppose que vous lancez déjà `mm` avec un fichier de configuration.
4+
5+## Étapes
6+
7+1. Activez la trace, pour une exécution :
8+
9+ ```bash
10+ AGENT_TRACE=true mm agent.yaml
11+ ```
12+
13+ ou durablement, dans la configuration :
14+
15+ ```yaml
16+ trace:
17+ enabled: true
18+ dir: .mm/traces # la valeur par défaut
19+ ```
20+
21+ La bannière se termine par `| trace: /chemin/absolu/.mm/traces` tant qu'elle est active.
22+
23+2. Posez une question comme d'habitude. Chaque appel au modèle laisse une paire de fichiers :
24+
25+ ```text
26+ .mm/traces/20261007-123237.903-bob-0001.request.json
27+ .mm/traces/20261007-123237.903-bob-0001.response-200.sse
28+ ```
29+
30+ Une question peut faire plusieurs appels : un par aller-retour avec les outils, plus un résumé de compaction. L'`acp.name` de l'agent figure dans chaque nom, donc un agent appelé par `ask_agent` ou `/ask` — il hérite de `AGENT_TRACE` — écrit ses propres fichiers à côté des vôtres (`…-jan-0001…`).
31+
32+3. Lisez la requête. C'est le corps exactement tel qu'envoyé, indenté :
33+
34+ ```bash
35+ jq '{model, temperature, stream_options, chat_template_kwargs}' .mm/traces/*-0001.request.json
36+ jq -r '.messages[] | "\(.role): \(.content | tostring | .[0:120])"' .mm/traces/*-0001.request.json
37+ jq -r '.tools[].function.name' .mm/traces/*-0001.request.json
38+ ```
39+
40+4. Lisez la réponse. Une réponse en streaming (`.sse`) est faite des lignes du serveur telles qu'elles sont arrivées, un chunk `data: {…}` chacune ; reconstituez le texte de la réponse avec :
41+
42+ ```bash
43+ grep '^data: {' .mm/traces/*-0001.response-200.sse | sed 's/^data: //' \
44+ | jq -rj '.choices[0].delta.content // empty'; echo
45+ ```
46+
47+ son raisonnement avec `.choices[0].delta.reasoning_content`, ses appels d'outils avec `.choices[0].delta.tool_calls`, ses comptes de tokens avec `.usage` (le dernier chunk). Une réponse sans streaming est un fichier `.json`, indenté. Un statut d'erreur figure dans le nom (`.response-400.json`) ; un appel resté sans réponse laisse `.response-error.txt`.
48+
49+5. Désactivez-la quand vous avez fini : les fichiers contiennent vos prompts, le contenu des fichiers lus et les sorties des commandes, en entier. `.mm/` est ignoré par git dans ce dépôt ; ajoutez-le aussi au `.gitignore` de votre projet.
50+
51+## Variantes
52+
53+- **Dans un éditeur (ACP).** Un `trace.dir` relatif est dans le `cwd` de la session — le projet ouvert par l'éditeur — et non dans le répertoire d'où l'éditeur a lancé `mm`. Mettez `AGENT_TRACE` dans les réglages de l'agent de l'éditeur (`env`), ou `trace.enabled` dans le fichier : l'éditeur ne voit pas les variables de votre shell.
54+- **Ailleurs.** `trace.dir: /tmp/mm-traces` rassemble les appels de tous les projets au même endroit.
55+- **Seulement les agents interrogés.** Mettez `trace.enabled: true` dans le fichier de cet agent au lieu d'utiliser `AGENT_TRACE`.
56+- **Ce que mm ajoute.** La requête porte déjà ce que mm ajoute lui-même — `stream_options.include_usage`, `return_progress` pour un serveur llama.cpp, `chat_template_kwargs.enable_thinking: false` sur un résumé — la trace sert donc aussi à vérifier ces ajouts.
57+
58+## Voir aussi
59+
60+- Les clés `trace` et les noms des fichiers : [référence de la configuration](../reference/configuration.md)
61+- `AGENT_TRACE` : [référence de la ligne de commande](../reference/cli.md)
62+- Pourquoi le raisonnement arrive dans son propre champ : [Modèles à raisonnement](../explanation/thinking-models.md)
modified docs/fr/how-to/use-from-an-editor.md +1 -0
@@ -32,6 +32,7 @@ Ce guide montre comment héberger `mm` dans un éditeur qui parle l'Agent Client
3232 - **Compresser ou recommencer la conversation.** Envoyez `/compact` ou `/new` ; les éditeurs qui listent les commandes de l'agent proposent les deux dans le menu des commandes slash. Voir la [référence des commandes slash](../reference/slash-commands.md#mode-acp).
3333 - **Attacher un fichier avec `@`.** Le sélecteur de l'éditeur envoie un `resource_link`, transmis au modèle sous la forme `[attached file: <chemin>]` ; un chemin tapé à la main comme `@chemin` est compris de la même façon (voir [`@chemin`](attach-a-file.md)). Le modèle lit le fichier avec ses outils.
3434 - **Recharger l'agent sans perdre le fil.** Avec `sessions.enabled: true` dans le fichier de configuration, le Reload Agent de l'éditeur (Zed) appelle `session/load` et toute la conversation, appels d'outils compris, est rejouée dans le fil. Voir [Conserver les sessions et les reprendre](keep-sessions.md).
35+- **Voir le JSON échangé avec le modèle.** Activez la trace, dans le fichier de configuration (`trace: {enabled: true}`) ou dans les réglages de l'éditeur (`"env": { "AGENT_CONFIG": "…", "AGENT_TRACE": "true" }` — l'éditeur ne transmet pas les variables de votre shell). Les fichiers arrivent dans `<projet>/.mm/traces/`, le projet étant le `cwd` de la session ; la bannière, dans les logs ACP, indique `trace: <session cwd>/.mm/traces`. Voir [Comment voir le JSON envoyé au modèle](inspect-model-traffic.md).
3536 - **Changer le nom que voit l'éditeur.** Ajoutez un bloc `acp:` au fichier de configuration ; `name` est l'identifiant que l'éditeur journalise, `title` le nom qu'il peut afficher (laissez `title` vide pour n'en envoyer aucun). La version envoyée est toujours celle du binaire, celle qu'affiche `mm --version` — vérifiez-la avec la requête `initialize` ci-dessous. Voir les [clés de `acp`](../reference/configuration.md#clés-de-acp).
3637
3738 ```yaml
@@ -32,6 +32,7 @@ Ce guide montre comment héberger `mm` dans un éditeur qui parle l'Agent Client
32 - **Compresser ou recommencer la conversation.** Envoyez `/compact` ou `/new` ; les éditeurs qui listent les commandes de l'agent proposent les deux dans le menu des commandes slash. Voir la [référence des commandes slash](../reference/slash-commands.md#mode-acp).32 - **Compresser ou recommencer la conversation.** Envoyez `/compact` ou `/new` ; les éditeurs qui listent les commandes de l'agent proposent les deux dans le menu des commandes slash. Voir la [référence des commandes slash](../reference/slash-commands.md#mode-acp).
33 - **Attacher un fichier avec `@`.** Le sélecteur de l'éditeur envoie un `resource_link`, transmis au modèle sous la forme `[attached file: <chemin>]` ; un chemin tapé à la main comme `@chemin` est compris de la même façon (voir [`@chemin`](attach-a-file.md)). Le modèle lit le fichier avec ses outils.33 - **Attacher un fichier avec `@`.** Le sélecteur de l'éditeur envoie un `resource_link`, transmis au modèle sous la forme `[attached file: <chemin>]` ; un chemin tapé à la main comme `@chemin` est compris de la même façon (voir [`@chemin`](attach-a-file.md)). Le modèle lit le fichier avec ses outils.
34 - **Recharger l'agent sans perdre le fil.** Avec `sessions.enabled: true` dans le fichier de configuration, le Reload Agent de l'éditeur (Zed) appelle `session/load` et toute la conversation, appels d'outils compris, est rejouée dans le fil. Voir [Conserver les sessions et les reprendre](keep-sessions.md).34 - **Recharger l'agent sans perdre le fil.** Avec `sessions.enabled: true` dans le fichier de configuration, le Reload Agent de l'éditeur (Zed) appelle `session/load` et toute la conversation, appels d'outils compris, est rejouée dans le fil. Voir [Conserver les sessions et les reprendre](keep-sessions.md).
35+- **Voir le JSON échangé avec le modèle.** Activez la trace, dans le fichier de configuration (`trace: {enabled: true}`) ou dans les réglages de l'éditeur (`"env": { "AGENT_CONFIG": "…", "AGENT_TRACE": "true" }` — l'éditeur ne transmet pas les variables de votre shell). Les fichiers arrivent dans `<projet>/.mm/traces/`, le projet étant le `cwd` de la session ; la bannière, dans les logs ACP, indique `trace: <session cwd>/.mm/traces`. Voir [Comment voir le JSON envoyé au modèle](inspect-model-traffic.md).
35 - **Changer le nom que voit l'éditeur.** Ajoutez un bloc `acp:` au fichier de configuration ; `name` est l'identifiant que l'éditeur journalise, `title` le nom qu'il peut afficher (laissez `title` vide pour n'en envoyer aucun). La version envoyée est toujours celle du binaire, celle qu'affiche `mm --version` — vérifiez-la avec la requête `initialize` ci-dessous. Voir les [clés de `acp`](../reference/configuration.md#clés-de-acp).36 - **Changer le nom que voit l'éditeur.** Ajoutez un bloc `acp:` au fichier de configuration ; `name` est l'identifiant que l'éditeur journalise, `title` le nom qu'il peut afficher (laissez `title` vide pour n'en envoyer aucun). La version envoyée est toujours celle du binaire, celle qu'affiche `mm --version` — vérifiez-la avec la requête `initialize` ci-dessous. Voir les [clés de `acp`](../reference/configuration.md#clés-de-acp).
36 37
37 ```yaml38 ```yaml
modified docs/fr/how-to/use-the-window-mode.md +1 -0
@@ -28,6 +28,7 @@ Ce guide montre comment lancer mini-me dans une fenêtre unique en plein écran
2828
2929 - **Demander avant chaque `bash` et chaque outil de fichier.** Mettez `yolo: false` dans le YAML. La zone de saisie est remplacée par la question `? bash: rm -rf build` et quatre touches y répondent : `a` autoriser une fois, `s` autoriser cet outil pour la session, `r` (ou Échap) refuser, `n` ne jamais autoriser cet outil dans cette session. Un outil refusé dit au modèle `[command rejected by the user]`. La valeur par défaut, `yolo: true`, exécute les outils sans demander. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).
3030 - **Désigner un fichier au modèle.** `@chemin` fonctionne exactement comme dans le mode ligne : les chemins existants sont attachés et repris en lignes `📎`. Voir [Désigner un fichier au modèle avec `@chemin`](attach-a-file.md).
31+- **Voir quel agent répond.** L'en-tête et l'étiquette au-dessus de chaque réponse nomment l'agent : `mm`, ou `mm [<acp.title>]` quand la configuration donne un titre. Pendant qu'un autre agent travaille — `/ask-<nom>`, ou l'outil `ask_agent` — l'en-tête le nomme avec son modèle et `asked by` votre agent, et sa réponse porte son titre (ou son nom dans `mmConfigs`) ; puis les deux reviennent à votre agent. Voir [Laisser votre agent appeler d'autres agents](let-the-agent-call-other-agents.md).
3132 - **Conserver et reprendre des sessions.** Avec `sessions.enabled: true`, l'en-tête affiche l'identifiant de la session enregistrée ; `/sessions` et `/resume` fonctionnent comme dans le mode ligne. Voir [Conserver les sessions et les reprendre](keep-sessions.md).
3233 - **Faire défiler la conversation.** PgUp/PgDn paginent ; **Tab** donne le focus à la conversation pour que ↑/↓, Début et Fin la fassent défiler ; Fin reprend le suivi de la fin. **Ctrl-L** efface la conversation affichée sans toucher à l'historique. La molette ne fait défiler qu'après **F2** (voir plus bas).
3334 - **Pas de terminal.** `mm -tui | tee log.txt`, ou tout lancement où stdin ou stdout n'est pas un terminal, affiche `[tui: stdin or stdout is not a terminal — running the line mode]` et lance le mode ligne. Rien d'autre ne change.
@@ -28,6 +28,7 @@ Ce guide montre comment lancer mini-me dans une fenêtre unique en plein écran
28 28
29 - **Demander avant chaque `bash` et chaque outil de fichier.** Mettez `yolo: false` dans le YAML. La zone de saisie est remplacée par la question `? bash: rm -rf build` et quatre touches y répondent : `a` autoriser une fois, `s` autoriser cet outil pour la session, `r` (ou Échap) refuser, `n` ne jamais autoriser cet outil dans cette session. Un outil refusé dit au modèle `[command rejected by the user]`. La valeur par défaut, `yolo: true`, exécute les outils sans demander. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).29 - **Demander avant chaque `bash` et chaque outil de fichier.** Mettez `yolo: false` dans le YAML. La zone de saisie est remplacée par la question `? bash: rm -rf build` et quatre touches y répondent : `a` autoriser une fois, `s` autoriser cet outil pour la session, `r` (ou Échap) refuser, `n` ne jamais autoriser cet outil dans cette session. Un outil refusé dit au modèle `[command rejected by the user]`. La valeur par défaut, `yolo: true`, exécute les outils sans demander. Voir [Confirmer les appels d'outils](confirm-tool-calls.md).
30 - **Désigner un fichier au modèle.** `@chemin` fonctionne exactement comme dans le mode ligne : les chemins existants sont attachés et repris en lignes `📎`. Voir [Désigner un fichier au modèle avec `@chemin`](attach-a-file.md).30 - **Désigner un fichier au modèle.** `@chemin` fonctionne exactement comme dans le mode ligne : les chemins existants sont attachés et repris en lignes `📎`. Voir [Désigner un fichier au modèle avec `@chemin`](attach-a-file.md).
31+- **Voir quel agent répond.** L'en-tête et l'étiquette au-dessus de chaque réponse nomment l'agent : `mm`, ou `mm [<acp.title>]` quand la configuration donne un titre. Pendant qu'un autre agent travaille — `/ask-<nom>`, ou l'outil `ask_agent` — l'en-tête le nomme avec son modèle et `asked by` votre agent, et sa réponse porte son titre (ou son nom dans `mmConfigs`) ; puis les deux reviennent à votre agent. Voir [Laisser votre agent appeler d'autres agents](let-the-agent-call-other-agents.md).
31 - **Conserver et reprendre des sessions.** Avec `sessions.enabled: true`, l'en-tête affiche l'identifiant de la session enregistrée ; `/sessions` et `/resume` fonctionnent comme dans le mode ligne. Voir [Conserver les sessions et les reprendre](keep-sessions.md).32 - **Conserver et reprendre des sessions.** Avec `sessions.enabled: true`, l'en-tête affiche l'identifiant de la session enregistrée ; `/sessions` et `/resume` fonctionnent comme dans le mode ligne. Voir [Conserver les sessions et les reprendre](keep-sessions.md).
32 - **Faire défiler la conversation.** PgUp/PgDn paginent ; **Tab** donne le focus à la conversation pour que ↑/↓, Début et Fin la fassent défiler ; Fin reprend le suivi de la fin. **Ctrl-L** efface la conversation affichée sans toucher à l'historique. La molette ne fait défiler qu'après **F2** (voir plus bas).33 - **Faire défiler la conversation.** PgUp/PgDn paginent ; **Tab** donne le focus à la conversation pour que ↑/↓, Début et Fin la fassent défiler ; Fin reprend le suivi de la fin. **Ctrl-L** efface la conversation affichée sans toucher à l'historique. La molette ne fait défiler qu'après **F2** (voir plus bas).
33 - **Pas de terminal.** `mm -tui | tee log.txt`, ou tout lancement où stdin ou stdout n'est pas un terminal, affiche `[tui: stdin or stdout is not a terminal — running the line mode]` et lance le mode ligne. Rien d'autre ne change.34 - **Pas de terminal.** `mm -tui | tee log.txt`, ou tout lancement où stdin ou stdout n'est pas un terminal, affiche `[tui: stdin or stdout is not a terminal — running the line mode]` et lance le mode ligne. Rien d'autre ne change.
modified docs/fr/reference/cli.md +4 -3
@@ -60,7 +60,8 @@ La première source existante gagne.
6060 | `AGENT_BASE_URL` | Surcharge `baseUrl` et désactive la sonde de repli. |
6161 | `DMR_BASE_URL` | Comme `AGENT_BASE_URL`, pour le fournisseur `dmr` seulement ; `AGENT_BASE_URL` gagne quand les deux sont définies. |
6262 | `AGENT_SESSIONS` | `off` désactive l'enregistrement des sessions pour cette exécution, quoi que dise `sessions.enabled`. Posée par `/ask` sur les agents qu'il lance. |
63-| `AGENT_YOLO` | Surcharge la clé `yolo` : `true` ou `false` (toute graphie acceptée par `strconv.ParseBool` ; toute autre valeur est une erreur de configuration). Posée à `false` par `/ask` sur les agents qu'il lance, pour que l'agent qui interroge décide. |
63+| `AGENT_TRACE` | Surcharge `trace.enabled` : `true` ou `false` (toute graphie acceptée par `strconv.ParseBool` ; toute autre valeur est une erreur de configuration). Héritée par les agents que lancent `/ask` et `ask_agent`. |
64+| `AGENT_YOLO` | Surcharge la clé `yolo` : `true` ou `false` (toute graphie acceptée par `strconv.ParseBool` ; toute autre valeur est une erreur de configuration). Posée à `false` par `/ask` et `ask_agent` sur les agents qu'ils lancent, pour que le côté qui interroge décide (le `yolo` de l'entrée `mmConfigs` de l'agent, sinon celui de l'agent qui interroge). |
6465 | `MM_BIN` | Posée par `mm` pour les commandes que lance son outil `bash` : le chemin du binaire `mm` en cours. |
6566 | `MM_CONFIG` | Posée par `mm` pour les commandes que lance son outil `bash` : le chemin absolu de son fichier de configuration. Lue par `--ask` / `--ask-with-context` quand ni `-config` ni chemin positionnel ne sont donnés. |
6667 | `MM_ASK_DEPTH` | Nombre de niveaux de `/ask` au-dessus de cet agent (absente = 0). Posée par `/ask` ; à 2, `/ask` est refusée. |
@@ -73,7 +74,7 @@ Affichée sur stdout en mode terminal, sur stderr en mode ACP. Grisée quand std
7374 | Ligne | Contenu |
7475 |-------|---------|
7576 | `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré, un fichier de `mmConfigs` introuvable (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), la clé obsolète `permissions`. |
76-| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`. |
77+| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`., et, tant que `trace.enabled` est actif, `trace: <répertoire absolu>` (`trace: <session cwd>/<dir>` en mode ACP). |
7778 | `Agent ready. Commands: …` | Mode terminal seulement. |
7879 | `Shortcut: Ctrl+C …` | Mode terminal seulement. |
7980 | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Mode terminal, avec `sessions.enabled` seulement : le répertoire de sessions résolu et le nombre de sessions qu'il contient déjà. |
@@ -141,7 +142,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
141142 | Contexte (`--ask-with-context`) | stdin. Sans `-p`, stdin contient `<contexte>`, une ligne `---`, puis la question. |
142143 | stdout | La réponse de l'agent, et rien d'autre, suivie d'un saut de ligne. |
143144 | stderr | `🗣 asking <agent> (<fournisseur>/<modèle>)…`, une ligne `🛠️ <agent> · <outil>: <arguments>` par appel d'outil, `[ask: …]` en cas d'erreur. |
144-| Appels d'outils de l'agent | Décidés par le `yolo` de cette configuration : `true` = autorisés ; `false` = refusés, avec la même ligne sur stderr qu'en mode invite (stdin portait la question, personne ne peut répondre). |
145+| Appels d'outils de l'agent | Décidés par le `yolo` de l'entrée `mmConfigs` de l'agent s'il est fixé, sinon par le `yolo` de cette configuration : `true` = autorisés ; `false` = refusés, avec la même ligne sur stderr qu'en mode invite (stdin portait la question, personne ne peut répondre). |
145146 | Processus parent | Surveillé toutes les 500 ms : s'il disparaît, la demande est annulée et l'agent arrêté. |
146147 | Session | Aucune : ni ce processus ni l'agent n'écrivent de fichier de session. |
147148
@@ -60,7 +60,8 @@ La première source existante gagne.
60 | `AGENT_BASE_URL` | Surcharge `baseUrl` et désactive la sonde de repli. |60 | `AGENT_BASE_URL` | Surcharge `baseUrl` et désactive la sonde de repli. |
61 | `DMR_BASE_URL` | Comme `AGENT_BASE_URL`, pour le fournisseur `dmr` seulement ; `AGENT_BASE_URL` gagne quand les deux sont définies. |61 | `DMR_BASE_URL` | Comme `AGENT_BASE_URL`, pour le fournisseur `dmr` seulement ; `AGENT_BASE_URL` gagne quand les deux sont définies. |
62 | `AGENT_SESSIONS` | `off` désactive l'enregistrement des sessions pour cette exécution, quoi que dise `sessions.enabled`. Posée par `/ask` sur les agents qu'il lance. |62 | `AGENT_SESSIONS` | `off` désactive l'enregistrement des sessions pour cette exécution, quoi que dise `sessions.enabled`. Posée par `/ask` sur les agents qu'il lance. |
63-| `AGENT_YOLO` | Surcharge la clé `yolo` : `true` ou `false` (toute graphie acceptée par `strconv.ParseBool` ; toute autre valeur est une erreur de configuration). Posée à `false` par `/ask` sur les agents qu'il lance, pour que l'agent qui interroge décide. |63+| `AGENT_TRACE` | Surcharge `trace.enabled` : `true` ou `false` (toute graphie acceptée par `strconv.ParseBool` ; toute autre valeur est une erreur de configuration). Héritée par les agents que lancent `/ask` et `ask_agent`. |
64+| `AGENT_YOLO` | Surcharge la clé `yolo` : `true` ou `false` (toute graphie acceptée par `strconv.ParseBool` ; toute autre valeur est une erreur de configuration). Posée à `false` par `/ask` et `ask_agent` sur les agents qu'ils lancent, pour que le côté qui interroge décide (le `yolo` de l'entrée `mmConfigs` de l'agent, sinon celui de l'agent qui interroge). |
64 | `MM_BIN` | Posée par `mm` pour les commandes que lance son outil `bash` : le chemin du binaire `mm` en cours. |65 | `MM_BIN` | Posée par `mm` pour les commandes que lance son outil `bash` : le chemin du binaire `mm` en cours. |
65 | `MM_CONFIG` | Posée par `mm` pour les commandes que lance son outil `bash` : le chemin absolu de son fichier de configuration. Lue par `--ask` / `--ask-with-context` quand ni `-config` ni chemin positionnel ne sont donnés. |66 | `MM_CONFIG` | Posée par `mm` pour les commandes que lance son outil `bash` : le chemin absolu de son fichier de configuration. Lue par `--ask` / `--ask-with-context` quand ni `-config` ni chemin positionnel ne sont donnés. |
66 | `MM_ASK_DEPTH` | Nombre de niveaux de `/ask` au-dessus de cet agent (absente = 0). Posée par `/ask` ; à 2, `/ask` est refusée. |67 | `MM_ASK_DEPTH` | Nombre de niveaux de `/ask` au-dessus de cet agent (absente = 0). Posée par `/ask` ; à 2, `/ask` est refusée. |
@@ -73,7 +74,7 @@ Affichée sur stdout en mode terminal, sur stderr en mode ACP. Grisée quand std
73 | Ligne | Contenu |74 | Ligne | Contenu |
74 |-------|---------|75 |-------|---------|
75 | `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré, un fichier de `mmConfigs` introuvable (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), la clé obsolète `permissions`. |76 | `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré, un fichier de `mmConfigs` introuvable (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), la clé obsolète `permissions`. |
76-| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`. |77+| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`., et, tant que `trace.enabled` est actif, `trace: <répertoire absolu>` (`trace: <session cwd>/<dir>` en mode ACP). |
77 | `Agent ready. Commands: …` | Mode terminal seulement. |78 | `Agent ready. Commands: …` | Mode terminal seulement. |
78 | `Shortcut: Ctrl+C …` | Mode terminal seulement. |79 | `Shortcut: Ctrl+C …` | Mode terminal seulement. |
79 | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Mode terminal, avec `sessions.enabled` seulement : le répertoire de sessions résolu et le nombre de sessions qu'il contient déjà. |80 | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Mode terminal, avec `sessions.enabled` seulement : le répertoire de sessions résolu et le nombre de sessions qu'il contient déjà. |
@@ -141,7 +142,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
141 | Contexte (`--ask-with-context`) | stdin. Sans `-p`, stdin contient `<contexte>`, une ligne `---`, puis la question. |142 | Contexte (`--ask-with-context`) | stdin. Sans `-p`, stdin contient `<contexte>`, une ligne `---`, puis la question. |
142 | stdout | La réponse de l'agent, et rien d'autre, suivie d'un saut de ligne. |143 | stdout | La réponse de l'agent, et rien d'autre, suivie d'un saut de ligne. |
143 | stderr | `🗣 asking <agent> (<fournisseur>/<modèle>)…`, une ligne `🛠️ <agent> · <outil>: <arguments>` par appel d'outil, `[ask: …]` en cas d'erreur. |144 | stderr | `🗣 asking <agent> (<fournisseur>/<modèle>)…`, une ligne `🛠️ <agent> · <outil>: <arguments>` par appel d'outil, `[ask: …]` en cas d'erreur. |
144-| Appels d'outils de l'agent | Décidés par le `yolo` de cette configuration : `true` = autorisés ; `false` = refusés, avec la même ligne sur stderr qu'en mode invite (stdin portait la question, personne ne peut répondre). |145+| Appels d'outils de l'agent | Décidés par le `yolo` de l'entrée `mmConfigs` de l'agent s'il est fixé, sinon par le `yolo` de cette configuration : `true` = autorisés ; `false` = refusés, avec la même ligne sur stderr qu'en mode invite (stdin portait la question, personne ne peut répondre). |
145 | Processus parent | Surveillé toutes les 500 ms : s'il disparaît, la demande est annulée et l'agent arrêté. |146 | Processus parent | Surveillé toutes les 500 ms : s'il disparaît, la demande est annulée et l'agent arrêté. |
146 | Session | Aucune : ni ce processus ni l'agent n'écrivent de fichier de session. |147 | Session | Aucune : ni ce processus ni l'agent n'écrivent de fichier de session. |
147 148
modified docs/fr/reference/configuration.md +12 -1
@@ -28,10 +28,11 @@
2828 | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. |
2929 | `context` | dictionnaire | voir ci-dessous | Compression automatique de l'historique. |
3030 | `sessions` | dictionnaire | voir ci-dessous | Sessions persistantes, par projet. |
31+| `trace` | dictionnaire | voir ci-dessous | Écrit chaque requête au modèle et chaque réponse dans des fichiers. |
3132 | `acp` | dictionnaire | voir ci-dessous | La façon dont `mm -acp` se présente à l'éditeur. |
3233 | `lsp` | dictionnaire | voir ci-dessous | Serveurs de langage derrière l'outil `lsp`. Aucun serveur listé = pas d'outil. |
3334 | `tui` | bool | `false` | Démarre dans le mode fenêtre unique sans l'option `-tui`. Retombe sur le mode ligne quand stdin ou stdout n'est pas un terminal ; ignoré avec `-acp`. |
34-| `yolo` | booléen | `true` | `true` : les outils s'exécutent sans demander. `false` : chaque appel qui modifie quelque chose — `bash`, `write_file`, `edit_file`, `fetch_content` avec `output`, `rename` de `lsp`, `create`/`update`/`next`/`delete` de `plan` — attend la réponse de l'utilisateur dans toutes les façades (mode ligne, fenêtre, éditeur), de même que chaque outil MCP, quoi que dise `mcp.permissions`. Réponses : autoriser une fois, autoriser cet outil pour la session, refuser cet appel, jamais (cet outil, pour la session). `read_file`, `read_skill`, `fetch_content` sans `output` et les actions de navigation de `lsp` ne demandent jamais. Avec `-p` et `mm --ask`, personne ne peut répondre : ces appels sont refusés. Un agent lancé par `/ask` tourne avec `AGENT_YOLO=false` et soumet ses appels à l'agent qui l'interroge, dont le `yolo` décide. Surchargée par `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` décident toujours quels outils existent. Voir [Confirmer les appels d'outils](../how-to/confirm-tool-calls.md). |
35+| `yolo` | booléen | `true` | `true` : les outils s'exécutent sans demander. `false` : chaque appel qui modifie quelque chose — `bash`, `write_file`, `edit_file`, `fetch_content` avec `output`, `rename` de `lsp`, `create`/`update`/`next`/`delete` de `plan` — attend la réponse de l'utilisateur dans toutes les façades (mode ligne, fenêtre, éditeur), de même que chaque outil MCP, quoi que dise `mcp.permissions`. Réponses : autoriser une fois, autoriser cet outil pour la session, refuser cet appel, jamais (cet outil, pour la session). `read_file`, `read_skill`, `fetch_content` sans `output` et les actions de navigation de `lsp` ne demandent jamais. Avec `-p` et `mm --ask`, personne ne peut répondre : ces appels sont refusés. Un agent lancé par `/ask`, `ask_agent` ou `mm --ask` tourne avec `AGENT_YOLO=false` et soumet ses appels à l'agent qui l'interroge : le `yolo` de son entrée `mmConfigs` décide s'il est fixé, sinon le `yolo` de l'agent qui interroge. Surchargée par `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` décident toujours quels outils existent. Voir [Confirmer les appels d'outils](../how-to/confirm-tool-calls.md). |
3536 | `permissions` | chaîne | absente | Obsolète, remplacée par `yolo`. `ask` = `yolo: false` (et l'emporte sur `yolo: true`) ; `auto` ne change rien. Les deux valeurs ajoutent un avertissement au démarrage. Doit valoir `auto` ou `ask`. |
3637 | `mmConfigs` | dictionnaire nom → chemin, ou nom → `{path, description, yolo}` | aucun | Les autres agents mm que celui-ci peut interroger. Chaque nom génère `/ask-<nom>` et `/ask-with-context-<nom>` (voir les [commandes slash](slash-commands.md#interroger-un-autre-agent)), et la liste déclare l'outil [`ask_agent`](tools.md#ask_agent), par lequel le modèle les appelle lui-même. La forme longue ajoute `description` (quand appeler cet agent ; affichée dans la description d'`ask_agent`, à défaut l'`acp.title` du fichier de l'agent) et `yolo` (décide des appels d'outils de cet agent quand il est interrogé, par `/ask`, `ask_agent` ou `mm --ask`, à la place du `yolo` de cet agent-ci : `true` ils s'exécutent, `false` chacun est demandé ; absent, le `yolo` de cet agent-ci décide). `path` ne doit pas être vide. Un chemin relatif l'est au dossier du fichier qui le déclare. Noms : lettres minuscules, chiffres et `-`, commençant par une lettre ou un chiffre, ne commençant pas par `with-context`. Un chemin dont le fichier n'existe pas donne un avertissement au démarrage, pas une erreur. |
3738
@@ -54,6 +55,15 @@
5455 | `sessions.enabled` | bool | `false` | Enregistre chaque conversation dans un fichier, réécrit après chaque tour, `/compact` et `/new`. Le fichier enregistre aussi, hors de l'historique, chaque erreur affichée (tableau `errors` : génération échouée, compression échouée, `/resume` échoué). Active `/sessions` et `/resume` dans le terminal et `session/load` en ACP. |
5556 | `sessions.dir` | chaîne | `.mm/sessions` | Répertoire des fichiers de session, un `<id>.json` chacun. Relatif au répertoire de lancement de `mm` (terminal) ou au `cwd` de la session (ACP) ; un chemin absolu est utilisé tel quel. Ne doit pas être vide. |
5657
58+## Clés de `trace`
59+
60+| Clé | Type | Défaut | Description |
61+|-----|------|--------|-------------|
62+| `trace.enabled` | bool | `false` | Écrit une paire de fichiers par appel au modèle : le corps de la requête tel qu'envoyé (après les champs ajoutés par mm : `stream_options`, `return_progress`, l'interrupteur de raisonnement d'un résumé), indenté, et le corps de la réponse tel que reçu. Surchargé par `AGENT_TRACE`. Les en-têtes ne sont jamais écrits. La bannière se termine par ` \| trace: <répertoire absolu>` tant qu'elle est active (` \| trace: <session cwd>/<dir>` en mode ACP). |
63+| `trace.dir` | chaîne | `.mm/traces` | Répertoire des fichiers. Un chemin relatif est résolu à chaque appel dans le répertoire de travail du tour — le `cwd` de la session en mode ACP, comme `sessions.dir` — sinon dans le répertoire où `mm` tourne ; un chemin absolu est utilisé tel quel. Un répertoire impossible à créer affiche une fois `[trace: cannot write to <dir>: <erreur> — nothing is traced]` (sur stderr en mode ACP) ; les appels continuent. Créé au besoin. Ne doit pas être vide quand la trace est active. |
64+
65+Noms des fichiers : `<AAAAMMJJ-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, puis `….response-<statut>.sse` (une réponse en streaming, les lignes SSE du serveur octet pour octet, écrites à leur arrivée), `….response-<statut>.json` (un corps JSON entier, indenté), ou `….response-error.txt` (pas de réponse : la méthode, l'URL et l'erreur). `NNNN` compte les appels au modèle du processus ; une nouvelle tentative du client HTTP est un appel à part. Un agent lancé par `/ask` ou `ask_agent` hérite de `AGENT_TRACE` et écrit à côté de celui qui l'interroge, sous son propre `acp.name`.
66+
5767 ## Clés de `acp`
5868
5969 | Clé | Type | Défaut | Description |
@@ -113,6 +123,7 @@ Signalées sous la forme `[config error: <chemin>: <message>]`, code de sortie 1
113123 | `context.summaryMaxTokens must be > 0` | zéro ou négatif |
114124 | `context.maxMessages must be >= 0` | valeur négative |
115125 | `sessions.dir must not be empty` | chaîne vide |
126+| `trace.dir must not be empty` | chaîne vide avec la trace active |
116127 | `acp.name must not be empty` | chaîne vide ou blanche |
117128 | `lsp.servers[N].name must not be empty` | un serveur sans `name` |
118129 | `lsp.servers[N].command must not be empty` | un serveur sans `command` |
@@ -28,10 +28,11 @@
28 | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. |28 | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. |
29 | `context` | dictionnaire | voir ci-dessous | Compression automatique de l'historique. |29 | `context` | dictionnaire | voir ci-dessous | Compression automatique de l'historique. |
30 | `sessions` | dictionnaire | voir ci-dessous | Sessions persistantes, par projet. |30 | `sessions` | dictionnaire | voir ci-dessous | Sessions persistantes, par projet. |
31+| `trace` | dictionnaire | voir ci-dessous | Écrit chaque requête au modèle et chaque réponse dans des fichiers. |
31 | `acp` | dictionnaire | voir ci-dessous | La façon dont `mm -acp` se présente à l'éditeur. |32 | `acp` | dictionnaire | voir ci-dessous | La façon dont `mm -acp` se présente à l'éditeur. |
32 | `lsp` | dictionnaire | voir ci-dessous | Serveurs de langage derrière l'outil `lsp`. Aucun serveur listé = pas d'outil. |33 | `lsp` | dictionnaire | voir ci-dessous | Serveurs de langage derrière l'outil `lsp`. Aucun serveur listé = pas d'outil. |
33 | `tui` | bool | `false` | Démarre dans le mode fenêtre unique sans l'option `-tui`. Retombe sur le mode ligne quand stdin ou stdout n'est pas un terminal ; ignoré avec `-acp`. |34 | `tui` | bool | `false` | Démarre dans le mode fenêtre unique sans l'option `-tui`. Retombe sur le mode ligne quand stdin ou stdout n'est pas un terminal ; ignoré avec `-acp`. |
34-| `yolo` | booléen | `true` | `true` : les outils s'exécutent sans demander. `false` : chaque appel qui modifie quelque chose — `bash`, `write_file`, `edit_file`, `fetch_content` avec `output`, `rename` de `lsp`, `create`/`update`/`next`/`delete` de `plan` — attend la réponse de l'utilisateur dans toutes les façades (mode ligne, fenêtre, éditeur), de même que chaque outil MCP, quoi que dise `mcp.permissions`. Réponses : autoriser une fois, autoriser cet outil pour la session, refuser cet appel, jamais (cet outil, pour la session). `read_file`, `read_skill`, `fetch_content` sans `output` et les actions de navigation de `lsp` ne demandent jamais. Avec `-p` et `mm --ask`, personne ne peut répondre : ces appels sont refusés. Un agent lancé par `/ask` tourne avec `AGENT_YOLO=false` et soumet ses appels à l'agent qui l'interroge, dont le `yolo` décide. Surchargée par `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` décident toujours quels outils existent. Voir [Confirmer les appels d'outils](../how-to/confirm-tool-calls.md). |35+| `yolo` | booléen | `true` | `true` : les outils s'exécutent sans demander. `false` : chaque appel qui modifie quelque chose — `bash`, `write_file`, `edit_file`, `fetch_content` avec `output`, `rename` de `lsp`, `create`/`update`/`next`/`delete` de `plan` — attend la réponse de l'utilisateur dans toutes les façades (mode ligne, fenêtre, éditeur), de même que chaque outil MCP, quoi que dise `mcp.permissions`. Réponses : autoriser une fois, autoriser cet outil pour la session, refuser cet appel, jamais (cet outil, pour la session). `read_file`, `read_skill`, `fetch_content` sans `output` et les actions de navigation de `lsp` ne demandent jamais. Avec `-p` et `mm --ask`, personne ne peut répondre : ces appels sont refusés. Un agent lancé par `/ask`, `ask_agent` ou `mm --ask` tourne avec `AGENT_YOLO=false` et soumet ses appels à l'agent qui l'interroge : le `yolo` de son entrée `mmConfigs` décide s'il est fixé, sinon le `yolo` de l'agent qui interroge. Surchargée par `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` décident toujours quels outils existent. Voir [Confirmer les appels d'outils](../how-to/confirm-tool-calls.md). |
35 | `permissions` | chaîne | absente | Obsolète, remplacée par `yolo`. `ask` = `yolo: false` (et l'emporte sur `yolo: true`) ; `auto` ne change rien. Les deux valeurs ajoutent un avertissement au démarrage. Doit valoir `auto` ou `ask`. |36 | `permissions` | chaîne | absente | Obsolète, remplacée par `yolo`. `ask` = `yolo: false` (et l'emporte sur `yolo: true`) ; `auto` ne change rien. Les deux valeurs ajoutent un avertissement au démarrage. Doit valoir `auto` ou `ask`. |
36 | `mmConfigs` | dictionnaire nom → chemin, ou nom → `{path, description, yolo}` | aucun | Les autres agents mm que celui-ci peut interroger. Chaque nom génère `/ask-<nom>` et `/ask-with-context-<nom>` (voir les [commandes slash](slash-commands.md#interroger-un-autre-agent)), et la liste déclare l'outil [`ask_agent`](tools.md#ask_agent), par lequel le modèle les appelle lui-même. La forme longue ajoute `description` (quand appeler cet agent ; affichée dans la description d'`ask_agent`, à défaut l'`acp.title` du fichier de l'agent) et `yolo` (décide des appels d'outils de cet agent quand il est interrogé, par `/ask`, `ask_agent` ou `mm --ask`, à la place du `yolo` de cet agent-ci : `true` ils s'exécutent, `false` chacun est demandé ; absent, le `yolo` de cet agent-ci décide). `path` ne doit pas être vide. Un chemin relatif l'est au dossier du fichier qui le déclare. Noms : lettres minuscules, chiffres et `-`, commençant par une lettre ou un chiffre, ne commençant pas par `with-context`. Un chemin dont le fichier n'existe pas donne un avertissement au démarrage, pas une erreur. |37 | `mmConfigs` | dictionnaire nom → chemin, ou nom → `{path, description, yolo}` | aucun | Les autres agents mm que celui-ci peut interroger. Chaque nom génère `/ask-<nom>` et `/ask-with-context-<nom>` (voir les [commandes slash](slash-commands.md#interroger-un-autre-agent)), et la liste déclare l'outil [`ask_agent`](tools.md#ask_agent), par lequel le modèle les appelle lui-même. La forme longue ajoute `description` (quand appeler cet agent ; affichée dans la description d'`ask_agent`, à défaut l'`acp.title` du fichier de l'agent) et `yolo` (décide des appels d'outils de cet agent quand il est interrogé, par `/ask`, `ask_agent` ou `mm --ask`, à la place du `yolo` de cet agent-ci : `true` ils s'exécutent, `false` chacun est demandé ; absent, le `yolo` de cet agent-ci décide). `path` ne doit pas être vide. Un chemin relatif l'est au dossier du fichier qui le déclare. Noms : lettres minuscules, chiffres et `-`, commençant par une lettre ou un chiffre, ne commençant pas par `with-context`. Un chemin dont le fichier n'existe pas donne un avertissement au démarrage, pas une erreur. |
37 38
@@ -54,6 +55,15 @@
54 | `sessions.enabled` | bool | `false` | Enregistre chaque conversation dans un fichier, réécrit après chaque tour, `/compact` et `/new`. Le fichier enregistre aussi, hors de l'historique, chaque erreur affichée (tableau `errors` : génération échouée, compression échouée, `/resume` échoué). Active `/sessions` et `/resume` dans le terminal et `session/load` en ACP. |55 | `sessions.enabled` | bool | `false` | Enregistre chaque conversation dans un fichier, réécrit après chaque tour, `/compact` et `/new`. Le fichier enregistre aussi, hors de l'historique, chaque erreur affichée (tableau `errors` : génération échouée, compression échouée, `/resume` échoué). Active `/sessions` et `/resume` dans le terminal et `session/load` en ACP. |
55 | `sessions.dir` | chaîne | `.mm/sessions` | Répertoire des fichiers de session, un `<id>.json` chacun. Relatif au répertoire de lancement de `mm` (terminal) ou au `cwd` de la session (ACP) ; un chemin absolu est utilisé tel quel. Ne doit pas être vide. |56 | `sessions.dir` | chaîne | `.mm/sessions` | Répertoire des fichiers de session, un `<id>.json` chacun. Relatif au répertoire de lancement de `mm` (terminal) ou au `cwd` de la session (ACP) ; un chemin absolu est utilisé tel quel. Ne doit pas être vide. |
56 57
58+## Clés de `trace`
59+
60+| Clé | Type | Défaut | Description |
61+|-----|------|--------|-------------|
62+| `trace.enabled` | bool | `false` | Écrit une paire de fichiers par appel au modèle : le corps de la requête tel qu'envoyé (après les champs ajoutés par mm : `stream_options`, `return_progress`, l'interrupteur de raisonnement d'un résumé), indenté, et le corps de la réponse tel que reçu. Surchargé par `AGENT_TRACE`. Les en-têtes ne sont jamais écrits. La bannière se termine par ` \| trace: <répertoire absolu>` tant qu'elle est active (` \| trace: <session cwd>/<dir>` en mode ACP). |
63+| `trace.dir` | chaîne | `.mm/traces` | Répertoire des fichiers. Un chemin relatif est résolu à chaque appel dans le répertoire de travail du tour — le `cwd` de la session en mode ACP, comme `sessions.dir` — sinon dans le répertoire où `mm` tourne ; un chemin absolu est utilisé tel quel. Un répertoire impossible à créer affiche une fois `[trace: cannot write to <dir>: <erreur> — nothing is traced]` (sur stderr en mode ACP) ; les appels continuent. Créé au besoin. Ne doit pas être vide quand la trace est active. |
64+
65+Noms des fichiers : `<AAAAMMJJ-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, puis `….response-<statut>.sse` (une réponse en streaming, les lignes SSE du serveur octet pour octet, écrites à leur arrivée), `….response-<statut>.json` (un corps JSON entier, indenté), ou `….response-error.txt` (pas de réponse : la méthode, l'URL et l'erreur). `NNNN` compte les appels au modèle du processus ; une nouvelle tentative du client HTTP est un appel à part. Un agent lancé par `/ask` ou `ask_agent` hérite de `AGENT_TRACE` et écrit à côté de celui qui l'interroge, sous son propre `acp.name`.
66+
57 ## Clés de `acp`67 ## Clés de `acp`
58 68
59 | Clé | Type | Défaut | Description |69 | Clé | Type | Défaut | Description |
@@ -113,6 +123,7 @@ Signalées sous la forme `[config error: <chemin>: <message>]`, code de sortie 1
113 | `context.summaryMaxTokens must be > 0` | zéro ou négatif |123 | `context.summaryMaxTokens must be > 0` | zéro ou négatif |
114 | `context.maxMessages must be >= 0` | valeur négative |124 | `context.maxMessages must be >= 0` | valeur négative |
115 | `sessions.dir must not be empty` | chaîne vide |125 | `sessions.dir must not be empty` | chaîne vide |
126+| `trace.dir must not be empty` | chaîne vide avec la trace active |
116 | `acp.name must not be empty` | chaîne vide ou blanche |127 | `acp.name must not be empty` | chaîne vide ou blanche |
117 | `lsp.servers[N].name must not be empty` | un serveur sans `name` |128 | `lsp.servers[N].name must not be empty` | un serveur sans `name` |
118 | `lsp.servers[N].command must not be empty` | un serveur sans `command` |129 | `lsp.servers[N].command must not be empty` | un serveur sans `command` |
modified internal/config/config.go +33 -0
@@ -139,6 +139,11 @@ type Config struct {
139139 // editor can reload one over ACP (session/load).
140140 Sessions SessionsConfig `yaml:"sessions"`
141141
142+ // Trace: write every request sent to the model and every response, as
143+ // they travel on the wire, to files (internal/engine/trace.go). Off
144+ // unless `trace.enabled: true` or AGENT_TRACE=true.
145+ Trace TraceConfig `yaml:"trace"`
146+
142147 // ACP: how the agent introduces itself to an editor in the `initialize`
143148 // response (`mm -acp`). The version is not configurable: it is the
144149 // binary's own, the one `mm --version` prints.
@@ -375,6 +380,15 @@ func (m MCPConfig) Validate() error {
375380 return nil
376381 }
377382
383+// TraceConfig is the `trace:` block of the YAML.
384+type TraceConfig struct {
385+ // Enabled turns the trace on. AGENT_TRACE (true/false) overrides it.
386+ Enabled bool `yaml:"enabled"`
387+ // Dir: where the trace files go, relative to the directory mm runs in;
388+ // an absolute path is used as-is.
389+ Dir string `yaml:"dir"`
390+}
391+
378392 type SessionsConfig struct {
379393 // Enabled turns the persistence on. Without it nothing is written and
380394 // ACP does not advertise session/load.
@@ -486,6 +500,10 @@ which ones you left running.`,
486500 Enabled: false,
487501 Dir: ".mm/sessions",
488502 },
503+ Trace: TraceConfig{
504+ Enabled: false,
505+ Dir: ".mm/traces",
506+ },
489507 ACP: ACPConfig{
490508 Name: "bob",
491509 Title: DefaultACPTitle,
@@ -582,9 +600,22 @@ func applyEnv() error {
582600 }
583601 Cfg.Yolo = yolo
584602 }
603+ // AGENT_TRACE: trace one run without editing the file. Not removed from
604+ // the environment of an agent started by /ask or ask_agent: tracing a
605+ // coordinator traces the agents it asks too, each under its own name.
606+ if v := os.Getenv(TraceEnv); v != "" {
607+ on, err := strconv.ParseBool(v)
608+ if err != nil {
609+ return fmt.Errorf("%s=%q: must be true or false", TraceEnv, v)
610+ }
611+ Cfg.Trace.Enabled = on
612+ }
585613 return nil
586614 }
587615
616+// TraceEnv names the variable that overrides `trace.enabled` for one run.
617+const TraceEnv = "AGENT_TRACE"
618+
588619 // applyDeprecated maps the deprecated top-level `permissions` onto Yolo:
589620 // "ask" was the window's way of asking, which `yolo: false` now means
590621 // everywhere. It wins over `yolo: true` written beside it — of two
@@ -684,6 +715,8 @@ func (c Config) validate() error {
684715 return errors.New("context.maxMessages must be >= 0")
685716 case c.Sessions.Dir == "":
686717 return errors.New("sessions.dir must not be empty")
718+ case c.Trace.Enabled && strings.TrimSpace(c.Trace.Dir) == "":
719+ return errors.New("trace.dir must not be empty")
687720 case strings.TrimSpace(c.ACP.Name) == "":
688721 return errors.New("acp.name must not be empty")
689722 case c.Permissions != "" && c.Permissions != PermissionsAuto && c.Permissions != PermissionsAsk:
@@ -139,6 +139,11 @@ type Config struct {
139 // editor can reload one over ACP (session/load).139 // editor can reload one over ACP (session/load).
140 Sessions SessionsConfig `yaml:"sessions"`140 Sessions SessionsConfig `yaml:"sessions"`
141 141
142+ // Trace: write every request sent to the model and every response, as
143+ // they travel on the wire, to files (internal/engine/trace.go). Off
144+ // unless `trace.enabled: true` or AGENT_TRACE=true.
145+ Trace TraceConfig `yaml:"trace"`
146+
142 // ACP: how the agent introduces itself to an editor in the `initialize`147 // ACP: how the agent introduces itself to an editor in the `initialize`
143 // response (`mm -acp`). The version is not configurable: it is the148 // response (`mm -acp`). The version is not configurable: it is the
144 // binary's own, the one `mm --version` prints.149 // binary's own, the one `mm --version` prints.
@@ -375,6 +380,15 @@ func (m MCPConfig) Validate() error {
375 return nil380 return nil
376 }381 }
377 382
383+// TraceConfig is the `trace:` block of the YAML.
384+type TraceConfig struct {
385+ // Enabled turns the trace on. AGENT_TRACE (true/false) overrides it.
386+ Enabled bool `yaml:"enabled"`
387+ // Dir: where the trace files go, relative to the directory mm runs in;
388+ // an absolute path is used as-is.
389+ Dir string `yaml:"dir"`
390+}
391+
378 type SessionsConfig struct {392 type SessionsConfig struct {
379 // Enabled turns the persistence on. Without it nothing is written and393 // Enabled turns the persistence on. Without it nothing is written and
380 // ACP does not advertise session/load.394 // ACP does not advertise session/load.
@@ -486,6 +500,10 @@ which ones you left running.`,
486 Enabled: false,500 Enabled: false,
487 Dir: ".mm/sessions",501 Dir: ".mm/sessions",
488 },502 },
503+ Trace: TraceConfig{
504+ Enabled: false,
505+ Dir: ".mm/traces",
506+ },
489 ACP: ACPConfig{507 ACP: ACPConfig{
490 Name: "bob",508 Name: "bob",
491 Title: DefaultACPTitle,509 Title: DefaultACPTitle,
@@ -582,9 +600,22 @@ func applyEnv() error {
582 }600 }
583 Cfg.Yolo = yolo601 Cfg.Yolo = yolo
584 }602 }
603+ // AGENT_TRACE: trace one run without editing the file. Not removed from
604+ // the environment of an agent started by /ask or ask_agent: tracing a
605+ // coordinator traces the agents it asks too, each under its own name.
606+ if v := os.Getenv(TraceEnv); v != "" {
607+ on, err := strconv.ParseBool(v)
608+ if err != nil {
609+ return fmt.Errorf("%s=%q: must be true or false", TraceEnv, v)
610+ }
611+ Cfg.Trace.Enabled = on
612+ }
585 return nil613 return nil
586 }614 }
587 615
616+// TraceEnv names the variable that overrides `trace.enabled` for one run.
617+const TraceEnv = "AGENT_TRACE"
618+
588 // applyDeprecated maps the deprecated top-level `permissions` onto Yolo:619 // applyDeprecated maps the deprecated top-level `permissions` onto Yolo:
589 // "ask" was the window's way of asking, which `yolo: false` now means620 // "ask" was the window's way of asking, which `yolo: false` now means
590 // everywhere. It wins over `yolo: true` written beside it — of two621 // everywhere. It wins over `yolo: true` written beside it — of two
@@ -684,6 +715,8 @@ func (c Config) validate() error {
684 return errors.New("context.maxMessages must be >= 0")715 return errors.New("context.maxMessages must be >= 0")
685 case c.Sessions.Dir == "":716 case c.Sessions.Dir == "":
686 return errors.New("sessions.dir must not be empty")717 return errors.New("sessions.dir must not be empty")
718+ case c.Trace.Enabled && strings.TrimSpace(c.Trace.Dir) == "":
719+ return errors.New("trace.dir must not be empty")
687 case strings.TrimSpace(c.ACP.Name) == "":720 case strings.TrimSpace(c.ACP.Name) == "":
688 return errors.New("acp.name must not be empty")721 return errors.New("acp.name must not be empty")
689 case c.Permissions != "" && c.Permissions != PermissionsAuto && c.Permissions != PermissionsAsk:722 case c.Permissions != "" && c.Permissions != PermissionsAuto && c.Permissions != PermissionsAsk:
modified internal/engine/openai_compat.go +12 -1
@@ -185,7 +185,7 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str
185185 // on the HTTP client, so a thinking model's reasoning can be read off
186186 // the wire the plugin drops it from (reasoning.go), and a streamed
187187 // request asks for the token counts it would otherwise lack (usage.go).
188- Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)},
188+ Opts: []option.RequestOption{option.WithMiddleware(middlewares(p)...)},
189189 }
190190 quietGenkit()
191191 g := genkit.Init(ctx, genkit.WithPlugins(client))
@@ -204,6 +204,17 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str
204204 return g, p.name + "/" + b.Model, nil
205205 }
206206
207+// middlewares is the HTTP client's chain, outermost first. The trace comes
208+// last, next to the network: it records the request as it leaves, after the
209+// others have added their fields, and the response before they read it.
210+func middlewares(p *openaiCompat) []option.Middleware {
211+ chain := []option.Middleware{streamOptions(p.progress, p.thinkingOff), reasoningTap}
212+ if config.Cfg.Trace.Enabled {
213+ chain = append(chain, traceMiddleware(config.Cfg.Trace.Dir, config.Cfg.ACP.Name))
214+ }
215+ return chain
216+}
217+
207218 // quietGenkit keeps Genkit's informational log lines off the screen: since
208219 // 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on
209220 // stderr, which lands in the middle of the terminal's output and of the
@@ -185,7 +185,7 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str
185 // on the HTTP client, so a thinking model's reasoning can be read off185 // on the HTTP client, so a thinking model's reasoning can be read off
186 // the wire the plugin drops it from (reasoning.go), and a streamed186 // the wire the plugin drops it from (reasoning.go), and a streamed
187 // request asks for the token counts it would otherwise lack (usage.go).187 // request asks for the token counts it would otherwise lack (usage.go).
188- Opts: []option.RequestOption{option.WithMiddleware(streamOptions(p.progress, p.thinkingOff), reasoningTap)},188+ Opts: []option.RequestOption{option.WithMiddleware(middlewares(p)...)},
189 }189 }
190 quietGenkit()190 quietGenkit()
191 g := genkit.Init(ctx, genkit.WithPlugins(client))191 g := genkit.Init(ctx, genkit.WithPlugins(client))
@@ -204,6 +204,17 @@ func (p *openaiCompat) Open(ctx context.Context, b Backend) (*genkit.Genkit, str
204 return g, p.name + "/" + b.Model, nil204 return g, p.name + "/" + b.Model, nil
205 }205 }
206 206
207+// middlewares is the HTTP client's chain, outermost first. The trace comes
208+// last, next to the network: it records the request as it leaves, after the
209+// others have added their fields, and the response before they read it.
210+func middlewares(p *openaiCompat) []option.Middleware {
211+ chain := []option.Middleware{streamOptions(p.progress, p.thinkingOff), reasoningTap}
212+ if config.Cfg.Trace.Enabled {
213+ chain = append(chain, traceMiddleware(config.Cfg.Trace.Dir, config.Cfg.ACP.Name))
214+ }
215+ return chain
216+}
217+
207 // quietGenkit keeps Genkit's informational log lines off the screen: since218 // quietGenkit keeps Genkit's informational log lines off the screen: since
208 // 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on219 // 1.13 Init logs "INFO Genkit initialized env=prod plugins=[dmr] …" on
209 // stderr, which lands in the middle of the terminal's output and of the220 // stderr, which lands in the middle of the terminal's output and of the
added internal/engine/trace.go +167 -0
new file mode 100644
@@ -0,0 +1,167 @@
1+package engine
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "fmt"
7+ "io"
8+ "net/http"
9+ "os"
10+ "path/filepath"
11+ "strings"
12+ "sync"
13+ "sync/atomic"
14+ "time"
15+
16+ "mm/internal/ui"
17+
18+ "github.com/openai/openai-go/option"
19+)
20+
21+// The trace writes what goes to the model and what comes back, exactly as it
22+// travels: the request body once mm's own middlewares have added their fields
23+// (stream_options, return_progress, the reasoning switch of a summary), and
24+// the response body byte for byte — a streamed answer as the SSE lines the
25+// server sent, every chunk's JSON included. It is the last middleware, the
26+// one next to the network, so nothing is added after it has looked.
27+//
28+// One pair of files per model call, in trace.dir:
29+//
30+// 20261007-081502.123-bob-0001.request.json the request, indented
31+// 20261007-081502.123-bob-0001.response-200.sse a streamed response
32+// 20261007-081502.123-bob-0002.response-200.json a whole one, indented
33+// 20261007-081502.123-bob-0003.response-error.txt no response: why
34+//
35+// The agent's acp.name is in the name: an agent started by /ask or
36+// ask_agent inherits AGENT_TRACE and writes beside its asker. Headers are
37+// never written: the API key travels in them.
38+
39+// traceSeq numbers the model calls of the process.
40+var traceSeq atomic.Int64
41+
42+// traceWarned: a trace that cannot be written says so once, not per call.
43+var traceWarned sync.Once
44+
45+// traceMiddleware returns the openai-go middleware writing the trace files
46+// of agent into dir. A file that cannot be written costs the trace, never
47+// the call — but the first failure is reported on ui.Out (stderr in ACP
48+// mode, where an editor shows it in the agent's log).
49+func traceMiddleware(dir, agent string) option.Middleware {
50+ return func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) {
51+ where := traceDirOf(dir)
52+ base := filepath.Join(where, fmt.Sprintf("%s-%s-%04d", time.Now().Format("20060102-150405.000"), agent, traceSeq.Add(1)))
53+ if err := os.MkdirAll(where, 0o755); err != nil {
54+ traceWarned.Do(func() { fmt.Fprintf(ui.Out, "[trace: cannot write to %s: %v — nothing is traced]\n", where, err) })
55+ }
56+ if req.Body != nil {
57+ if body, err := io.ReadAll(req.Body); err == nil {
58+ _ = req.Body.Close()
59+ req.Body = io.NopCloser(bytes.NewReader(body))
60+ req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil }
61+ _ = os.WriteFile(base+".request.json", indented(body), 0o644)
62+ }
63+ }
64+ resp, err := next(req)
65+ if err != nil {
66+ _ = os.WriteFile(base+".response-error.txt", []byte(req.Method+" "+req.URL.String()+"\n"+err.Error()+"\n"), 0o644)
67+ return resp, err
68+ }
69+ ext := ".json"
70+ if strings.Contains(resp.Header.Get("Content-Type"), "event-stream") {
71+ ext = ".sse"
72+ }
73+ resp.Body = &traceBody{body: resp.Body, path: fmt.Sprintf("%s.response-%d%s", base, resp.StatusCode, ext), stream: ext == ".sse"}
74+ return resp, nil
75+ }
76+}
77+
78+// traceDirOf is where this call's files go: an absolute dir as it is; a
79+// relative one in the working directory of the turn in progress — the
80+// session's cwd in ACP, like sessions.dir — else in the directory mm runs
81+// in. An editor may launch mm anywhere; the session says which project.
82+func traceDirOf(dir string) string {
83+ if !filepath.IsAbs(dir) {
84+ dir = ui.Resolve(dir)
85+ }
86+ if abs, err := filepath.Abs(dir); err == nil {
87+ return abs
88+ }
89+ return dir
90+}
91+
92+// traceBody copies a response body to its trace file as the client reads
93+// it. A stream is written as it arrives — a call that hangs or is cancelled
94+// still leaves what came; a whole JSON body is kept and written indented
95+// when the client is done with it.
96+type traceBody struct {
97+ body io.ReadCloser
98+ path string
99+ stream bool
100+
101+ mu sync.Mutex
102+ file *os.File // stream: open while reading
103+ buf bytes.Buffer // whole body
104+ done bool
105+}
106+
107+func (t *traceBody) Read(p []byte) (int, error) {
108+ n, err := t.body.Read(p)
109+ if n > 0 {
110+ t.keep(p[:n])
111+ }
112+ if err == io.EOF {
113+ t.finish()
114+ }
115+ return n, err
116+}
117+
118+func (t *traceBody) Close() error {
119+ t.finish()
120+ return t.body.Close()
121+}
122+
123+// keep records one piece of the body.
124+func (t *traceBody) keep(b []byte) {
125+ t.mu.Lock()
126+ defer t.mu.Unlock()
127+ if !t.stream {
128+ t.buf.Write(b)
129+ return
130+ }
131+ if t.file == nil && !t.done {
132+ t.file, _ = os.Create(t.path)
133+ }
134+ if t.file != nil {
135+ _, _ = t.file.Write(b)
136+ }
137+}
138+
139+// finish closes the stream's file, or writes the whole body. Once.
140+func (t *traceBody) finish() {
141+ t.mu.Lock()
142+ defer t.mu.Unlock()
143+ if t.done {
144+ return
145+ }
146+ t.done = true
147+ if t.stream {
148+ if t.file == nil { // nothing came: still say so
149+ t.file, _ = os.Create(t.path)
150+ }
151+ if t.file != nil {
152+ _ = t.file.Close()
153+ }
154+ return
155+ }
156+ _ = os.WriteFile(t.path, indented(t.buf.Bytes()), 0o644)
157+}
158+
159+// indented is body as indented JSON, or body unchanged when it is not JSON.
160+func indented(body []byte) []byte {
161+ var out bytes.Buffer
162+ if json.Indent(&out, body, "", " ") != nil {
163+ return body
164+ }
165+ out.WriteByte('\n')
166+ return out.Bytes()
167+}
new file mode 100644
@@ -0,0 +1,167 @@
1+package engine
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "fmt"
7+ "io"
8+ "net/http"
9+ "os"
10+ "path/filepath"
11+ "strings"
12+ "sync"
13+ "sync/atomic"
14+ "time"
15+
16+ "mm/internal/ui"
17+
18+ "github.com/openai/openai-go/option"
19+)
20+
21+// The trace writes what goes to the model and what comes back, exactly as it
22+// travels: the request body once mm's own middlewares have added their fields
23+// (stream_options, return_progress, the reasoning switch of a summary), and
24+// the response body byte for byte — a streamed answer as the SSE lines the
25+// server sent, every chunk's JSON included. It is the last middleware, the
26+// one next to the network, so nothing is added after it has looked.
27+//
28+// One pair of files per model call, in trace.dir:
29+//
30+// 20261007-081502.123-bob-0001.request.json the request, indented
31+// 20261007-081502.123-bob-0001.response-200.sse a streamed response
32+// 20261007-081502.123-bob-0002.response-200.json a whole one, indented
33+// 20261007-081502.123-bob-0003.response-error.txt no response: why
34+//
35+// The agent's acp.name is in the name: an agent started by /ask or
36+// ask_agent inherits AGENT_TRACE and writes beside its asker. Headers are
37+// never written: the API key travels in them.
38+
39+// traceSeq numbers the model calls of the process.
40+var traceSeq atomic.Int64
41+
42+// traceWarned: a trace that cannot be written says so once, not per call.
43+var traceWarned sync.Once
44+
45+// traceMiddleware returns the openai-go middleware writing the trace files
46+// of agent into dir. A file that cannot be written costs the trace, never
47+// the call — but the first failure is reported on ui.Out (stderr in ACP
48+// mode, where an editor shows it in the agent's log).
49+func traceMiddleware(dir, agent string) option.Middleware {
50+ return func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) {
51+ where := traceDirOf(dir)
52+ base := filepath.Join(where, fmt.Sprintf("%s-%s-%04d", time.Now().Format("20060102-150405.000"), agent, traceSeq.Add(1)))
53+ if err := os.MkdirAll(where, 0o755); err != nil {
54+ traceWarned.Do(func() { fmt.Fprintf(ui.Out, "[trace: cannot write to %s: %v — nothing is traced]\n", where, err) })
55+ }
56+ if req.Body != nil {
57+ if body, err := io.ReadAll(req.Body); err == nil {
58+ _ = req.Body.Close()
59+ req.Body = io.NopCloser(bytes.NewReader(body))
60+ req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil }
61+ _ = os.WriteFile(base+".request.json", indented(body), 0o644)
62+ }
63+ }
64+ resp, err := next(req)
65+ if err != nil {
66+ _ = os.WriteFile(base+".response-error.txt", []byte(req.Method+" "+req.URL.String()+"\n"+err.Error()+"\n"), 0o644)
67+ return resp, err
68+ }
69+ ext := ".json"
70+ if strings.Contains(resp.Header.Get("Content-Type"), "event-stream") {
71+ ext = ".sse"
72+ }
73+ resp.Body = &traceBody{body: resp.Body, path: fmt.Sprintf("%s.response-%d%s", base, resp.StatusCode, ext), stream: ext == ".sse"}
74+ return resp, nil
75+ }
76+}
77+
78+// traceDirOf is where this call's files go: an absolute dir as it is; a
79+// relative one in the working directory of the turn in progress — the
80+// session's cwd in ACP, like sessions.dir — else in the directory mm runs
81+// in. An editor may launch mm anywhere; the session says which project.
82+func traceDirOf(dir string) string {
83+ if !filepath.IsAbs(dir) {
84+ dir = ui.Resolve(dir)
85+ }
86+ if abs, err := filepath.Abs(dir); err == nil {
87+ return abs
88+ }
89+ return dir
90+}
91+
92+// traceBody copies a response body to its trace file as the client reads
93+// it. A stream is written as it arrives — a call that hangs or is cancelled
94+// still leaves what came; a whole JSON body is kept and written indented
95+// when the client is done with it.
96+type traceBody struct {
97+ body io.ReadCloser
98+ path string
99+ stream bool
100+
101+ mu sync.Mutex
102+ file *os.File // stream: open while reading
103+ buf bytes.Buffer // whole body
104+ done bool
105+}
106+
107+func (t *traceBody) Read(p []byte) (int, error) {
108+ n, err := t.body.Read(p)
109+ if n > 0 {
110+ t.keep(p[:n])
111+ }
112+ if err == io.EOF {
113+ t.finish()
114+ }
115+ return n, err
116+}
117+
118+func (t *traceBody) Close() error {
119+ t.finish()
120+ return t.body.Close()
121+}
122+
123+// keep records one piece of the body.
124+func (t *traceBody) keep(b []byte) {
125+ t.mu.Lock()
126+ defer t.mu.Unlock()
127+ if !t.stream {
128+ t.buf.Write(b)
129+ return
130+ }
131+ if t.file == nil && !t.done {
132+ t.file, _ = os.Create(t.path)
133+ }
134+ if t.file != nil {
135+ _, _ = t.file.Write(b)
136+ }
137+}
138+
139+// finish closes the stream's file, or writes the whole body. Once.
140+func (t *traceBody) finish() {
141+ t.mu.Lock()
142+ defer t.mu.Unlock()
143+ if t.done {
144+ return
145+ }
146+ t.done = true
147+ if t.stream {
148+ if t.file == nil { // nothing came: still say so
149+ t.file, _ = os.Create(t.path)
150+ }
151+ if t.file != nil {
152+ _ = t.file.Close()
153+ }
154+ return
155+ }
156+ _ = os.WriteFile(t.path, indented(t.buf.Bytes()), 0o644)
157+}
158+
159+// indented is body as indented JSON, or body unchanged when it is not JSON.
160+func indented(body []byte) []byte {
161+ var out bytes.Buffer
162+ if json.Indent(&out, body, "", " ") != nil {
163+ return body
164+ }
165+ out.WriteByte('\n')
166+ return out.Bytes()
167+}
added internal/engine/trace_test.go +182 -0
new file mode 100644
@@ -0,0 +1,182 @@
1+package engine
2+
3+import (
4+ "bytes"
5+ "errors"
6+ "io"
7+ "net/http"
8+ "net/http/httptest"
9+ "os"
10+ "path/filepath"
11+ "strings"
12+ "sync"
13+ "testing"
14+
15+ "mm/internal/ui"
16+)
17+
18+// traceFiles returns the trace dir's files, name -> content.
19+func traceFiles(t *testing.T, dir string) map[string]string {
20+ t.Helper()
21+ entries, err := os.ReadDir(dir)
22+ if err != nil {
23+ t.Fatal(err)
24+ }
25+ files := map[string]string{}
26+ for _, e := range entries {
27+ b, _ := os.ReadFile(filepath.Join(dir, e.Name()))
28+ files[e.Name()] = string(b)
29+ }
30+ return files
31+}
32+
33+// traced sends body through the trace middleware to srv and reads the whole
34+// response, as the SDK would.
35+func traced(t *testing.T, dir, url, body string) {
36+ t.Helper()
37+ req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(body))
38+ req.Header.Set("Authorization", "Bearer sk-secret")
39+ resp, err := traceMiddleware(dir, "bob")(req, http.DefaultClient.Do)
40+ if err != nil {
41+ t.Fatal(err)
42+ }
43+ _, _ = io.ReadAll(resp.Body)
44+ _ = resp.Body.Close()
45+}
46+
47+// A streamed call: the request as sent (indented), the SSE as received,
48+// named after the agent and the status; the key is nowhere.
49+func TestTraceStreamedCall(t *testing.T) {
50+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51+ got, _ := io.ReadAll(r.Body)
52+ if string(got) != `{"model":"m","stream":true}` {
53+ t.Errorf("the server got %q: the trace changed the request", got)
54+ }
55+ w.Header().Set("Content-Type", "text/event-stream")
56+ _, _ = io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"hi\"}}]}\n\ndata: [DONE]\n\n")
57+ }))
58+ t.Cleanup(srv.Close)
59+ dir := t.TempDir()
60+
61+ traced(t, dir, srv.URL, `{"model":"m","stream":true}`)
62+
63+ files := traceFiles(t, dir)
64+ var request, response string
65+ for name, content := range files {
66+ switch {
67+ case strings.Contains(name, "-bob-") && strings.HasSuffix(name, ".request.json"):
68+ request = content
69+ case strings.Contains(name, "-bob-") && strings.HasSuffix(name, ".response-200.sse"):
70+ response = content
71+ }
72+ }
73+ if request != "{\n \"model\": \"m\",\n \"stream\": true\n}\n" {
74+ t.Errorf("request file %q (files %v)", request, keys(files))
75+ }
76+ if !strings.Contains(response, `data: {"choices":[{"delta":{"content":"hi"}}]}`) || !strings.Contains(response, "[DONE]") {
77+ t.Errorf("response file %q (files %v)", response, keys(files))
78+ }
79+ for name, content := range files {
80+ if strings.Contains(content, "sk-secret") {
81+ t.Errorf("%s carries the API key", name)
82+ }
83+ }
84+}
85+
86+// A whole JSON answer is indented; an error status is in the file's name.
87+func TestTraceJSONErrorResponse(t *testing.T) {
88+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
89+ w.Header().Set("Content-Type", "application/json")
90+ w.WriteHeader(http.StatusBadRequest)
91+ _, _ = io.WriteString(w, `{"error":{"message":"exceeds the available context size"}}`)
92+ }))
93+ t.Cleanup(srv.Close)
94+ dir := t.TempDir()
95+
96+ traced(t, dir, srv.URL, `{"model":"m"}`)
97+
98+ for name, content := range traceFiles(t, dir) {
99+ if strings.HasSuffix(name, ".response-400.json") {
100+ if !strings.Contains(content, "\n \"error\": {") {
101+ t.Errorf("not indented: %q", content)
102+ }
103+ return
104+ }
105+ }
106+ t.Errorf("no .response-400.json among %v", keys(traceFiles(t, dir)))
107+}
108+
109+// No response at all: the reason is written, and the error goes on as is.
110+func TestTraceTransportError(t *testing.T) {
111+ dir := t.TempDir()
112+ req, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:1/v1/chat/completions", strings.NewReader(`{}`))
113+ boom := errors.New("connection refused")
114+ _, err := traceMiddleware(dir, "bob")(req, func(*http.Request) (*http.Response, error) { return nil, boom })
115+ if !errors.Is(err, boom) {
116+ t.Fatalf("err = %v", err)
117+ }
118+ for name, content := range traceFiles(t, dir) {
119+ if strings.HasSuffix(name, ".response-error.txt") && strings.Contains(content, "connection refused") {
120+ return
121+ }
122+ }
123+ t.Errorf("no .response-error.txt among %v", keys(traceFiles(t, dir)))
124+}
125+
126+func keys(m map[string]string) []string {
127+ var out []string
128+ for k := range m {
129+ out = append(out, k)
130+ }
131+ return out
132+}
133+
134+// sessionSink is the active sink of an ACP session: only its WorkDir matters.
135+type sessionSink struct {
136+ ui.Sink
137+ dir string
138+}
139+
140+func (s sessionSink) WorkDir() string { return s.dir }
141+
142+// A relative trace.dir lands in the session's cwd — where an editor's
143+// project is — not in the directory the editor launched mm from.
144+func TestTraceFollowsTheSessionDirectory(t *testing.T) {
145+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
146+ _, _ = io.WriteString(w, `{}`)
147+ }))
148+ t.Cleanup(srv.Close)
149+ project := t.TempDir()
150+ ui.SetActive(sessionSink{dir: project})
151+ t.Cleanup(func() { ui.SetActive(nil) })
152+
153+ traced(t, ".mm/traces", srv.URL, `{"model":"m"}`)
154+
155+ if files := traceFiles(t, filepath.Join(project, ".mm", "traces")); len(files) != 2 {
156+ t.Errorf("files in the session's .mm/traces: %v", keys(files))
157+ }
158+}
159+
160+// A directory that cannot be created is said once, and the call goes on.
161+func TestTraceSaysWhenItCannotWrite(t *testing.T) {
162+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
163+ _, _ = io.WriteString(w, `{}`)
164+ }))
165+ t.Cleanup(srv.Close)
166+ blocker := filepath.Join(t.TempDir(), "a-file")
167+ if err := os.WriteFile(blocker, nil, 0o644); err != nil {
168+ t.Fatal(err)
169+ }
170+ var out bytes.Buffer
171+ oldOut := ui.Out
172+ ui.Out = &out
173+ traceWarned = sync.Once{}
174+ t.Cleanup(func() { ui.Out = oldOut; traceWarned = sync.Once{} })
175+
176+ traced(t, filepath.Join(blocker, "traces"), srv.URL, `{}`)
177+ traced(t, filepath.Join(blocker, "traces"), srv.URL, `{}`)
178+
179+ if got := out.String(); strings.Count(got, "[trace: cannot write to") != 1 {
180+ t.Errorf("warnings: %q", got)
181+ }
182+}
new file mode 100644
@@ -0,0 +1,182 @@
1+package engine
2+
3+import (
4+ "bytes"
5+ "errors"
6+ "io"
7+ "net/http"
8+ "net/http/httptest"
9+ "os"
10+ "path/filepath"
11+ "strings"
12+ "sync"
13+ "testing"
14+
15+ "mm/internal/ui"
16+)
17+
18+// traceFiles returns the trace dir's files, name -> content.
19+func traceFiles(t *testing.T, dir string) map[string]string {
20+ t.Helper()
21+ entries, err := os.ReadDir(dir)
22+ if err != nil {
23+ t.Fatal(err)
24+ }
25+ files := map[string]string{}
26+ for _, e := range entries {
27+ b, _ := os.ReadFile(filepath.Join(dir, e.Name()))
28+ files[e.Name()] = string(b)
29+ }
30+ return files
31+}
32+
33+// traced sends body through the trace middleware to srv and reads the whole
34+// response, as the SDK would.
35+func traced(t *testing.T, dir, url, body string) {
36+ t.Helper()
37+ req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(body))
38+ req.Header.Set("Authorization", "Bearer sk-secret")
39+ resp, err := traceMiddleware(dir, "bob")(req, http.DefaultClient.Do)
40+ if err != nil {
41+ t.Fatal(err)
42+ }
43+ _, _ = io.ReadAll(resp.Body)
44+ _ = resp.Body.Close()
45+}
46+
47+// A streamed call: the request as sent (indented), the SSE as received,
48+// named after the agent and the status; the key is nowhere.
49+func TestTraceStreamedCall(t *testing.T) {
50+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51+ got, _ := io.ReadAll(r.Body)
52+ if string(got) != `{"model":"m","stream":true}` {
53+ t.Errorf("the server got %q: the trace changed the request", got)
54+ }
55+ w.Header().Set("Content-Type", "text/event-stream")
56+ _, _ = io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"hi\"}}]}\n\ndata: [DONE]\n\n")
57+ }))
58+ t.Cleanup(srv.Close)
59+ dir := t.TempDir()
60+
61+ traced(t, dir, srv.URL, `{"model":"m","stream":true}`)
62+
63+ files := traceFiles(t, dir)
64+ var request, response string
65+ for name, content := range files {
66+ switch {
67+ case strings.Contains(name, "-bob-") && strings.HasSuffix(name, ".request.json"):
68+ request = content
69+ case strings.Contains(name, "-bob-") && strings.HasSuffix(name, ".response-200.sse"):
70+ response = content
71+ }
72+ }
73+ if request != "{\n \"model\": \"m\",\n \"stream\": true\n}\n" {
74+ t.Errorf("request file %q (files %v)", request, keys(files))
75+ }
76+ if !strings.Contains(response, `data: {"choices":[{"delta":{"content":"hi"}}]}`) || !strings.Contains(response, "[DONE]") {
77+ t.Errorf("response file %q (files %v)", response, keys(files))
78+ }
79+ for name, content := range files {
80+ if strings.Contains(content, "sk-secret") {
81+ t.Errorf("%s carries the API key", name)
82+ }
83+ }
84+}
85+
86+// A whole JSON answer is indented; an error status is in the file's name.
87+func TestTraceJSONErrorResponse(t *testing.T) {
88+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
89+ w.Header().Set("Content-Type", "application/json")
90+ w.WriteHeader(http.StatusBadRequest)
91+ _, _ = io.WriteString(w, `{"error":{"message":"exceeds the available context size"}}`)
92+ }))
93+ t.Cleanup(srv.Close)
94+ dir := t.TempDir()
95+
96+ traced(t, dir, srv.URL, `{"model":"m"}`)
97+
98+ for name, content := range traceFiles(t, dir) {
99+ if strings.HasSuffix(name, ".response-400.json") {
100+ if !strings.Contains(content, "\n \"error\": {") {
101+ t.Errorf("not indented: %q", content)
102+ }
103+ return
104+ }
105+ }
106+ t.Errorf("no .response-400.json among %v", keys(traceFiles(t, dir)))
107+}
108+
109+// No response at all: the reason is written, and the error goes on as is.
110+func TestTraceTransportError(t *testing.T) {
111+ dir := t.TempDir()
112+ req, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:1/v1/chat/completions", strings.NewReader(`{}`))
113+ boom := errors.New("connection refused")
114+ _, err := traceMiddleware(dir, "bob")(req, func(*http.Request) (*http.Response, error) { return nil, boom })
115+ if !errors.Is(err, boom) {
116+ t.Fatalf("err = %v", err)
117+ }
118+ for name, content := range traceFiles(t, dir) {
119+ if strings.HasSuffix(name, ".response-error.txt") && strings.Contains(content, "connection refused") {
120+ return
121+ }
122+ }
123+ t.Errorf("no .response-error.txt among %v", keys(traceFiles(t, dir)))
124+}
125+
126+func keys(m map[string]string) []string {
127+ var out []string
128+ for k := range m {
129+ out = append(out, k)
130+ }
131+ return out
132+}
133+
134+// sessionSink is the active sink of an ACP session: only its WorkDir matters.
135+type sessionSink struct {
136+ ui.Sink
137+ dir string
138+}
139+
140+func (s sessionSink) WorkDir() string { return s.dir }
141+
142+// A relative trace.dir lands in the session's cwd — where an editor's
143+// project is — not in the directory the editor launched mm from.
144+func TestTraceFollowsTheSessionDirectory(t *testing.T) {
145+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
146+ _, _ = io.WriteString(w, `{}`)
147+ }))
148+ t.Cleanup(srv.Close)
149+ project := t.TempDir()
150+ ui.SetActive(sessionSink{dir: project})
151+ t.Cleanup(func() { ui.SetActive(nil) })
152+
153+ traced(t, ".mm/traces", srv.URL, `{"model":"m"}`)
154+
155+ if files := traceFiles(t, filepath.Join(project, ".mm", "traces")); len(files) != 2 {
156+ t.Errorf("files in the session's .mm/traces: %v", keys(files))
157+ }
158+}
159+
160+// A directory that cannot be created is said once, and the call goes on.
161+func TestTraceSaysWhenItCannotWrite(t *testing.T) {
162+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
163+ _, _ = io.WriteString(w, `{}`)
164+ }))
165+ t.Cleanup(srv.Close)
166+ blocker := filepath.Join(t.TempDir(), "a-file")
167+ if err := os.WriteFile(blocker, nil, 0o644); err != nil {
168+ t.Fatal(err)
169+ }
170+ var out bytes.Buffer
171+ oldOut := ui.Out
172+ ui.Out = &out
173+ traceWarned = sync.Once{}
174+ t.Cleanup(func() { ui.Out = oldOut; traceWarned = sync.Once{} })
175+
176+ traced(t, filepath.Join(blocker, "traces"), srv.URL, `{}`)
177+ traced(t, filepath.Join(blocker, "traces"), srv.URL, `{}`)
178+
179+ if got := out.String(); strings.Count(got, "[trace: cannot write to") != 1 {
180+ t.Errorf("warnings: %q", got)
181+ }
182+}
modified main.go +24 -4
@@ -242,7 +242,7 @@ func main() {
242242 return
243243 }
244244
245- printBanner(ctx, e, path, set)
245+ printBanner(ctx, e, path, set, *acpMode)
246246
247247 // Three front ends, one wiring: everything above — config, engine, tools,
248248 // detector — is strictly identical whichever loop runs below.
@@ -481,7 +481,7 @@ func assembleMCP(e *engine.Engine, d *detector.LoopDetector, set *toolSet) {
481481 // banner. The probe runs before the banner so its warnings sit right under
482482 // the prompt they explain; it never stops the agent — on a demo machine the
483483 // server is often started AFTER the agent.
484-func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet) {
484+func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet, acpMode bool) {
485485 toolNames, skillCount := set.names, set.skillCount
486486 info := e.Probe(ctx)
487487 info.Warnings = append(info.Warnings, set.warnings...)
@@ -554,8 +554,8 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet
554554 // The version leads the line: it is the first thing anyone is asked for
555555 // when something behaves oddly, and the banner is the one output every
556556 // front end shows — the window keeps it as the conversation's first line.
557- say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s",
558- dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), off)
557+ say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s%s",
558+ dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), traceColumn(acpMode), off)
559559 }
560560
561561 // yoloColumn says in the banner whether the tools run unasked — the one
@@ -567,6 +567,26 @@ func yoloColumn() string {
567567 return "yolo: off (tools ask)"
568568 }
569569
570+// traceColumn says in the banner that every request and response is being
571+// written to disk — prompts included — and where. In ACP mode a relative
572+// dir is in each session's cwd, which no session has given yet.
573+func traceColumn(acpMode bool) string {
574+ if !config.Cfg.Trace.Enabled {
575+ return ""
576+ }
577+ dir := config.Cfg.Trace.Dir
578+ if filepath.IsAbs(dir) {
579+ return " | trace: " + dir
580+ }
581+ if acpMode {
582+ return " | trace: <session cwd>/" + dir
583+ }
584+ if abs, err := filepath.Abs(dir); err == nil {
585+ dir = abs
586+ }
587+ return " | trace: " + dir
588+}
589+
570590 // commandNames is the slash commands' names, for the shadowing check.
571591 func commandNames() []string {
572592 var out []string
@@ -242,7 +242,7 @@ func main() {
242 return242 return
243 }243 }
244 244
245- printBanner(ctx, e, path, set)245+ printBanner(ctx, e, path, set, *acpMode)
246 246
247 // Three front ends, one wiring: everything above — config, engine, tools,247 // Three front ends, one wiring: everything above — config, engine, tools,
248 // detector — is strictly identical whichever loop runs below.248 // detector — is strictly identical whichever loop runs below.
@@ -481,7 +481,7 @@ func assembleMCP(e *engine.Engine, d *detector.LoopDetector, set *toolSet) {
481 // banner. The probe runs before the banner so its warnings sit right under481 // banner. The probe runs before the banner so its warnings sit right under
482 // the prompt they explain; it never stops the agent — on a demo machine the482 // the prompt they explain; it never stops the agent — on a demo machine the
483 // server is often started AFTER the agent.483 // server is often started AFTER the agent.
484-func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet) {484+func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet, acpMode bool) {
485 toolNames, skillCount := set.names, set.skillCount485 toolNames, skillCount := set.names, set.skillCount
486 info := e.Probe(ctx)486 info := e.Probe(ctx)
487 info.Warnings = append(info.Warnings, set.warnings...)487 info.Warnings = append(info.Warnings, set.warnings...)
@@ -554,8 +554,8 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet
554 // The version leads the line: it is the first thing anyone is asked for554 // The version leads the line: it is the first thing anyone is asked for
555 // when something behaves oddly, and the banner is the one output every555 // when something behaves oddly, and the banner is the one output every
556 // front end shows — the window keeps it as the conversation's first line.556 // front end shows — the window keeps it as the conversation's first line.
557- say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s",557+ say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s%s",
558- dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), off)558+ dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), traceColumn(acpMode), off)
559 }559 }
560 560
561 // yoloColumn says in the banner whether the tools run unasked — the one561 // yoloColumn says in the banner whether the tools run unasked — the one
@@ -567,6 +567,26 @@ func yoloColumn() string {
567 return "yolo: off (tools ask)"567 return "yolo: off (tools ask)"
568 }568 }
569 569
570+// traceColumn says in the banner that every request and response is being
571+// written to disk — prompts included — and where. In ACP mode a relative
572+// dir is in each session's cwd, which no session has given yet.
573+func traceColumn(acpMode bool) string {
574+ if !config.Cfg.Trace.Enabled {
575+ return ""
576+ }
577+ dir := config.Cfg.Trace.Dir
578+ if filepath.IsAbs(dir) {
579+ return " | trace: " + dir
580+ }
581+ if acpMode {
582+ return " | trace: <session cwd>/" + dir
583+ }
584+ if abs, err := filepath.Abs(dir); err == nil {
585+ dir = abs
586+ }
587+ return " | trace: " + dir
588+}
589+
570 // commandNames is the slash commands' names, for the shadowing check.590 // commandNames is the slash commands' names, for the shadowing check.
571 func commandNames() []string {591 func commandNames() []string {
572 var out []string592 var out []string