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

Structured output #27

Merged@k33g wants to merge feature/structured-output into main
Merged as 3e8d3b0ea692.
37 files changed
added .tickets/issues/0080-structured-output-from-go-type-declarations.yaml +138 -0
new file mode 100644
@@ -0,0 +1,138 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 80
3+title: Structured output from Go type declarations in the config
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-08T10:00:00.000Z
9+updatedAt: 2026-10-08T16:00:00.000Z
10+labels:
11+ - enhancement
12+epic: Models
13+related:
14+ - 54
15+ - 55
16+body: |
17+ ## Goal
18+
19+ Make the model answer with JSON that follows a structure written in the agent's YAML file, as Go type declarations:
20+
21+ ```yaml
22+ structuredOutput: |
23+ type Language struct {
24+ Name string `json:"name"` // language name, in English
25+ }
26+ type Country struct {
27+ Name string `json:"name"`
28+ Languages []Language `json:"languages"`
29+ }
30+ var Countries = []Country{}
31+ ```
32+
33+ `mm countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" | jq '.[].name'`
34+
35+ ## How the string becomes a structure
36+
37+ No Go type is created at runtime: Go cannot compile code while running. mm reads the text as SOURCE code:
38+
39+ 1. `go/parser` (standard library) parses `package schema` + the section into a syntax tree.
40+ 2. mm walks the tree: every `type X struct` is a definition, `var X = T{}` names the root, every field gives a key (its `json` tag, or the Go name when there is no tag), a type, and a description (its `//` comment).
41+ 3. That becomes a JSON Schema, given to Genkit with `ai.WithOutputSchema`. The OpenAI-compatible plugin sends it as `response_format: {type: json_schema, strict: true}`, so the server constrains generation (a grammar in llama.cpp), and Genkit checks the answer against the schema before handing it back.
42+
43+ Type mapping: `string` → string, `int*`/`uint*` → integer, `float*` → number, `bool` → boolean, `[]T` → array, `map[string]T` → object with additionalProperties, a declared struct → `$defs` + `$ref`, `*T` or `omitempty` → optional (required but nullable, which strict mode accepts). Anything else (interface, chan, func, generics, undefined type) is a config error with its line:col, at start-up.
44+
45+ ## Decisions
46+
47+ - Keys come from `json` tags; without a tag, the Go name as is (like encoding/json).
48+ - An agent that declares tools cannot have structuredOutput: config error. llama.cpp refuses a grammar together with tools, and a constrained answer leaves no room for a tool call.
49+ - Rejected: `reflect.StructOf` (no named or recursive types, no descriptions), yaegi (an interpreter as a dependency to read declarations), a YAML shape notation (a mini-language to invent, no descriptions).
50+ - The root may be an array: llama.cpp accepts it. OpenAI's hosted API demands an object root: use a struct there.
51+
52+ ## Found while building it (2026-10-08)
53+
54+ - `sampling.response_format` is rejected by Genkit's compat_oai plugin; the road is `ai.WithOutputSchema`, native only when the model declares `Constrained: ai.ConstrainedSupportNoTools`. Genkit validates the answer itself: mm has no validator of its own.
55+ - The schema alone does not reach the model (grammar = syntax only, Genkit adds no prompt text when the constraint is native): the declarations are appended to the system prompt. Before that, Qwen3.5-4B wrote the country's name in `capital` every time.
56+ - Properties are sent in declaration order (`structured.Properties`), but llama.cpp b11429 reorders them alphabetically in its grammar.
57+ - A small model can repeat list items until max_tokens: mm names that case ("cut off by max_tokens"); the demo bounds its lists in the comments.
58+ - Quality gate (task 6) not measured: qlty is not installed in the sandbox of 2026-10-08.
59+tasks:
60+ - id: 1
61+ title: "Package internal/structured: parse the Go declarations (go/parser) into a JSON Schema; errors with line:col"
62+ state: closed
63+ priority: none
64+ author:
65+ name: k33g
66+ email: ph.charriere@gmail.com
67+ createdAt: 2026-10-08T10:00:00.000Z
68+ updatedAt: 2026-10-08T11:00:00.000Z
69+ closedAt: 2026-10-08T11:00:00.000Z
70+ - id: 2
71+ title: "Config: structuredOutput field, compiled when the config loads; invalid declarations = config error"
72+ state: closed
73+ priority: none
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-10-08T10:00:00.000Z
78+ updatedAt: 2026-10-08T16:00:00.000Z
79+ closedAt: 2026-10-08T16:00:00.000Z
80+ - id: 3
81+ title: "Engine: model declares constrained output (no tools); Generate passes ai.WithOutputSchema when structuredOutput is set"
82+ state: closed
83+ priority: none
84+ author:
85+ name: k33g
86+ email: ph.charriere@gmail.com
87+ createdAt: 2026-10-08T10:00:00.000Z
88+ updatedAt: 2026-10-08T16:00:00.000Z
89+ closedAt: 2026-10-08T16:00:00.000Z
90+ - id: 4
91+ title: "Refuse structuredOutput when the agent declares any tool (config error listing the tools)"
92+ state: closed
93+ priority: none
94+ author:
95+ name: k33g
96+ email: ph.charriere@gmail.com
97+ createdAt: 2026-10-08T10:00:00.000Z
98+ updatedAt: 2026-10-08T16:00:00.000Z
99+ closedAt: 2026-10-08T16:00:00.000Z
100+ - id: 5
101+ title: "Live check on a local server (llama.cpp / llmman): -p prints the JSON, jq reads it"
102+ state: closed
103+ priority: none
104+ author:
105+ name: k33g
106+ email: ph.charriere@gmail.com
107+ createdAt: 2026-10-08T10:00:00.000Z
108+ updatedAt: 2026-10-08T16:00:00.000Z
109+ closedAt: 2026-10-08T16:00:00.000Z
110+ - id: 6
111+ title: "Quality gate (quality skill)"
112+ state: open
113+ priority: none
114+ author:
115+ name: k33g
116+ email: ph.charriere@gmail.com
117+ createdAt: 2026-10-08T10:00:00.000Z
118+ - id: 7
119+ title: "Docs EN+FR (how-to + reference/configuration) and .memory update"
120+ state: closed
121+ priority: none
122+ author:
123+ name: k33g
124+ email: ph.charriere@gmail.com
125+ createdAt: 2026-10-08T10:00:00.000Z
126+ updatedAt: 2026-10-08T16:00:00.000Z
127+ closedAt: 2026-10-08T16:00:00.000Z
128+ - id: 8
129+ title: "Demo folder demo-structured-output: a simple JSON agent (countries), a second one (triage), a coordinator using both through ask_agent, READMEs FR+EN"
130+ state: closed
131+ priority: none
132+ author:
133+ name: k33g
134+ email: ph.charriere@gmail.com
135+ createdAt: 2026-10-08T14:00:00.000Z
136+ updatedAt: 2026-10-08T16:00:00.000Z
137+ closedAt: 2026-10-08T16:00:00.000Z
138+comments: []
new file mode 100644
@@ -0,0 +1,138 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 80
3+title: Structured output from Go type declarations in the config
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-08T10:00:00.000Z
9+updatedAt: 2026-10-08T16:00:00.000Z
10+labels:
11+ - enhancement
12+epic: Models
13+related:
14+ - 54
15+ - 55
16+body: |
17+ ## Goal
18+
19+ Make the model answer with JSON that follows a structure written in the agent's YAML file, as Go type declarations:
20+
21+ ```yaml
22+ structuredOutput: |
23+ type Language struct {
24+ Name string `json:"name"` // language name, in English
25+ }
26+ type Country struct {
27+ Name string `json:"name"`
28+ Languages []Language `json:"languages"`
29+ }
30+ var Countries = []Country{}
31+ ```
32+
33+ `mm countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" | jq '.[].name'`
34+
35+ ## How the string becomes a structure
36+
37+ No Go type is created at runtime: Go cannot compile code while running. mm reads the text as SOURCE code:
38+
39+ 1. `go/parser` (standard library) parses `package schema` + the section into a syntax tree.
40+ 2. mm walks the tree: every `type X struct` is a definition, `var X = T{}` names the root, every field gives a key (its `json` tag, or the Go name when there is no tag), a type, and a description (its `//` comment).
41+ 3. That becomes a JSON Schema, given to Genkit with `ai.WithOutputSchema`. The OpenAI-compatible plugin sends it as `response_format: {type: json_schema, strict: true}`, so the server constrains generation (a grammar in llama.cpp), and Genkit checks the answer against the schema before handing it back.
42+
43+ Type mapping: `string` → string, `int*`/`uint*` → integer, `float*` → number, `bool` → boolean, `[]T` → array, `map[string]T` → object with additionalProperties, a declared struct → `$defs` + `$ref`, `*T` or `omitempty` → optional (required but nullable, which strict mode accepts). Anything else (interface, chan, func, generics, undefined type) is a config error with its line:col, at start-up.
44+
45+ ## Decisions
46+
47+ - Keys come from `json` tags; without a tag, the Go name as is (like encoding/json).
48+ - An agent that declares tools cannot have structuredOutput: config error. llama.cpp refuses a grammar together with tools, and a constrained answer leaves no room for a tool call.
49+ - Rejected: `reflect.StructOf` (no named or recursive types, no descriptions), yaegi (an interpreter as a dependency to read declarations), a YAML shape notation (a mini-language to invent, no descriptions).
50+ - The root may be an array: llama.cpp accepts it. OpenAI's hosted API demands an object root: use a struct there.
51+
52+ ## Found while building it (2026-10-08)
53+
54+ - `sampling.response_format` is rejected by Genkit's compat_oai plugin; the road is `ai.WithOutputSchema`, native only when the model declares `Constrained: ai.ConstrainedSupportNoTools`. Genkit validates the answer itself: mm has no validator of its own.
55+ - The schema alone does not reach the model (grammar = syntax only, Genkit adds no prompt text when the constraint is native): the declarations are appended to the system prompt. Before that, Qwen3.5-4B wrote the country's name in `capital` every time.
56+ - Properties are sent in declaration order (`structured.Properties`), but llama.cpp b11429 reorders them alphabetically in its grammar.
57+ - A small model can repeat list items until max_tokens: mm names that case ("cut off by max_tokens"); the demo bounds its lists in the comments.
58+ - Quality gate (task 6) not measured: qlty is not installed in the sandbox of 2026-10-08.
59+tasks:
60+ - id: 1
61+ title: "Package internal/structured: parse the Go declarations (go/parser) into a JSON Schema; errors with line:col"
62+ state: closed
63+ priority: none
64+ author:
65+ name: k33g
66+ email: ph.charriere@gmail.com
67+ createdAt: 2026-10-08T10:00:00.000Z
68+ updatedAt: 2026-10-08T11:00:00.000Z
69+ closedAt: 2026-10-08T11:00:00.000Z
70+ - id: 2
71+ title: "Config: structuredOutput field, compiled when the config loads; invalid declarations = config error"
72+ state: closed
73+ priority: none
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-10-08T10:00:00.000Z
78+ updatedAt: 2026-10-08T16:00:00.000Z
79+ closedAt: 2026-10-08T16:00:00.000Z
80+ - id: 3
81+ title: "Engine: model declares constrained output (no tools); Generate passes ai.WithOutputSchema when structuredOutput is set"
82+ state: closed
83+ priority: none
84+ author:
85+ name: k33g
86+ email: ph.charriere@gmail.com
87+ createdAt: 2026-10-08T10:00:00.000Z
88+ updatedAt: 2026-10-08T16:00:00.000Z
89+ closedAt: 2026-10-08T16:00:00.000Z
90+ - id: 4
91+ title: "Refuse structuredOutput when the agent declares any tool (config error listing the tools)"
92+ state: closed
93+ priority: none
94+ author:
95+ name: k33g
96+ email: ph.charriere@gmail.com
97+ createdAt: 2026-10-08T10:00:00.000Z
98+ updatedAt: 2026-10-08T16:00:00.000Z
99+ closedAt: 2026-10-08T16:00:00.000Z
100+ - id: 5
101+ title: "Live check on a local server (llama.cpp / llmman): -p prints the JSON, jq reads it"
102+ state: closed
103+ priority: none
104+ author:
105+ name: k33g
106+ email: ph.charriere@gmail.com
107+ createdAt: 2026-10-08T10:00:00.000Z
108+ updatedAt: 2026-10-08T16:00:00.000Z
109+ closedAt: 2026-10-08T16:00:00.000Z
110+ - id: 6
111+ title: "Quality gate (quality skill)"
112+ state: open
113+ priority: none
114+ author:
115+ name: k33g
116+ email: ph.charriere@gmail.com
117+ createdAt: 2026-10-08T10:00:00.000Z
118+ - id: 7
119+ title: "Docs EN+FR (how-to + reference/configuration) and .memory update"
120+ state: closed
121+ priority: none
122+ author:
123+ name: k33g
124+ email: ph.charriere@gmail.com
125+ createdAt: 2026-10-08T10:00:00.000Z
126+ updatedAt: 2026-10-08T16:00:00.000Z
127+ closedAt: 2026-10-08T16:00:00.000Z
128+ - id: 8
129+ title: "Demo folder demo-structured-output: a simple JSON agent (countries), a second one (triage), a coordinator using both through ask_agent, READMEs FR+EN"
130+ state: closed
131+ priority: none
132+ author:
133+ name: k33g
134+ email: ph.charriere@gmail.com
135+ createdAt: 2026-10-08T14:00:00.000Z
136+ updatedAt: 2026-10-08T16:00:00.000Z
137+ closedAt: 2026-10-08T16:00:00.000Z
138+comments: []
added demo-structured-output/README.en.md +159 -0
new file mode 100644
@@ -0,0 +1,159 @@
1+# mini-me demo: JSON answers (`structuredOutput`)
2+
3+This directory shows how to make a mini-me agent answer **only with JSON**, in a shape you declare as Go types in its YAML file, and then how another agent uses that JSON. The models are served by [llmman](https://github.com/llmmanorg/llmman). Version française : [README.md](README.md).
4+
5+## What is here
6+
7+| File | Role |
8+|---|---|
9+| `agent.countries.yaml` | **countries**: a simple JSON agent. A question about countries → an array of countries (name, capital, languages). Array root: `var Countries = []Country{}`. |
10+| `agent.triage.yaml` | **triage**: a second JSON agent. Bug reports → an object `{"reports": [...]}` (title, severity, component, summary, duplicate). Struct root: `var Answer = Triage{}`. |
11+| `agent.coordinator.yaml` | **Bob**, the coordinator: a normal agent with tools (`ask_agent`, `read_file`, `write_file`), who hands questions to countries and triage and works from their JSON. |
12+| `reports.txt` | Four bug reports for the multi-agent example (the 4th repeats the 1st). |
13+
14+All three agents use `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`.
15+
16+## Prerequisites
17+
18+1. llmman running (`llmman serve`, on `127.0.0.1:17434`; from a Docker Sandbox, mm falls back to `host.docker.internal:17434` by itself).
19+2. The model, pulled beforehand (otherwise the first question pulls it and waits):
20+
21+ ```bash
22+ llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
23+ ```
24+
25+3. `mm` built at the repository root (`go build -o mm .`). **Run it from this directory**: the `mmConfigs` paths are relative to `agent.coordinator.yaml`, and `reports.txt` is read from the current directory.
26+
27+ ```bash
28+ cd demo-structured-output
29+ ```
30+
31+4. `jq`, to read the JSON (optional).
32+
33+If `jq` answers `parse error: Invalid numeric literal at line 1, column 8`, `mm` did not give it JSON: run the command again without `| jq` to read the message. The usual case: the command runs outside this directory, and `agent.countries.yaml` is not found (`[config error: open agent.countries.yaml: no such file or directory ]`). Since the version that adds `structuredOutput`, these errors go to stderr and `jq` receives nothing.
34+
35+## How the shape of the answer is declared
36+
37+In `agent.countries.yaml`:
38+
39+```yaml
40+structuredOutput: |
41+ // A language spoken in the country.
42+ type Language struct {
43+ Name string `json:"name"` // the language's name, in English
44+ Official bool `json:"official"` // true when it has an official status at the national level
45+ }
46+
47+ type Country struct {
48+ Name string `json:"name"` // the country's common name, in English
49+ Capital *string `json:"capital"` // the capital CITY (not the country), in English; null when unknown
50+ Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
51+ }
52+
53+ // One entry per country named in the question, in the order asked.
54+ var Countries = []Country{}
55+```
56+
57+- Every `type` is a definition; the one `var` names the **root** of the answer.
58+- A field's key is its `json` tag (its Go name when it has none).
59+- A pointer (`*string`) or `omitempty` makes the value optional: it may be `null`.
60+- **The `//` comments are the instructions the model reads**: mm appends them to its system prompt. Write them as instructions, and bound the lists (`at most 4`) — otherwise a small model may repeat items until `max_tokens`.
61+
62+mm translates these declarations into a JSON Schema, the server constrains the generation to it, and the answer is checked before it is handed back.
63+
64+A JSON agent has **no tool at all** (`bashTool`, `editTools`, `planTool` set to `false`, no skills, no `mmConfigs`): a constrained answer leaves no room for a tool call, and mm refuses to start otherwise.
65+
66+## 1. A simple agent: one question, JSON back
67+
68+```bash
69+../mm agent.countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" | jq .
70+```
71+
72+With `-p`, stdout carries **only the JSON** (no 🤖, no statistics), and the exit status is 1 when the answer is missing or invalid. So it can be processed directly:
73+
74+```bash
75+../mm agent.countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" \
76+ | jq -c '.[] | {name, capital, languages: [.languages[].name]}'
77+```
78+
79+```
80+{"name":"France","capital":"Paris","languages":["French","Occitan","Breton","Catalan"]}
81+{"name":"Canada","capital":"Ottawa","languages":["English","French","Algonquin","Innu-aimun"]}
82+{"name":"Vietnam","capital":"Hanoi","languages":["Vietnamese","French","Chinese","English"]}
83+{"name":"United States","capital":"Washington, D.C.","languages":["English","Spanish","Chinese","Vietnamese"]}
84+```
85+
86+The minor languages may vary with the wording of the question; the name and the capital stay stable.
87+
88+The second agent, on the reports file:
89+
90+```bash
91+../mm agent.triage.yaml -p "$(cat reports.txt)" | jq -c '.reports[] | {severity, title, duplicate}'
92+```
93+
94+```
95+{"severity":"high","title":"Export freezes and closes on large projects","duplicate":null}
96+{"severity":"low","title":"Date format incorrect in Settings","duplicate":null}
97+{"severity":"low","title":"Typo on login page","duplicate":null}
98+{"severity":"high","title":"Export crashes on big projects","duplicate":1}
99+```
100+
101+In an interactive session (`../mm agent.countries.yaml`, or `../mm -tui agent.countries.yaml`), every answer is JSON, shown like any answer; the banner says `tools: none (structured output: JSON answers)`.
102+
103+## 2. Several agents: Bob uses the others' JSON
104+
105+Bob cannot have a `structuredOutput` himself: he has tools. He declares the two JSON agents under `mmConfigs` and calls them with `ask_agent`; their JSON comes back to him as the tool's result.
106+
107+```yaml
108+mmConfigs:
109+ countries:
110+ path: agent.countries.yaml
111+ description: Facts about countries (capital, languages). Answers in JSON. Name every country in the prompt.
112+ triage:
113+ path: agent.triage.yaml
114+ description: Triages bug reports (title, severity, component, summary, duplicate). Answers in JSON. Put the full text of every report in the prompt.
115+```
116+
117+Read the reports, have them triaged, write a table:
118+
119+```bash
120+../mm agent.coordinator.yaml -p "Read reports.txt, have the reports triaged, then write triage.md: a Markdown table (title, severity, component, duplicate of) sorted by severity, high first."
121+```
122+
123+```
124+📄 read_file: reports.txt
125+🗣 ask_agent: triage (llmman/hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M) — Triage these bug reports from r…
126+{"reports":[{"component":"Export","duplicate":null,"severity":"high",…}]}
127+💾 write_file: triage.md (340 bytes)
128+```
129+
130+A factual question, which Bob hands to countries:
131+
132+```bash
133+../mm agent.coordinator.yaml -p "Which of Belgium and Switzerland has more official languages? Answer in one sentence."
134+```
135+
136+Or in a session, to watch the agent work: `../mm -tui agent.coordinator.yaml`.
137+
138+## 3. Ask a JSON agent from a shell, through Bob's config
139+
140+`mm --ask` prints only the asked agent's answer — here, its JSON:
141+
142+```bash
143+../mm agent.coordinator.yaml --ask countries -p "capital and languages of Vietnam" | jq .
144+```
145+
146+## Good to know
147+
148+- **llama.cpp (so llmman) writes the keys in alphabetical order**, whatever the declaration order: here `capital` before `name`. The model fills a field from what it already wrote: make every comment self-sufficient ("the capital CITY (not the country)").
149+- **Cut answer**: `the answer was cut off by max_tokens before its JSON was complete` — bound the lists in their comments, or raise `sampling.max_tokens`.
150+- **Reasoning**: countries and triage turn it off (`chat_template_kwargs.enable_thinking: false`) — a lookup does not need it, and it is faster. Bob keeps it: without reasoning, the 4B model answered from memory and claimed it had asked countries.
151+- **OpenAI hosted API**: its strict mode demands an object at the root — use a struct, as `agent.triage.yaml` does.
152+- **With tools**: `structuredOutput cannot be used with tools — this agent declares: …`; turn the listed tools off.
153+- To see the exact request (the `response_format` sent): `AGENT_TRACE=true ../mm agent.countries.yaml -p "…"`, then the files in `.mm/traces/`.
154+
155+## Further reading
156+
157+- [Get answers as JSON](../docs/en/how-to/get-json-answers.md)
158+- [`structuredOutput` reference](../docs/en/reference/configuration.md#structuredoutput)
159+- [Structured output — explanation](../docs/en/explanation/structured-output.md)
new file mode 100644
@@ -0,0 +1,159 @@
1+# mini-me demo: JSON answers (`structuredOutput`)
2+
3+This directory shows how to make a mini-me agent answer **only with JSON**, in a shape you declare as Go types in its YAML file, and then how another agent uses that JSON. The models are served by [llmman](https://github.com/llmmanorg/llmman). Version française : [README.md](README.md).
4+
5+## What is here
6+
7+| File | Role |
8+|---|---|
9+| `agent.countries.yaml` | **countries**: a simple JSON agent. A question about countries → an array of countries (name, capital, languages). Array root: `var Countries = []Country{}`. |
10+| `agent.triage.yaml` | **triage**: a second JSON agent. Bug reports → an object `{"reports": [...]}` (title, severity, component, summary, duplicate). Struct root: `var Answer = Triage{}`. |
11+| `agent.coordinator.yaml` | **Bob**, the coordinator: a normal agent with tools (`ask_agent`, `read_file`, `write_file`), who hands questions to countries and triage and works from their JSON. |
12+| `reports.txt` | Four bug reports for the multi-agent example (the 4th repeats the 1st). |
13+
14+All three agents use `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`.
15+
16+## Prerequisites
17+
18+1. llmman running (`llmman serve`, on `127.0.0.1:17434`; from a Docker Sandbox, mm falls back to `host.docker.internal:17434` by itself).
19+2. The model, pulled beforehand (otherwise the first question pulls it and waits):
20+
21+ ```bash
22+ llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
23+ ```
24+
25+3. `mm` built at the repository root (`go build -o mm .`). **Run it from this directory**: the `mmConfigs` paths are relative to `agent.coordinator.yaml`, and `reports.txt` is read from the current directory.
26+
27+ ```bash
28+ cd demo-structured-output
29+ ```
30+
31+4. `jq`, to read the JSON (optional).
32+
33+If `jq` answers `parse error: Invalid numeric literal at line 1, column 8`, `mm` did not give it JSON: run the command again without `| jq` to read the message. The usual case: the command runs outside this directory, and `agent.countries.yaml` is not found (`[config error: open agent.countries.yaml: no such file or directory ]`). Since the version that adds `structuredOutput`, these errors go to stderr and `jq` receives nothing.
34+
35+## How the shape of the answer is declared
36+
37+In `agent.countries.yaml`:
38+
39+```yaml
40+structuredOutput: |
41+ // A language spoken in the country.
42+ type Language struct {
43+ Name string `json:"name"` // the language's name, in English
44+ Official bool `json:"official"` // true when it has an official status at the national level
45+ }
46+
47+ type Country struct {
48+ Name string `json:"name"` // the country's common name, in English
49+ Capital *string `json:"capital"` // the capital CITY (not the country), in English; null when unknown
50+ Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
51+ }
52+
53+ // One entry per country named in the question, in the order asked.
54+ var Countries = []Country{}
55+```
56+
57+- Every `type` is a definition; the one `var` names the **root** of the answer.
58+- A field's key is its `json` tag (its Go name when it has none).
59+- A pointer (`*string`) or `omitempty` makes the value optional: it may be `null`.
60+- **The `//` comments are the instructions the model reads**: mm appends them to its system prompt. Write them as instructions, and bound the lists (`at most 4`) — otherwise a small model may repeat items until `max_tokens`.
61+
62+mm translates these declarations into a JSON Schema, the server constrains the generation to it, and the answer is checked before it is handed back.
63+
64+A JSON agent has **no tool at all** (`bashTool`, `editTools`, `planTool` set to `false`, no skills, no `mmConfigs`): a constrained answer leaves no room for a tool call, and mm refuses to start otherwise.
65+
66+## 1. A simple agent: one question, JSON back
67+
68+```bash
69+../mm agent.countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" | jq .
70+```
71+
72+With `-p`, stdout carries **only the JSON** (no 🤖, no statistics), and the exit status is 1 when the answer is missing or invalid. So it can be processed directly:
73+
74+```bash
75+../mm agent.countries.yaml -p "languages spoken in France, Canada, Vietnam and the US" \
76+ | jq -c '.[] | {name, capital, languages: [.languages[].name]}'
77+```
78+
79+```
80+{"name":"France","capital":"Paris","languages":["French","Occitan","Breton","Catalan"]}
81+{"name":"Canada","capital":"Ottawa","languages":["English","French","Algonquin","Innu-aimun"]}
82+{"name":"Vietnam","capital":"Hanoi","languages":["Vietnamese","French","Chinese","English"]}
83+{"name":"United States","capital":"Washington, D.C.","languages":["English","Spanish","Chinese","Vietnamese"]}
84+```
85+
86+The minor languages may vary with the wording of the question; the name and the capital stay stable.
87+
88+The second agent, on the reports file:
89+
90+```bash
91+../mm agent.triage.yaml -p "$(cat reports.txt)" | jq -c '.reports[] | {severity, title, duplicate}'
92+```
93+
94+```
95+{"severity":"high","title":"Export freezes and closes on large projects","duplicate":null}
96+{"severity":"low","title":"Date format incorrect in Settings","duplicate":null}
97+{"severity":"low","title":"Typo on login page","duplicate":null}
98+{"severity":"high","title":"Export crashes on big projects","duplicate":1}
99+```
100+
101+In an interactive session (`../mm agent.countries.yaml`, or `../mm -tui agent.countries.yaml`), every answer is JSON, shown like any answer; the banner says `tools: none (structured output: JSON answers)`.
102+
103+## 2. Several agents: Bob uses the others' JSON
104+
105+Bob cannot have a `structuredOutput` himself: he has tools. He declares the two JSON agents under `mmConfigs` and calls them with `ask_agent`; their JSON comes back to him as the tool's result.
106+
107+```yaml
108+mmConfigs:
109+ countries:
110+ path: agent.countries.yaml
111+ description: Facts about countries (capital, languages). Answers in JSON. Name every country in the prompt.
112+ triage:
113+ path: agent.triage.yaml
114+ description: Triages bug reports (title, severity, component, summary, duplicate). Answers in JSON. Put the full text of every report in the prompt.
115+```
116+
117+Read the reports, have them triaged, write a table:
118+
119+```bash
120+../mm agent.coordinator.yaml -p "Read reports.txt, have the reports triaged, then write triage.md: a Markdown table (title, severity, component, duplicate of) sorted by severity, high first."
121+```
122+
123+```
124+📄 read_file: reports.txt
125+🗣 ask_agent: triage (llmman/hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M) — Triage these bug reports from r…
126+{"reports":[{"component":"Export","duplicate":null,"severity":"high",…}]}
127+💾 write_file: triage.md (340 bytes)
128+```
129+
130+A factual question, which Bob hands to countries:
131+
132+```bash
133+../mm agent.coordinator.yaml -p "Which of Belgium and Switzerland has more official languages? Answer in one sentence."
134+```
135+
136+Or in a session, to watch the agent work: `../mm -tui agent.coordinator.yaml`.
137+
138+## 3. Ask a JSON agent from a shell, through Bob's config
139+
140+`mm --ask` prints only the asked agent's answer — here, its JSON:
141+
142+```bash
143+../mm agent.coordinator.yaml --ask countries -p "capital and languages of Vietnam" | jq .
144+```
145+
146+## Good to know
147+
148+- **llama.cpp (so llmman) writes the keys in alphabetical order**, whatever the declaration order: here `capital` before `name`. The model fills a field from what it already wrote: make every comment self-sufficient ("the capital CITY (not the country)").
149+- **Cut answer**: `the answer was cut off by max_tokens before its JSON was complete` — bound the lists in their comments, or raise `sampling.max_tokens`.
150+- **Reasoning**: countries and triage turn it off (`chat_template_kwargs.enable_thinking: false`) — a lookup does not need it, and it is faster. Bob keeps it: without reasoning, the 4B model answered from memory and claimed it had asked countries.
151+- **OpenAI hosted API**: its strict mode demands an object at the root — use a struct, as `agent.triage.yaml` does.
152+- **With tools**: `structuredOutput cannot be used with tools — this agent declares: …`; turn the listed tools off.
153+- To see the exact request (the `response_format` sent): `AGENT_TRACE=true ../mm agent.countries.yaml -p "…"`, then the files in `.mm/traces/`.
154+
155+## Further reading
156+
157+- [Get answers as JSON](../docs/en/how-to/get-json-answers.md)
158+- [`structuredOutput` reference](../docs/en/reference/configuration.md#structuredoutput)
159+- [Structured output — explanation](../docs/en/explanation/structured-output.md)
added demo-structured-output/README.md +159 -0
new file mode 100644
@@ -0,0 +1,159 @@
1+# Démo mini-me : réponses en JSON (`structuredOutput`)
2+
3+Ce dossier montre comment faire répondre un agent mini-me **uniquement en JSON**, selon une forme que vous déclarez en types Go dans son fichier YAML, puis comment un autre agent exploite ce JSON. Les modèles sont servis par [llmman](https://github.com/llmmanorg/llmman). English version: [README.en.md](README.en.md).
4+
5+## Ce qu'il y a ici
6+
7+| Fichier | Rôle |
8+|---|---|
9+| `agent.countries.yaml` | **countries** : un agent JSON simple. Une question sur des pays → un tableau de pays (nom, capitale, langues). Racine tableau : `var Countries = []Country{}`. |
10+| `agent.triage.yaml` | **triage** : un deuxième agent JSON. Des rapports de bugs → un objet `{"reports": [...]}` (titre, gravité, composant, résumé, doublon). Racine struct : `var Answer = Triage{}`. |
11+| `agent.coordinator.yaml` | **Bob**, le coordinateur : un agent normal, avec des outils (`ask_agent`, `read_file`, `write_file`), qui confie les questions à countries et triage et travaille à partir de leur JSON. |
12+| `reports.txt` | Quatre rapports de bugs pour l'exemple multi-agents (le 4ᵉ répète le 1ᵉʳ). |
13+
14+Les trois agents utilisent `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`.
15+
16+## Prérequis
17+
18+1. llmman qui tourne (`llmman serve`, sur `127.0.0.1:17434` ; depuis un Docker Sandbox, mm se replie tout seul sur `host.docker.internal:17434`).
19+2. Le modèle, téléchargé à l'avance (sinon la première question le télécharge et attend) :
20+
21+ ```bash
22+ llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
23+ ```
24+
25+3. `mm` compilé à la racine du dépôt (`go build -o mm .`). **Lancez-le depuis ce dossier** : les chemins de `mmConfigs` sont relatifs à `agent.coordinator.yaml`, et `reports.txt` est lu dans le dossier courant.
26+
27+ ```bash
28+ cd demo-structured-output
29+ ```
30+
31+4. `jq`, pour lire le JSON (facultatif).
32+
33+Si `jq` répond `parse error: Invalid numeric literal at line 1, column 8`, c'est que `mm` ne vous a pas donné de JSON : relancez sans `| jq` pour lire le message. Le cas le plus courant : la commande est lancée hors de ce dossier, et `agent.countries.yaml` est introuvable (`[config error: open agent.countries.yaml: no such file or directory ]`). Depuis la version qui ajoute `structuredOutput`, ces erreurs vont sur stderr et `jq` ne reçoit plus rien.
34+
35+## Comment se déclare la forme de la réponse
36+
37+Dans `agent.countries.yaml` :
38+
39+```yaml
40+structuredOutput: |
41+ // A language spoken in the country.
42+ type Language struct {
43+ Name string `json:"name"` // the language's name, in English
44+ Official bool `json:"official"` // true when it has an official status at the national level
45+ }
46+
47+ type Country struct {
48+ Name string `json:"name"` // the country's common name, in English
49+ Capital *string `json:"capital"` // the capital CITY (not the country), in English; null when unknown
50+ Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
51+ }
52+
53+ // One entry per country named in the question, in the order asked.
54+ var Countries = []Country{}
55+```
56+
57+- Chaque `type` est une définition, l'unique `var` nomme la **racine** de la réponse.
58+- La clé d'un champ est son tag `json` (son nom Go s'il n'en a pas).
59+- Un pointeur (`*string`) ou `omitempty` rend la valeur facultative : elle peut valoir `null`.
60+- **Les commentaires `//` sont les consignes que lit le modèle** : mm les ajoute à son prompt système. Écrivez-les comme des instructions, et bornez les listes (`at most 4`) — sinon un petit modèle peut en répéter les éléments jusqu'à `max_tokens`.
61+
62+mm traduit ces déclarations en JSON Schema, le serveur contraint la génération à ce schéma, et la réponse est vérifiée avant d'être rendue.
63+
64+Un agent JSON **n'a aucun outil** (`bashTool`, `editTools`, `planTool` à `false`, pas de skills, pas de `mmConfigs`) : une réponse contrainte ne laisse pas de place à un appel d'outil, et mm refuse de démarrer sinon.
65+
66+## 1. Un agent simple : une question, du JSON
67+
68+```bash
69+mm agent.countries.yaml -p "donne moi les langages parlés de la France, le Canada, le Vietnam et les US" | jq .
70+```
71+
72+Avec `-p`, stdout ne porte **que le JSON** (rien d'autre : ni 🤖, ni statistiques), et le code de sortie vaut 1 si la réponse manque ou est invalide. On peut donc le traiter directement :
73+
74+```bash
75+mm agent.countries.yaml -p "donne moi les langages parlés de la France, le Canada, le Vietnam et les US" \
76+ | jq -c '.[] | {name, capital, langues: [.languages[].name]}'
77+```
78+
79+```
80+{"name":"France","capital":"Paris","langues":["French","Occitan","Breton","Catalan"]}
81+{"name":"Canada","capital":"Ottawa","langues":["English","French","Algonquin","Innu-aimun"]}
82+{"name":"Vietnam","capital":"Hanoi","langues":["Vietnamese","French","Chinese","English"]}
83+{"name":"United States","capital":"Washington, D.C.","langues":["English","Spanish","Chinese","Vietnamese"]}
84+```
85+
86+Les langues secondaires peuvent varier d'une formulation de la question à l'autre ; le nom et la capitale restent stables.
87+
88+Le deuxième agent, sur le fichier de rapports :
89+
90+```bash
91+../mm agent.triage.yaml -p "$(cat reports.txt)" | jq -c '.reports[] | {severity, title, duplicate}'
92+```
93+
94+```
95+{"severity":"high","title":"Export freezes and closes on large projects","duplicate":null}
96+{"severity":"low","title":"Date format incorrect in Settings","duplicate":null}
97+{"severity":"low","title":"Typo on login page","duplicate":null}
98+{"severity":"high","title":"Export crashes on big projects","duplicate":1}
99+```
100+
101+En session interactive (`../mm agent.countries.yaml`, ou `../mm -tui agent.countries.yaml`), chaque réponse est du JSON, affiché comme n'importe quelle réponse ; la bannière indique `tools: none (structured output: JSON answers)`.
102+
103+## 2. Plusieurs agents : Bob exploite le JSON des autres
104+
105+Bob ne peut pas avoir de `structuredOutput` lui-même : il a des outils. Il déclare les deux agents JSON sous `mmConfigs` et les appelle avec `ask_agent` ; leur JSON lui revient comme résultat de l'outil.
106+
107+```yaml
108+mmConfigs:
109+ countries:
110+ path: agent.countries.yaml
111+ description: Facts about countries (capital, languages). Answers in JSON. Name every country in the prompt.
112+ triage:
113+ path: agent.triage.yaml
114+ description: Triages bug reports (title, severity, component, summary, duplicate). Answers in JSON. Put the full text of every report in the prompt.
115+```
116+
117+Lire les rapports, les faire trier, écrire un tableau :
118+
119+```bash
120+mm agent.coordinator.yaml -p "Read reports.txt, have the reports triaged, then write triage.md: a Markdown table (title, severity, component, duplicate of) sorted by severity, high first."
121+```
122+
123+```
124+📄 read_file: reports.txt
125+🗣 ask_agent: triage (llmman/hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M) — Triage these bug reports from r…
126+{"reports":[{"component":"Export","duplicate":null,"severity":"high",…}]}
127+💾 write_file: triage.md (340 bytes)
128+```
129+
130+Une question de faits, que Bob confie à countries :
131+
132+```bash
133+mm agent.coordinator.yaml -p "Which of Belgium and Switzerland has more official languages? Answer in one sentence."
134+```
135+
136+Ou en session, pour voir l'agent travailler : `mm -tui agent.coordinator.yaml`.
137+
138+## 3. Interroger un agent JSON depuis un shell, par la config de Bob
139+
140+`mm --ask` n'imprime que la réponse de l'agent demandé — ici, son JSON :
141+
142+```bash
143+mm agent.coordinator.yaml --ask countries -p "capital and languages of Vietnam" | jq .
144+```
145+
146+## Ce qu'il faut savoir
147+
148+- **llama.cpp (donc llmman) écrit les clés dans l'ordre alphabétique**, quel que soit l'ordre de déclaration : ici `capital` avant `name`. Le modèle remplit un champ à partir de ce qu'il a déjà écrit : rendez chaque commentaire autosuffisant (« the capital CITY (not the country) »).
149+- **Réponse coupée** : `the answer was cut off by max_tokens before its JSON was complete` — bornez les listes dans leurs commentaires ou augmentez `sampling.max_tokens`.
150+- **Raisonnement** : countries et triage l'ont coupé (`chat_template_kwargs.enable_thinking: false`) — une consultation n'en a pas besoin, et c'est plus rapide. Bob le garde : sans raisonnement, le modèle 4B répondait de mémoire en prétendant avoir demandé à countries.
151+- **API OpenAI hébergée** : son mode strict exige un objet à la racine — utilisez une struct, comme dans `agent.triage.yaml`.
152+- **Avec des outils** : `structuredOutput cannot be used with tools — this agent declares: …` ; désactivez les outils listés.
153+- Pour voir la requête exacte (le `response_format` envoyé) : `AGENT_TRACE=true ../mm agent.countries.yaml -p "…"`, puis les fichiers de `.mm/traces/`.
154+
155+## Pour aller plus loin
156+
157+- [Obtenir des réponses en JSON](../docs/fr/how-to/get-json-answers.md)
158+- [Référence de `structuredOutput`](../docs/fr/reference/configuration.md#structuredoutput)
159+- [Sortie structurée — explication](../docs/fr/explanation/structured-output.md)
new file mode 100644
@@ -0,0 +1,159 @@
1+# Démo mini-me : réponses en JSON (`structuredOutput`)
2+
3+Ce dossier montre comment faire répondre un agent mini-me **uniquement en JSON**, selon une forme que vous déclarez en types Go dans son fichier YAML, puis comment un autre agent exploite ce JSON. Les modèles sont servis par [llmman](https://github.com/llmmanorg/llmman). English version: [README.en.md](README.en.md).
4+
5+## Ce qu'il y a ici
6+
7+| Fichier | Rôle |
8+|---|---|
9+| `agent.countries.yaml` | **countries** : un agent JSON simple. Une question sur des pays → un tableau de pays (nom, capitale, langues). Racine tableau : `var Countries = []Country{}`. |
10+| `agent.triage.yaml` | **triage** : un deuxième agent JSON. Des rapports de bugs → un objet `{"reports": [...]}` (titre, gravité, composant, résumé, doublon). Racine struct : `var Answer = Triage{}`. |
11+| `agent.coordinator.yaml` | **Bob**, le coordinateur : un agent normal, avec des outils (`ask_agent`, `read_file`, `write_file`), qui confie les questions à countries et triage et travaille à partir de leur JSON. |
12+| `reports.txt` | Quatre rapports de bugs pour l'exemple multi-agents (le 4ᵉ répète le 1ᵉʳ). |
13+
14+Les trois agents utilisent `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`.
15+
16+## Prérequis
17+
18+1. llmman qui tourne (`llmman serve`, sur `127.0.0.1:17434` ; depuis un Docker Sandbox, mm se replie tout seul sur `host.docker.internal:17434`).
19+2. Le modèle, téléchargé à l'avance (sinon la première question le télécharge et attend) :
20+
21+ ```bash
22+ llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
23+ ```
24+
25+3. `mm` compilé à la racine du dépôt (`go build -o mm .`). **Lancez-le depuis ce dossier** : les chemins de `mmConfigs` sont relatifs à `agent.coordinator.yaml`, et `reports.txt` est lu dans le dossier courant.
26+
27+ ```bash
28+ cd demo-structured-output
29+ ```
30+
31+4. `jq`, pour lire le JSON (facultatif).
32+
33+Si `jq` répond `parse error: Invalid numeric literal at line 1, column 8`, c'est que `mm` ne vous a pas donné de JSON : relancez sans `| jq` pour lire le message. Le cas le plus courant : la commande est lancée hors de ce dossier, et `agent.countries.yaml` est introuvable (`[config error: open agent.countries.yaml: no such file or directory ]`). Depuis la version qui ajoute `structuredOutput`, ces erreurs vont sur stderr et `jq` ne reçoit plus rien.
34+
35+## Comment se déclare la forme de la réponse
36+
37+Dans `agent.countries.yaml` :
38+
39+```yaml
40+structuredOutput: |
41+ // A language spoken in the country.
42+ type Language struct {
43+ Name string `json:"name"` // the language's name, in English
44+ Official bool `json:"official"` // true when it has an official status at the national level
45+ }
46+
47+ type Country struct {
48+ Name string `json:"name"` // the country's common name, in English
49+ Capital *string `json:"capital"` // the capital CITY (not the country), in English; null when unknown
50+ Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
51+ }
52+
53+ // One entry per country named in the question, in the order asked.
54+ var Countries = []Country{}
55+```
56+
57+- Chaque `type` est une définition, l'unique `var` nomme la **racine** de la réponse.
58+- La clé d'un champ est son tag `json` (son nom Go s'il n'en a pas).
59+- Un pointeur (`*string`) ou `omitempty` rend la valeur facultative : elle peut valoir `null`.
60+- **Les commentaires `//` sont les consignes que lit le modèle** : mm les ajoute à son prompt système. Écrivez-les comme des instructions, et bornez les listes (`at most 4`) — sinon un petit modèle peut en répéter les éléments jusqu'à `max_tokens`.
61+
62+mm traduit ces déclarations en JSON Schema, le serveur contraint la génération à ce schéma, et la réponse est vérifiée avant d'être rendue.
63+
64+Un agent JSON **n'a aucun outil** (`bashTool`, `editTools`, `planTool` à `false`, pas de skills, pas de `mmConfigs`) : une réponse contrainte ne laisse pas de place à un appel d'outil, et mm refuse de démarrer sinon.
65+
66+## 1. Un agent simple : une question, du JSON
67+
68+```bash
69+mm agent.countries.yaml -p "donne moi les langages parlés de la France, le Canada, le Vietnam et les US" | jq .
70+```
71+
72+Avec `-p`, stdout ne porte **que le JSON** (rien d'autre : ni 🤖, ni statistiques), et le code de sortie vaut 1 si la réponse manque ou est invalide. On peut donc le traiter directement :
73+
74+```bash
75+mm agent.countries.yaml -p "donne moi les langages parlés de la France, le Canada, le Vietnam et les US" \
76+ | jq -c '.[] | {name, capital, langues: [.languages[].name]}'
77+```
78+
79+```
80+{"name":"France","capital":"Paris","langues":["French","Occitan","Breton","Catalan"]}
81+{"name":"Canada","capital":"Ottawa","langues":["English","French","Algonquin","Innu-aimun"]}
82+{"name":"Vietnam","capital":"Hanoi","langues":["Vietnamese","French","Chinese","English"]}
83+{"name":"United States","capital":"Washington, D.C.","langues":["English","Spanish","Chinese","Vietnamese"]}
84+```
85+
86+Les langues secondaires peuvent varier d'une formulation de la question à l'autre ; le nom et la capitale restent stables.
87+
88+Le deuxième agent, sur le fichier de rapports :
89+
90+```bash
91+../mm agent.triage.yaml -p "$(cat reports.txt)" | jq -c '.reports[] | {severity, title, duplicate}'
92+```
93+
94+```
95+{"severity":"high","title":"Export freezes and closes on large projects","duplicate":null}
96+{"severity":"low","title":"Date format incorrect in Settings","duplicate":null}
97+{"severity":"low","title":"Typo on login page","duplicate":null}
98+{"severity":"high","title":"Export crashes on big projects","duplicate":1}
99+```
100+
101+En session interactive (`../mm agent.countries.yaml`, ou `../mm -tui agent.countries.yaml`), chaque réponse est du JSON, affiché comme n'importe quelle réponse ; la bannière indique `tools: none (structured output: JSON answers)`.
102+
103+## 2. Plusieurs agents : Bob exploite le JSON des autres
104+
105+Bob ne peut pas avoir de `structuredOutput` lui-même : il a des outils. Il déclare les deux agents JSON sous `mmConfigs` et les appelle avec `ask_agent` ; leur JSON lui revient comme résultat de l'outil.
106+
107+```yaml
108+mmConfigs:
109+ countries:
110+ path: agent.countries.yaml
111+ description: Facts about countries (capital, languages). Answers in JSON. Name every country in the prompt.
112+ triage:
113+ path: agent.triage.yaml
114+ description: Triages bug reports (title, severity, component, summary, duplicate). Answers in JSON. Put the full text of every report in the prompt.
115+```
116+
117+Lire les rapports, les faire trier, écrire un tableau :
118+
119+```bash
120+mm agent.coordinator.yaml -p "Read reports.txt, have the reports triaged, then write triage.md: a Markdown table (title, severity, component, duplicate of) sorted by severity, high first."
121+```
122+
123+```
124+📄 read_file: reports.txt
125+🗣 ask_agent: triage (llmman/hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M) — Triage these bug reports from r…
126+{"reports":[{"component":"Export","duplicate":null,"severity":"high",…}]}
127+💾 write_file: triage.md (340 bytes)
128+```
129+
130+Une question de faits, que Bob confie à countries :
131+
132+```bash
133+mm agent.coordinator.yaml -p "Which of Belgium and Switzerland has more official languages? Answer in one sentence."
134+```
135+
136+Ou en session, pour voir l'agent travailler : `mm -tui agent.coordinator.yaml`.
137+
138+## 3. Interroger un agent JSON depuis un shell, par la config de Bob
139+
140+`mm --ask` n'imprime que la réponse de l'agent demandé — ici, son JSON :
141+
142+```bash
143+mm agent.coordinator.yaml --ask countries -p "capital and languages of Vietnam" | jq .
144+```
145+
146+## Ce qu'il faut savoir
147+
148+- **llama.cpp (donc llmman) écrit les clés dans l'ordre alphabétique**, quel que soit l'ordre de déclaration : ici `capital` avant `name`. Le modèle remplit un champ à partir de ce qu'il a déjà écrit : rendez chaque commentaire autosuffisant (« the capital CITY (not the country) »).
149+- **Réponse coupée** : `the answer was cut off by max_tokens before its JSON was complete` — bornez les listes dans leurs commentaires ou augmentez `sampling.max_tokens`.
150+- **Raisonnement** : countries et triage l'ont coupé (`chat_template_kwargs.enable_thinking: false`) — une consultation n'en a pas besoin, et c'est plus rapide. Bob le garde : sans raisonnement, le modèle 4B répondait de mémoire en prétendant avoir demandé à countries.
151+- **API OpenAI hébergée** : son mode strict exige un objet à la racine — utilisez une struct, comme dans `agent.triage.yaml`.
152+- **Avec des outils** : `structuredOutput cannot be used with tools — this agent declares: …` ; désactivez les outils listés.
153+- Pour voir la requête exacte (le `response_format` envoyé) : `AGENT_TRACE=true ../mm agent.countries.yaml -p "…"`, puis les fichiers de `.mm/traces/`.
154+
155+## Pour aller plus loin
156+
157+- [Obtenir des réponses en JSON](../docs/fr/how-to/get-json-answers.md)
158+- [Référence de `structuredOutput`](../docs/fr/reference/configuration.md#structuredoutput)
159+- [Sortie structurée — explication](../docs/fr/explanation/structured-output.md)
added demo-structured-output/agent.coordinator.yaml +65 -0
new file mode 100644
@@ -0,0 +1,65 @@
1+# Agent settings — Bob, the coordinator: a free-text agent WITH tools, that
2+# hands the questions needing structured data to two JSON-only agents and
3+# works from their answers (ticket 80).
4+#
5+# mm agent.coordinator.yaml # talk to Bob
6+# mm agent.coordinator.yaml -p "Read reports.txt, have it triaged, and write triage.md"
7+# mm agent.coordinator.yaml --ask countries -p "capital of Vietnam" | jq # skip Bob
8+#
9+# Bob cannot have a structuredOutput himself: he has tools, and an answer
10+# constrained to a schema leaves no room for a tool call. The JSON agents have
11+# no tool at all. That is the split: Bob acts, they answer in a fixed shape.
12+provider: llmman
13+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
14+baseUrl: http://127.0.0.1:17434/v1
15+fallback: http://host.docker.internal:17434/v1
16+
17+# The agents Bob can ask with the `ask_agent` tool. Their answer — the JSON —
18+# comes back to him as the tool's result. Paths are relative to this file.
19+mmConfigs:
20+ countries:
21+ path: agent.countries.yaml
22+ description: Facts about countries (capital, languages). Answers in JSON. Name every country in the prompt.
23+ triage:
24+ path: agent.triage.yaml
25+ description: Triages bug reports (title, severity, component, summary, duplicate). Answers in JSON. Put the full text of every report in the prompt.
26+
27+# Bob reads and writes files (read_file, write_file, edit_file); no shell.
28+bashTool: false
29+editTools: true
30+planTool: false
31+skillsDir: no-skills
32+
33+# Bob's file writes run without asking, so that `-p` runs to the end. Set it
34+# to false to approve each write.
35+yolo: true
36+
37+acp:
38+ name: bob
39+ title: Bob (coordinator)
40+
41+system: |
42+ Your name is Bob. You coordinate two agents that answer in JSON, through
43+ your `ask_agent` tool:
44+ - `countries` for facts about countries;
45+ - `triage` to triage bug reports.
46+ When a question is about one or more countries, call `countries` FIRST,
47+ before you write anything: you do not answer country facts from memory.
48+ When you are given bug reports, call `triage` FIRST.
49+ They do not see this conversation: give them everything in the prompt —
50+ the countries by name, the full text of the reports. Call each agent ONCE
51+ per task, then work from its JSON: never invent a value it did not give.
52+
53+ To read a file, use `read_file`; to create one, `write_file`.
54+ Answer the user in English, briefly, and say which agent you asked.
55+
56+sampling:
57+ temperature: 0.0
58+ parallel_tool_calls: false
59+ # Bob keeps his reasoning (Qwen3.5 thinks by default): without it, the 4B
60+ # model answered country questions from memory and claimed it had asked.
61+ max_tokens: 8192
62+
63+maxTurns: 20
64+watchdogTimeout: 2m
65+firstTokenTimeout: 3m
new file mode 100644
@@ -0,0 +1,65 @@
1+# Agent settings — Bob, the coordinator: a free-text agent WITH tools, that
2+# hands the questions needing structured data to two JSON-only agents and
3+# works from their answers (ticket 80).
4+#
5+# mm agent.coordinator.yaml # talk to Bob
6+# mm agent.coordinator.yaml -p "Read reports.txt, have it triaged, and write triage.md"
7+# mm agent.coordinator.yaml --ask countries -p "capital of Vietnam" | jq # skip Bob
8+#
9+# Bob cannot have a structuredOutput himself: he has tools, and an answer
10+# constrained to a schema leaves no room for a tool call. The JSON agents have
11+# no tool at all. That is the split: Bob acts, they answer in a fixed shape.
12+provider: llmman
13+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
14+baseUrl: http://127.0.0.1:17434/v1
15+fallback: http://host.docker.internal:17434/v1
16+
17+# The agents Bob can ask with the `ask_agent` tool. Their answer — the JSON —
18+# comes back to him as the tool's result. Paths are relative to this file.
19+mmConfigs:
20+ countries:
21+ path: agent.countries.yaml
22+ description: Facts about countries (capital, languages). Answers in JSON. Name every country in the prompt.
23+ triage:
24+ path: agent.triage.yaml
25+ description: Triages bug reports (title, severity, component, summary, duplicate). Answers in JSON. Put the full text of every report in the prompt.
26+
27+# Bob reads and writes files (read_file, write_file, edit_file); no shell.
28+bashTool: false
29+editTools: true
30+planTool: false
31+skillsDir: no-skills
32+
33+# Bob's file writes run without asking, so that `-p` runs to the end. Set it
34+# to false to approve each write.
35+yolo: true
36+
37+acp:
38+ name: bob
39+ title: Bob (coordinator)
40+
41+system: |
42+ Your name is Bob. You coordinate two agents that answer in JSON, through
43+ your `ask_agent` tool:
44+ - `countries` for facts about countries;
45+ - `triage` to triage bug reports.
46+ When a question is about one or more countries, call `countries` FIRST,
47+ before you write anything: you do not answer country facts from memory.
48+ When you are given bug reports, call `triage` FIRST.
49+ They do not see this conversation: give them everything in the prompt —
50+ the countries by name, the full text of the reports. Call each agent ONCE
51+ per task, then work from its JSON: never invent a value it did not give.
52+
53+ To read a file, use `read_file`; to create one, `write_file`.
54+ Answer the user in English, briefly, and say which agent you asked.
55+
56+sampling:
57+ temperature: 0.0
58+ parallel_tool_calls: false
59+ # Bob keeps his reasoning (Qwen3.5 thinks by default): without it, the 4B
60+ # model answered country questions from memory and claimed it had asked.
61+ max_tokens: 8192
62+
63+maxTurns: 20
64+watchdogTimeout: 2m
65+firstTokenTimeout: 3m
added demo-structured-output/agent.countries.yaml +62 -0
new file mode 100644
@@ -0,0 +1,62 @@
1+# Agent settings — "countries": an agent that answers ONLY in JSON, with the
2+# shape declared below in `structuredOutput` (ticket 80). mini-me on
3+# Qwen3.5-4B, served by llmman (https://github.com/llmmanorg/llmman).
4+#
5+# llmman serve # 127.0.0.1:17434
6+# mm agent.countries.yaml -p "languages spoken in France and Canada" | jq .
7+provider: llmman
8+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
9+baseUrl: http://127.0.0.1:17434/v1
10+# From a Docker sandbox, the daemon runs on the host.
11+fallback: http://host.docker.internal:17434/v1
12+
13+# The shape of EVERY answer, written as Go type declarations:
14+# - every `type` is a definition, the one `var` names the root of the answer;
15+# - a field's key is its `json` tag (its Go name when there is none);
16+# - a `//` comment becomes the field's description — the model reads it;
17+# - a pointer (or `omitempty`) makes a field optional: it may be null.
18+# mm turns it into a JSON Schema; the server constrains the generation to it
19+# (response_format json_schema), and the answer is checked before it is shown.
20+# llama.cpp (so llmman) writes the keys in ALPHABETICAL order whatever the
21+# declaration order: here `capital` comes before `name`. A model fills a field
22+# from what it already wrote, so make every description self-sufficient.
23+structuredOutput: |
24+ // A language spoken in the country.
25+ type Language struct {
26+ Name string `json:"name"` // the language's name, in English
27+ Official bool `json:"official"` // true when it has an official status at the national level
28+ }
29+
30+ type Country struct {
31+ Name string `json:"name"` // the country's common name, in English
32+ Capital *string `json:"capital"` // the capital CITY (not the country), in English; null when unknown
33+ Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
34+ }
35+
36+ // One entry per country named in the question, in the order asked.
37+ var Countries = []Country{}
38+
39+# An answer constrained to a schema leaves no room for a tool call: every tool
40+# is off — mm refuses to start otherwise. No skills either (the directory does
41+# not exist, so no read_skill tool).
42+bashTool: false
43+editTools: false
44+planTool: false
45+skillsDir: no-skills
46+
47+acp:
48+ name: countries
49+ title: Countries (JSON)
50+
51+system: |
52+ You answer questions about countries. Fill every field from what you know.
53+
54+sampling:
55+ temperature: 0.0
56+ max_tokens: 4096
57+ # Qwen3.5 thinks before it answers by default; a lookup needs no reasoning.
58+ chat_template_kwargs:
59+ enable_thinking: false
60+
61+watchdogTimeout: 30s
62+firstTokenTimeout: 3m
new file mode 100644
@@ -0,0 +1,62 @@
1+# Agent settings — "countries": an agent that answers ONLY in JSON, with the
2+# shape declared below in `structuredOutput` (ticket 80). mini-me on
3+# Qwen3.5-4B, served by llmman (https://github.com/llmmanorg/llmman).
4+#
5+# llmman serve # 127.0.0.1:17434
6+# mm agent.countries.yaml -p "languages spoken in France and Canada" | jq .
7+provider: llmman
8+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
9+baseUrl: http://127.0.0.1:17434/v1
10+# From a Docker sandbox, the daemon runs on the host.
11+fallback: http://host.docker.internal:17434/v1
12+
13+# The shape of EVERY answer, written as Go type declarations:
14+# - every `type` is a definition, the one `var` names the root of the answer;
15+# - a field's key is its `json` tag (its Go name when there is none);
16+# - a `//` comment becomes the field's description — the model reads it;
17+# - a pointer (or `omitempty`) makes a field optional: it may be null.
18+# mm turns it into a JSON Schema; the server constrains the generation to it
19+# (response_format json_schema), and the answer is checked before it is shown.
20+# llama.cpp (so llmman) writes the keys in ALPHABETICAL order whatever the
21+# declaration order: here `capital` comes before `name`. A model fills a field
22+# from what it already wrote, so make every description self-sufficient.
23+structuredOutput: |
24+ // A language spoken in the country.
25+ type Language struct {
26+ Name string `json:"name"` // the language's name, in English
27+ Official bool `json:"official"` // true when it has an official status at the national level
28+ }
29+
30+ type Country struct {
31+ Name string `json:"name"` // the country's common name, in English
32+ Capital *string `json:"capital"` // the capital CITY (not the country), in English; null when unknown
33+ Languages []Language `json:"languages"` // the main languages only, at most 4, most spoken first, each once
34+ }
35+
36+ // One entry per country named in the question, in the order asked.
37+ var Countries = []Country{}
38+
39+# An answer constrained to a schema leaves no room for a tool call: every tool
40+# is off — mm refuses to start otherwise. No skills either (the directory does
41+# not exist, so no read_skill tool).
42+bashTool: false
43+editTools: false
44+planTool: false
45+skillsDir: no-skills
46+
47+acp:
48+ name: countries
49+ title: Countries (JSON)
50+
51+system: |
52+ You answer questions about countries. Fill every field from what you know.
53+
54+sampling:
55+ temperature: 0.0
56+ max_tokens: 4096
57+ # Qwen3.5 thinks before it answers by default; a lookup needs no reasoning.
58+ chat_template_kwargs:
59+ enable_thinking: false
60+
61+watchdogTimeout: 30s
62+firstTokenTimeout: 3m
added demo-structured-output/agent.triage.yaml +51 -0
new file mode 100644
@@ -0,0 +1,51 @@
1+# Agent settings — "triage": reads bug reports and answers ONLY in JSON, one
2+# entry per report (ticket 80). Asked by the coordinator (agent.coordinator.yaml)
3+# through ask_agent, or straight from a shell:
4+#
5+# mm agent.triage.yaml -p "$(cat reports.txt)" | jq '.reports[] | select(.severity == "high")'
6+provider: llmman
7+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
8+baseUrl: http://127.0.0.1:17434/v1
9+fallback: http://host.docker.internal:17434/v1
10+
11+# A struct root — the shape OpenAI's hosted API demands (an object at the top),
12+# and the one to choose when the answer should be extensible later.
13+structuredOutput: |
14+ // One bug report, triaged.
15+ type Report struct {
16+ Title string `json:"title"` // a short title, at most 8 words, in English
17+ Severity string `json:"severity"` // exactly one of: "low", "medium", "high" — high = data loss, crash or security
18+ Component string `json:"component"` // the part of the product concerned, in one or two words
19+ Summary string `json:"summary"` // one sentence: what happens and when
20+ Duplicate *int `json:"duplicate"` // the number (1-based) of an EARLIER report this one repeats; null otherwise
21+ }
22+
23+ type Triage struct {
24+ Reports []Report `json:"reports"` // one entry per report given, in the order given
25+ }
26+
27+ var Answer = Triage{}
28+
29+# Every tool off: an answer constrained to a schema leaves no room for a tool call.
30+bashTool: false
31+editTools: false
32+planTool: false
33+skillsDir: no-skills
34+
35+acp:
36+ name: triage
37+ title: Triage (JSON)
38+
39+system: |
40+ You triage bug reports for a software team. You are given one or more
41+ reports, numbered or separated by blank lines. Judge each one only from
42+ its text.
43+
44+sampling:
45+ temperature: 0.0
46+ max_tokens: 4096
47+ chat_template_kwargs:
48+ enable_thinking: false
49+
50+watchdogTimeout: 30s
51+firstTokenTimeout: 3m
new file mode 100644
@@ -0,0 +1,51 @@
1+# Agent settings — "triage": reads bug reports and answers ONLY in JSON, one
2+# entry per report (ticket 80). Asked by the coordinator (agent.coordinator.yaml)
3+# through ask_agent, or straight from a shell:
4+#
5+# mm agent.triage.yaml -p "$(cat reports.txt)" | jq '.reports[] | select(.severity == "high")'
6+provider: llmman
7+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
8+baseUrl: http://127.0.0.1:17434/v1
9+fallback: http://host.docker.internal:17434/v1
10+
11+# A struct root — the shape OpenAI's hosted API demands (an object at the top),
12+# and the one to choose when the answer should be extensible later.
13+structuredOutput: |
14+ // One bug report, triaged.
15+ type Report struct {
16+ Title string `json:"title"` // a short title, at most 8 words, in English
17+ Severity string `json:"severity"` // exactly one of: "low", "medium", "high" — high = data loss, crash or security
18+ Component string `json:"component"` // the part of the product concerned, in one or two words
19+ Summary string `json:"summary"` // one sentence: what happens and when
20+ Duplicate *int `json:"duplicate"` // the number (1-based) of an EARLIER report this one repeats; null otherwise
21+ }
22+
23+ type Triage struct {
24+ Reports []Report `json:"reports"` // one entry per report given, in the order given
25+ }
26+
27+ var Answer = Triage{}
28+
29+# Every tool off: an answer constrained to a schema leaves no room for a tool call.
30+bashTool: false
31+editTools: false
32+planTool: false
33+skillsDir: no-skills
34+
35+acp:
36+ name: triage
37+ title: Triage (JSON)
38+
39+system: |
40+ You triage bug reports for a software team. You are given one or more
41+ reports, numbered or separated by blank lines. Judge each one only from
42+ its text.
43+
44+sampling:
45+ temperature: 0.0
46+ max_tokens: 4096
47+ chat_template_kwargs:
48+ enable_thinking: false
49+
50+watchdogTimeout: 30s
51+firstTokenTimeout: 3m
added demo-structured-output/reports.txt +4 -0
new file mode 100644
@@ -0,0 +1,4 @@
1+1. When I click "Export" on a project with more than 500 tasks, the app freezes and then closes. I lost my unsaved changes.
2+2. The "Settings" page shows the date as 10/08/2026 even though my language is French.
3+3. Typo on the login page: "Pasword" instead of "Password".
4+4. Exporting a big project crashes the application, same as someone reported before I think.
new file mode 100644
@@ -0,0 +1,4 @@
1+1. When I click "Export" on a project with more than 500 tasks, the app freezes and then closes. I lost my unsaved changes.
2+2. The "Settings" page shows the date as 10/08/2026 even though my language is French.
3+3. Typo on the login page: "Pasword" instead of "Password".
4+4. Exporting a big project crashes the application, same as someone reported before I think.
added demo-structured-output/triage.md +8 -0
new file mode 100644
@@ -0,0 +1,8 @@
1+# Bug Report Triage
2+
3+| Title | Severity | Component | Duplicate of |
4+|-------|----------|-----------|--------------|
5+| Export freeze and crash with large projects | high | Export | - |
6+| Export crash on large projects | high | Export | 1 |
7+| Date format incorrect in Settings | low | Settings | - |
8+| Typo on login page | low | Login | - |
new file mode 100644
@@ -0,0 +1,8 @@
1+# Bug Report Triage
2+
3+| Title | Severity | Component | Duplicate of |
4+|-------|----------|-----------|--------------|
5+| Export freeze and crash with large projects | high | Export | - |
6+| Export crash on large projects | high | Export | 1 |
7+| Date format incorrect in Settings | low | Settings | - |
8+| Typo on login page | low | Login | - |
modified docs/diagrams/packages.drawio +3 -3
@@ -1,3 +1,3 @@
1-<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry"/></mxCell>
2- <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry"/></mxCell>
3- <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry"/></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry"/></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry"/></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry"/></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
1+<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
2+ <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>
3+ <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
@@ -1,3 +1,3 @@
1-<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry"/></mxCell>
2- <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry"/></mxCell>
3- <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry"/></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry"/></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry"/></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry"/></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
1+<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
2+ <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>
3+ <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
modified docs/en/README.md +2 -0
@@ -15,6 +15,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
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>`
1717 - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq
18+- [Get answers as JSON](how-to/get-json-answers.md) — `structuredOutput`: Go declarations, `-p … | jq`, a JSON agent behind a coordinator
1819 - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo`
1920 - [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`
2021 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)
@@ -63,6 +64,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
6364 - [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
6465 - [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
6566 - [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`)
67+- [Structured output](explanation/structured-output.md) — why Go declarations, why they also go in the system prompt, why no tools, what guarantees the answer
6668 - [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
6769 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
6870
@@ -15,6 +15,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
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 jq17 - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq
18+- [Get answers as JSON](how-to/get-json-answers.md) — `structuredOutput`: Go declarations, `-p … | jq`, a JSON agent behind a coordinator
18 - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo`19 - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo`
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`20 - [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`
20 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)21 - [Use mini-me from an editor (ACP)](how-to/use-from-an-editor.md)
@@ -63,6 +64,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
63 - [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 limits64 - [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
64 - [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 alive65 - [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
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`)66 - [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`)
67+- [Structured output](explanation/structured-output.md) — why Go declarations, why they also go in the system prompt, why no tools, what guarantees the answer
66 - [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 limit68 - [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
67 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)69 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
68 70
modified docs/en/explanation/architecture.md +1 -0
@@ -9,6 +9,7 @@ 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+| `structured` | Translates the Go declarations of `structuredOutput` into the JSON Schema the answer must follow, with `go/parser`; used by `config` when the file loads. See [Structured output](structured-output.md). |
1213 | `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`). |
1314 | `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). |
1415 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |
@@ -9,6 +9,7 @@ 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+| `structured` | Translates the Go declarations of `structuredOutput` into the JSON Schema the answer must follow, with `go/parser`; used by `config` when the file loads. See [Structured output](structured-output.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 | `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`). |14 | `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`. |15 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |
added docs/en/explanation/structured-output.md +40 -0
new file mode 100644
@@ -0,0 +1,40 @@
1+# Structured output — explanation
2+
3+## What is this about?
4+
5+Some answers are meant for a program rather than a person: a list of countries for a script, a bug triage for a coordinator agent. Free text is a poor carrier for that — the program has to guess where the data starts, and a model drifts from the format it was asked for. Servers that speak the OpenAI API can instead **constrain** the generation: given a JSON Schema in `response_format`, llama.cpp compiles it into a grammar, and the model cannot produce a token outside it. `structuredOutput` (ticket 80) is how an mm agent opts into that.
6+
7+## Why Go declarations
8+
9+A JSON Schema is the wire format, but it is verbose and easy to get subtly wrong by hand. The section is written as Go type declarations instead: compact, readable, familiar to the people who write mm's configurations — and to models trained on code.
10+
11+No Go type is created at runtime: Go cannot compile code while it runs. mm reads the text as **source**. `go/parser`, from the standard library, builds its syntax tree; `internal/structured` walks it and translates each declaration — a struct into a closed object, a slice into an array, a declared type into a `$ref` — then hands the schema to Genkit with `ai.WithOutputSchema`. Unsupported constructs are refused when the configuration loads, with their line in the YAML block, rather than as a failed answer later.
12+
13+Rejected alternatives:
14+
15+- **`reflect.StructOf`** builds struct types at runtime, but cannot express named or recursive types, ignores comments, and would only re-check what the schema already says.
16+- **An embedded Go interpreter** (yaegi): a large dependency to run code of which only the declarations are ever used.
17+- **A YAML shape notation** (`countries: [{name: string}]`): shorter, but a mini-language to invent and document, with no place for descriptions.
18+- **A raw JSON Schema** in the config: possible, but it is what the Go form exists to spare people from writing.
19+
20+## Why the declarations are also in the system prompt
21+
22+The schema constrains the **syntax**, not the meaning. When the constraint is native, Genkit puts nothing about the schema in the prompt, and llama.cpp keeps only the grammar: the model sees key names, never the descriptions. Measured on Qwen3.5-4B: asked for capitals, it wrote the country's name in `capital` every time. mm therefore appends the declarations themselves, comments included, to the system prompt. That is why the comments matter: they are the instructions the model reads.
23+
24+## Why keys keep their declaration order — and why it may not help
25+
26+`properties` are sent in declaration order, because a model fills a field from what it has already written: declaring `Name` first is a way to say "decide this first". A plain Go map would have sorted them. But llama.cpp (build b11429, measured through llmman) reorders the keys alphabetically when it builds its grammar, so on that server `capital` is still written before `name`. Servers that respect the order benefit; on llama.cpp, the remedy is a self-sufficient comment on every field.
27+
28+## Why an agent with tools cannot have one
29+
30+A server that constrains every answer to the schema leaves no room for a tool call, and llama.cpp refuses grammar constraints together with tools. Genkit's own rule says the same (`ConstrainedSupportNoTools`). Rather than a hidden two-phase turn (work with tools, then reformat), mm keeps the roles apart: an agent either acts, or answers in a fixed shape. The two meet through `ask_agent` — a coordinator with tools asks a JSON agent and receives the JSON as the tool's result. The configuration error lists the tools to turn off.
31+
32+## What guarantees the answer
33+
34+Three layers: the server's grammar (when the server supports it), Genkit's validation of the answer against the schema (a non-conforming answer fails the turn), and — in `-p` — an exit status a script can test, with nothing but the JSON on stdout. The usual failure is not bad syntax but a cut answer: a small model repeating list items until `max_tokens`. mm names that case explicitly, because the fix is a setting or a bounded list, not the model's syntax.
35+
36+## How it relates to the rest
37+
38+- [Asking another agent](asking-another-agent.md) — how the JSON reaches a coordinator.
39+- [Thinking models](thinking-models.md) — a lookup agent rarely needs reasoning; a coordinator often does (on Qwen3.5-4B, without it, the coordinator answered from memory and claimed it had asked).
40+- [How to get answers as JSON](../how-to/get-json-answers.md) and the [`structuredOutput` reference](../reference/configuration.md#structuredoutput).
new file mode 100644
@@ -0,0 +1,40 @@
1+# Structured output — explanation
2+
3+## What is this about?
4+
5+Some answers are meant for a program rather than a person: a list of countries for a script, a bug triage for a coordinator agent. Free text is a poor carrier for that — the program has to guess where the data starts, and a model drifts from the format it was asked for. Servers that speak the OpenAI API can instead **constrain** the generation: given a JSON Schema in `response_format`, llama.cpp compiles it into a grammar, and the model cannot produce a token outside it. `structuredOutput` (ticket 80) is how an mm agent opts into that.
6+
7+## Why Go declarations
8+
9+A JSON Schema is the wire format, but it is verbose and easy to get subtly wrong by hand. The section is written as Go type declarations instead: compact, readable, familiar to the people who write mm's configurations — and to models trained on code.
10+
11+No Go type is created at runtime: Go cannot compile code while it runs. mm reads the text as **source**. `go/parser`, from the standard library, builds its syntax tree; `internal/structured` walks it and translates each declaration — a struct into a closed object, a slice into an array, a declared type into a `$ref` — then hands the schema to Genkit with `ai.WithOutputSchema`. Unsupported constructs are refused when the configuration loads, with their line in the YAML block, rather than as a failed answer later.
12+
13+Rejected alternatives:
14+
15+- **`reflect.StructOf`** builds struct types at runtime, but cannot express named or recursive types, ignores comments, and would only re-check what the schema already says.
16+- **An embedded Go interpreter** (yaegi): a large dependency to run code of which only the declarations are ever used.
17+- **A YAML shape notation** (`countries: [{name: string}]`): shorter, but a mini-language to invent and document, with no place for descriptions.
18+- **A raw JSON Schema** in the config: possible, but it is what the Go form exists to spare people from writing.
19+
20+## Why the declarations are also in the system prompt
21+
22+The schema constrains the **syntax**, not the meaning. When the constraint is native, Genkit puts nothing about the schema in the prompt, and llama.cpp keeps only the grammar: the model sees key names, never the descriptions. Measured on Qwen3.5-4B: asked for capitals, it wrote the country's name in `capital` every time. mm therefore appends the declarations themselves, comments included, to the system prompt. That is why the comments matter: they are the instructions the model reads.
23+
24+## Why keys keep their declaration order — and why it may not help
25+
26+`properties` are sent in declaration order, because a model fills a field from what it has already written: declaring `Name` first is a way to say "decide this first". A plain Go map would have sorted them. But llama.cpp (build b11429, measured through llmman) reorders the keys alphabetically when it builds its grammar, so on that server `capital` is still written before `name`. Servers that respect the order benefit; on llama.cpp, the remedy is a self-sufficient comment on every field.
27+
28+## Why an agent with tools cannot have one
29+
30+A server that constrains every answer to the schema leaves no room for a tool call, and llama.cpp refuses grammar constraints together with tools. Genkit's own rule says the same (`ConstrainedSupportNoTools`). Rather than a hidden two-phase turn (work with tools, then reformat), mm keeps the roles apart: an agent either acts, or answers in a fixed shape. The two meet through `ask_agent` — a coordinator with tools asks a JSON agent and receives the JSON as the tool's result. The configuration error lists the tools to turn off.
31+
32+## What guarantees the answer
33+
34+Three layers: the server's grammar (when the server supports it), Genkit's validation of the answer against the schema (a non-conforming answer fails the turn), and — in `-p` — an exit status a script can test, with nothing but the JSON on stdout. The usual failure is not bad syntax but a cut answer: a small model repeating list items until `max_tokens`. mm names that case explicitly, because the fix is a setting or a bounded list, not the model's syntax.
35+
36+## How it relates to the rest
37+
38+- [Asking another agent](asking-another-agent.md) — how the JSON reaches a coordinator.
39+- [Thinking models](thinking-models.md) — a lookup agent rarely needs reasoning; a coordinator often does (on Qwen3.5-4B, without it, the coordinator answered from memory and claimed it had asked).
40+- [How to get answers as JSON](../how-to/get-json-answers.md) and the [`structuredOutput` reference](../reference/configuration.md#structuredoutput).
added docs/en/how-to/get-json-answers.md +74 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/cli.md +1 -0
@@ -132,6 +132,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
132132 | Config file | Can appear before or after the `-prompt`/`-p` flag: `mm -p "prompt" config.yaml` and `mm config.yaml -p "prompt"` both work. |
133133 | Compatibility | Incompatible with `-acp` and `-tui`; prompt mode takes precedence and runs regardless. |
134134 | Permissions | Nobody can answer. With `yolo: false`, every call that would ask is rejected (`[command rejected by the user]` for the model) and stderr says once: `[yolo is off and this mode cannot ask: tool calls that need a confirmation are rejected — set yolo: true to let them run]`. MCP tools marked `ask` are rejected the same way, whatever `yolo` says. |
135+| With `structuredOutput` | stdout carries the JSON answer and a newline, nothing else: no streamed text, no 🤖, no statistics line. Messages go to stderr. A failed or invalid answer prints `[structured output: <error>]` on stderr, nothing on stdout, and exits with status 1; a valid answer exits with status 0. See [`structuredOutput`](configuration.md#structuredoutput). |
135136
136137 ## Ask mode specifics
137138
@@ -132,6 +132,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
132 | Config file | Can appear before or after the `-prompt`/`-p` flag: `mm -p "prompt" config.yaml` and `mm config.yaml -p "prompt"` both work. |132 | Config file | Can appear before or after the `-prompt`/`-p` flag: `mm -p "prompt" config.yaml` and `mm config.yaml -p "prompt"` both work. |
133 | Compatibility | Incompatible with `-acp` and `-tui`; prompt mode takes precedence and runs regardless. |133 | Compatibility | Incompatible with `-acp` and `-tui`; prompt mode takes precedence and runs regardless. |
134 | Permissions | Nobody can answer. With `yolo: false`, every call that would ask is rejected (`[command rejected by the user]` for the model) and stderr says once: `[yolo is off and this mode cannot ask: tool calls that need a confirmation are rejected — set yolo: true to let them run]`. MCP tools marked `ask` are rejected the same way, whatever `yolo` says. |134 | Permissions | Nobody can answer. With `yolo: false`, every call that would ask is rejected (`[command rejected by the user]` for the model) and stderr says once: `[yolo is off and this mode cannot ask: tool calls that need a confirmation are rejected — set yolo: true to let them run]`. MCP tools marked `ask` are rejected the same way, whatever `yolo` says. |
135+| With `structuredOutput` | stdout carries the JSON answer and a newline, nothing else: no streamed text, no 🤖, no statistics line. Messages go to stderr. A failed or invalid answer prints `[structured output: <error>]` on stderr, nothing on stdout, and exits with status 1; a valid answer exits with status 0. See [`structuredOutput`](configuration.md#structuredoutput). |
135 136
136 ## Ask mode specifics137 ## Ask mode specifics
137 138
modified docs/en/reference/configuration.md +53 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/README.md +2 -0
@@ -15,6 +15,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
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>`
1717 - [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
18+- [Obtenir des réponses en JSON](how-to/get-json-answers.md) — `structuredOutput` : déclarations Go, `-p … | jq`, un agent JSON derrière un coordinateur
1819 - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent
1920 - [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`
2021 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)
@@ -63,6 +64,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
6364 - [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
6465 - [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
6566 - [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`)
67+- [Sortie structurée](explanation/structured-output.md) — pourquoi des déclarations Go, pourquoi aussi dans le prompt système, pourquoi pas d'outils, ce qui garantit la réponse
6668 - [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
6769 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
6870
@@ -15,6 +15,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
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 jq17 - [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
18+- [Obtenir des réponses en JSON](how-to/get-json-answers.md) — `structuredOutput` : déclarations Go, `-p … | jq`, un agent JSON derrière un coordinateur
18 - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent19 - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent
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`20 - [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`
20 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)21 - [Utiliser mini-me depuis un éditeur (ACP)](how-to/use-from-an-editor.md)
@@ -63,6 +64,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
63 - [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 connues64 - [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
64 - [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 vie65 - [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
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`)66 - [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`)
67+- [Sortie structurée](explanation/structured-output.md) — pourquoi des déclarations Go, pourquoi aussi dans le prompt système, pourquoi pas d'outils, ce qui garantit la réponse
66 - [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 profondeur68 - [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
67 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)69 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
68 70
modified docs/fr/explanation/architecture.md +1 -0
@@ -9,6 +9,7 @@ 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+| `structured` | Traduit les déclarations Go de `structuredOutput` en JSON Schema que doit suivre la réponse, avec `go/parser` ; utilisé par `config` au chargement du fichier. Voir [Sortie structurée](structured-output.md). |
1213 | `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`). |
1314 | `tools` | Les outils intégrés que le modèle peut appeler, `ask_agent` compris (il interroge un agent de `mmConfigs` via `ask`). |
1415 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |
@@ -9,6 +9,7 @@ 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+| `structured` | Traduit les déclarations Go de `structuredOutput` en JSON Schema que doit suivre la réponse, avec `go/parser` ; utilisé par `config` au chargement du fichier. Voir [Sortie structurée](structured-output.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 | `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`). |14 | `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`. |15 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |
added docs/fr/explanation/structured-output.md +40 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/fr/how-to/get-json-answers.md +74 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/cli.md +1 -0
@@ -132,6 +132,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
132132 | Fichier de config | Peut apparaître avant ou après le flag `-prompt`/`-p` : `mm -p "prompt" config.yaml` et `mm config.yaml -p "prompt"` fonctionnent tous deux. |
133133 | Compatibilité | Incompatible avec `-acp` et `-tui` ; le mode invite prend la priorité et s'exécute dans tous les cas. |
134134 | Permissions | Personne ne peut répondre. Avec `yolo: false`, chaque appel qui demanderait est refusé (`[command rejected by the user]` pour le modèle) et stderr le dit une fois : `[yolo is off and this mode cannot ask: tool calls that need a confirmation are rejected — set yolo: true to let them run]`. Les outils MCP marqués `ask` sont refusés de la même façon, quoi que dise `yolo`. |
135+| Avec `structuredOutput` | stdout porte la réponse JSON et un saut de ligne, rien d'autre : pas de texte en flux, pas de 🤖, pas de ligne de statistiques. Les messages vont sur stderr. Une réponse en échec ou invalide imprime `[structured output: <erreur>]` sur stderr, rien sur stdout, et sort avec le code 1 ; une réponse valide sort avec le code 0. Voir [`structuredOutput`](configuration.md#structuredoutput). |
135136
136137 ## Spécificités du mode ask
137138
@@ -132,6 +132,7 @@ mm v0.1.7 | config: agent.llamacpp.yaml | provider: llamacpp | model: jetbrains/
132 | Fichier de config | Peut apparaître avant ou après le flag `-prompt`/`-p` : `mm -p "prompt" config.yaml` et `mm config.yaml -p "prompt"` fonctionnent tous deux. |132 | Fichier de config | Peut apparaître avant ou après le flag `-prompt`/`-p` : `mm -p "prompt" config.yaml` et `mm config.yaml -p "prompt"` fonctionnent tous deux. |
133 | Compatibilité | Incompatible avec `-acp` et `-tui` ; le mode invite prend la priorité et s'exécute dans tous les cas. |133 | Compatibilité | Incompatible avec `-acp` et `-tui` ; le mode invite prend la priorité et s'exécute dans tous les cas. |
134 | Permissions | Personne ne peut répondre. Avec `yolo: false`, chaque appel qui demanderait est refusé (`[command rejected by the user]` pour le modèle) et stderr le dit une fois : `[yolo is off and this mode cannot ask: tool calls that need a confirmation are rejected — set yolo: true to let them run]`. Les outils MCP marqués `ask` sont refusés de la même façon, quoi que dise `yolo`. |134 | Permissions | Personne ne peut répondre. Avec `yolo: false`, chaque appel qui demanderait est refusé (`[command rejected by the user]` pour le modèle) et stderr le dit une fois : `[yolo is off and this mode cannot ask: tool calls that need a confirmation are rejected — set yolo: true to let them run]`. Les outils MCP marqués `ask` sont refusés de la même façon, quoi que dise `yolo`. |
135+| Avec `structuredOutput` | stdout porte la réponse JSON et un saut de ligne, rien d'autre : pas de texte en flux, pas de 🤖, pas de ligne de statistiques. Les messages vont sur stderr. Une réponse en échec ou invalide imprime `[structured output: <erreur>]` sur stderr, rien sur stdout, et sort avec le code 1 ; une réponse valide sort avec le code 0. Voir [`structuredOutput`](configuration.md#structuredoutput). |
135 136
136 ## Spécificités du mode ask137 ## Spécificités du mode ask
137 138
modified docs/fr/reference/configuration.md +53 -1
Large diff collapsed to keep the page fast — load it when you need it.
added internal/agent/structured.go +53 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/agent/structured_test.go +77 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/config/config.go +14 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/config/structured.go +49 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/config/structured_test.go +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/engine/engine.go +6 -0
@@ -355,6 +355,11 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
355355 ai.WithConfig(config.Cfg.Sampling),
356356 ai.WithUse(capture),
357357 }
358+ // structuredOutput (ticket 80): the server constrains the answer to the
359+ // schema, and Genkit checks it against the schema before handing it back.
360+ if config.Cfg.OutputSchema != nil {
361+ opts = append(opts, ai.WithOutputSchema(config.Cfg.OutputSchema))
362+ }
358363
359364 // The prefix is printed by the first token rather than up front, so the
360365 // spinner has a line to itself and vanishes without taking anything with it.
@@ -506,6 +511,7 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
506511 }
507512
508513 think.done()
514+ err = cutStructuredAnswer(resp, err)
509515
510516 // `full` stops at the last call to the model; its answer is appended after.
511517 if resp != nil && resp.Message != nil {
@@ -355,6 +355,11 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
355 ai.WithConfig(config.Cfg.Sampling),355 ai.WithConfig(config.Cfg.Sampling),
356 ai.WithUse(capture),356 ai.WithUse(capture),
357 }357 }
358+ // structuredOutput (ticket 80): the server constrains the answer to the
359+ // schema, and Genkit checks it against the schema before handing it back.
360+ if config.Cfg.OutputSchema != nil {
361+ opts = append(opts, ai.WithOutputSchema(config.Cfg.OutputSchema))
362+ }
358 363
359 // The prefix is printed by the first token rather than up front, so the364 // The prefix is printed by the first token rather than up front, so the
360 // spinner has a line to itself and vanishes without taking anything with it.365 // spinner has a line to itself and vanishes without taking anything with it.
@@ -506,6 +511,7 @@ func (e *Engine) Generate(ctx context.Context, messages []*ai.Message, tools []a
506 }511 }
507 512
508 think.done()513 think.done()
514+ err = cutStructuredAnswer(resp, err)
509 515
510 // `full` stops at the last call to the model; its answer is appended after.516 // `full` stops at the last call to the model; its answer is appended after.
511 if resp != nil && resp.Message != nil {517 if resp != nil && resp.Message != nil {
modified internal/engine/openai_compat.go +14 -1
Large diff collapsed to keep the page fast — load it when you need it.
added internal/engine/structured.go +24 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/engine/structured_test.go +151 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/structured/example_test.go +19 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/structured/ordered.go +66 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/structured/structured.go +267 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/structured/structured_test.go +156 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/structured/types.go +183 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified main.go +47 -7
Large diff collapsed to keep the page fast — load it when you need it.
modified main_test.go +20 -0
Large diff collapsed to keep the page fast — load it when you need it.