Structured output #27
Merged@k33g wants to merge
feature/structured-output into mainMerged 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="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" 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="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" 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="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" 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="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" 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="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" 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="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" 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="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" 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="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" 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="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" 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="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" 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="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" 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="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" 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="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" 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="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" 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="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" 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="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" 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="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" 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="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" 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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" 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="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" 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="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" 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="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" 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="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" 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="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" 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="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" 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="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" 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="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." 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="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" 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="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" 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="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" 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="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" 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="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" 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="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" 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="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" 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="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" 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="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" 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="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" 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="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" 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="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" 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="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" 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="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" 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="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" 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="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" 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="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" 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="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" 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="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" 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="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" 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="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" 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="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" 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="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" 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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" 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="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" 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="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" 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="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" 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="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" 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="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" 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="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" 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="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" 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="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." 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="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" 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="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" 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="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" 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="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" 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="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" 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="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" 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="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" 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="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" 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="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" 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="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" 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="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" 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="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" 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="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" 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="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" 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="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" 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="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" 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="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" 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="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" 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="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" 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="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" 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="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" 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="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" 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="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" 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="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" 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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" 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="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" 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="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" 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="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" 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="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" 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="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" 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="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" 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="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" 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="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." 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="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" 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="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" 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="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" 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="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" 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="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" 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="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" 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="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" 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="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" 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="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" 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="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" 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="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" 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="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" 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="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" 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="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" 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="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" 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="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" 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="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" 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="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" 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="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" 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="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" 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="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" 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="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" 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="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" 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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" 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="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" 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="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" 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="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" 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="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" 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="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" 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="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" 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="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" 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="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." 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="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" 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="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" 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="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" 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="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" 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="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" 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="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" 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) | ||
| 15 | 15 | - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/` |
| 16 | 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` |
| 17 | 17 | - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq |
| 18 | +- [Get answers as JSON](how-to/get-json-answers.md) — `structuredOutput`: Go declarations, `-p … | jq`, a JSON agent behind a coordinator | |
| 18 | 19 | - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo` |
| 19 | 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 | 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 | 64 | - [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 | 65 | - [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 | 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 | 68 | - [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 | 69 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| 68 | 70 | |
| @@ -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 jq | 17 | - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq |
| 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 limits | 64 | - [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 alive | 65 | - [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 limit | 68 | - [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 | ||
| 9 | 9 | | Package | Responsibility | |
| 10 | 10 | |---------|----------------| |
| 11 | 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 | 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 | 14 | | `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). | |
| 14 | 15 | | `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/ | ||
| 132 | 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 | 133 | | Compatibility | Incompatible with `-acp` and `-tui`; prompt mode takes precedence and runs regardless. | |
| 134 | 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 | 137 | ## Ask mode specifics |
| 137 | 138 | |
| @@ -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 specifics | 137 | ## 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: | ||
| 15 | 15 | - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/` |
| 16 | 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` |
| 17 | 17 | - [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq |
| 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 | 19 | - [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 | 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 | 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 | 64 | - [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 | 65 | - [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 | 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 | 68 | - [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 | 69 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| 68 | 70 | |
| @@ -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 jq | 17 | - [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq |
| 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 agent | 19 | - [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 connues | 64 | - [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 vie | 65 | - [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 profondeur | 68 | - [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 | ||
| 9 | 9 | | Paquet | Responsabilité | |
| 10 | 10 | |--------|----------------| |
| 11 | 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 | 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 | 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 | 15 | | `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/ | ||
| 132 | 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 | 133 | | Compatibilité | Incompatible avec `-acp` et `-tui` ; le mode invite prend la priorité et s'exécute dans tous les cas. | |
| 134 | 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 | 137 | ## Spécificités du mode ask |
| 137 | 138 | |
| @@ -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 ask | 137 | ## 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 | ||
| 355 | 355 | ai.WithConfig(config.Cfg.Sampling), |
| 356 | 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 | 364 | // The prefix is printed by the first token rather than up front, so the |
| 360 | 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 | 513 | think.done() |
| 514 | + err = cutStructuredAnswer(resp, err) | |
| 509 | 515 | |
| 510 | 516 | // `full` stops at the last call to the model; its answer is appended after. |
| 511 | 517 | 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 the | 364 | // 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.